//! EdDSA utilities
use ed25519_dalek::{
    pkcs8::{
        spki::{
            der::pem::LineEnding,
            Error as Pkcs8Error,
        },
        DecodePublicKey,
        EncodePublicKey,
        PublicKeyBytes,
    },
    SecretKey,
    SigningKey,
    Signature,
    SignatureError,
    Signer,
    Verifier,
    VerifyingKey,
};

use crate::{
    multibase::Multibase,
    multicodec::Multicodec,
};

pub type Ed25519SecretKey = SecretKey;
pub type Ed25519PublicKey = VerifyingKey;
pub type EddsaError = SignatureError;

pub fn generate_ed25519_key() -> SecretKey {
    let mut rng = rand::rngs::OsRng;
    let keypair = SigningKey::generate(&mut rng);
    keypair.to_bytes()
}

#[cfg(any(test, feature = "test-utils"))]
pub fn generate_weak_ed25519_key() -> SecretKey {
    use rand::SeedableRng;
    let mut rng = rand::rngs::StdRng::seed_from_u64(0);
    let keypair = SigningKey::generate(&mut rng);
    keypair.to_bytes()
}

#[derive(thiserror::Error, Debug)]
pub enum Ed25519SerializationError {
    #[error("conversion error")]
    ConversionError,

    #[error(transparent)]
    KeyError(#[from] SignatureError),

    #[error(transparent)]
    Pkcs8Error(#[from] Pkcs8Error),

    #[error("multikey error")]
    MultikeyError,
}

pub fn ed25519_secret_key_from_bytes(
    bytes: &[u8],
) -> Result<SecretKey, Ed25519SerializationError> {
    let secret_key: SecretKey = bytes.try_into()
        .map_err(|_| Ed25519SerializationError::ConversionError)?;
    Ok(secret_key)
}

pub fn ed25519_secret_key_to_multikey(
    secret_key: &Ed25519SecretKey,
) -> String {
    let secret_key_multicode = Multicodec::Ed25519Priv.encode(secret_key);
    Multibase::Base58Btc.encode(&secret_key_multicode)
}

pub fn ed25519_secret_key_from_multikey(
    secret_key_multibase: &str,
) -> Result<Ed25519SecretKey, Ed25519SerializationError> {
    let secret_key_multicode = Multibase::Base58Btc
        .decode_exact(secret_key_multibase)
        .map_err(|_| Ed25519SerializationError::MultikeyError)?;
    let secret_key_bytes =
        Multicodec::Ed25519Priv.decode_exact(&secret_key_multicode)
            .map_err(|_| Ed25519SerializationError::MultikeyError)?;
    let secret_key = ed25519_secret_key_from_bytes(&secret_key_bytes)?;
    Ok(secret_key)
}

pub fn ed25519_public_key_from_bytes(
    bytes: &[u8],
) -> Result<VerifyingKey, Ed25519SerializationError> {
    let bytes: [u8; 32] = bytes.try_into()
        .map_err(|_| Ed25519SerializationError::ConversionError)?;
    let public_key = VerifyingKey::from_bytes(&bytes)?;
    Ok(public_key)
}

/// Encodes ed25519 public key into PEM format
pub fn ed25519_public_key_to_pkcs8_pem(
    public_key: VerifyingKey,
) -> Result<String, Ed25519SerializationError> {
    // LF = \n
    let public_key_pem = public_key.to_public_key_pem(LineEnding::LF)?;
    Ok(public_key_pem)
}

/// Decodes ed25519 public key from PEM format
pub fn ed25519_public_key_from_pkcs8_pem(
    public_key_pem: &str,
) -> Result<VerifyingKey, Ed25519SerializationError> {
    let public_key_bytes = PublicKeyBytes::from_public_key_pem(public_key_pem)?;
    let public_key = VerifyingKey::from_bytes(public_key_bytes.as_ref())?;
    Ok(public_key)
}

pub fn ed25519_public_key_from_secret_key(
    secret_key: &SecretKey,
) -> VerifyingKey {
    SigningKey::from(secret_key).verifying_key()
}

pub fn ed25519_public_key_to_multikey(
    public_key: &Ed25519PublicKey,
) -> String {
    let public_key_multicode =
        Multicodec::Ed25519Pub.encode(public_key.as_bytes());
    Multibase::Base58Btc.encode(&public_key_multicode)
}

pub fn ed25519_public_key_from_multikey(
    multikey: &str,
) -> Result<Ed25519PublicKey, Ed25519SerializationError> {
    let public_key_multicode = Multibase::Base58Btc
        .decode_exact(multikey)
        .map_err(|_| Ed25519SerializationError::MultikeyError)?;
    let public_key_bytes =
        Multicodec::Ed25519Pub.decode_exact(&public_key_multicode)
            .map_err(|_| Ed25519SerializationError::MultikeyError)?;
    let public_key = ed25519_public_key_from_bytes(&public_key_bytes)?;
    Ok(public_key)
}

pub fn create_eddsa_signature(
    secret_key: &SecretKey,
    message: &[u8],
) -> [u8; 64] {
    let secret_key = SigningKey::from_bytes(secret_key);
    let signature = secret_key.sign(message);
    signature.to_bytes()
}

pub fn verify_eddsa_signature(
    public_key: &VerifyingKey,
    message: &[u8],
    signature: &[u8],
) -> Result<(), SignatureError> {
    let signature = Signature::try_from(signature)?;
    public_key.verify(message, &signature)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_secret_key_multikey_encode_decode() {
        let secret_key = generate_weak_ed25519_key();
        let encoded = ed25519_secret_key_to_multikey(&secret_key);
        let decoded = ed25519_secret_key_from_multikey(&encoded).unwrap();
        assert_eq!(decoded, secret_key);
    }

    #[test]
    fn test_public_key_pem_encode_decode() {
        let secret_key = generate_weak_ed25519_key();
        let public_key = ed25519_public_key_from_secret_key(&secret_key);
        let encoded = ed25519_public_key_to_pkcs8_pem(public_key).unwrap();
        let decoded = ed25519_public_key_from_pkcs8_pem(&encoded).unwrap();
        assert_eq!(decoded, public_key);
    }

    #[test]
    fn test_public_key_multikey_encode_decode() {
        let secret_key = generate_weak_ed25519_key();
        let public_key = ed25519_public_key_from_secret_key(&secret_key);
        let encoded = ed25519_public_key_to_multikey(&public_key);
        let decoded = ed25519_public_key_from_multikey(&encoded).unwrap();
        assert_eq!(decoded, public_key);
    }

    #[test]
    fn test_verify_eddsa_signature() {
        let secret_key = generate_ed25519_key();
        let message = "test";
        let signature = create_eddsa_signature(
            &secret_key,
            message.as_bytes(),
        );
        let public_key =
            ed25519_public_key_from_secret_key(&secret_key);
        let result = verify_eddsa_signature(
            &public_key,
            message.as_bytes(),
            &signature,
        );
        assert_eq!(result.is_ok(), true);
    }
}
