use crate::{EncryptedMessage, FrostError};
use ghost_networks::NetworkCurve;

use chacha20poly1305::{
    aead::{self, Aead, AeadCore, KeyInit},
    Key as EncryptionKey,
    Nonce as EncryptionNonce,
    ChaCha20Poly1305 as EncryptionCipher,
};

use hkdf::Hkdf as KeyDerivation;
use sha2::{Digest, Sha256 as Hashing};

use rand_chacha::rand_core::{CryptoRng, RngCore};
use sp_std::collections::btree_map::BTreeMap;

macro_rules! with_ciphersuite {
    ($curve:expr, |$alias:ident| $body:block) => {
        match $curve {
            ExodusCurve::Secp256k1 => {
                use frost_secp256k1 as $alias;
                $body
            },
            NetworkCurve::Ed25519 => {
                use frost_ed25519 as $alias;
                $body

            }
        }
    }
}

impl ExodusCurve {
    pub fn dkg_verify_proof_of_knowledge(
        &self,
        index: u16,
        round1_package_bytes: &[u8],
    ) -> Result<(), FrostError> {
        with_ciphersuite!(self, |f| {
            let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
            let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            frost_core::keys::dkg::verify_proof_of_knowledge(
                identifier,
                &round1_package.commitment(),
                &round1_package.proof_of_knowledge(),
            ).map_err(|_| FrostError::InvalidProofOfKnowledge)
        })
    }

    pub fn verify_signature_share(
        &self,
        index: u16,
        verifying_share_bytes: &[u8],
        signature_share_bytes: &[u8],
        signing_package_bytes: &[u8],
        verifying_key_bytes: &[u8],
    ) -> Result<(), FrostError> {
        with_ciphersuite!(self, |f| {
            let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
            let verifying_share = f::keys::VerifyingShare::deserialize(verifying_share_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let signature_share = f::round2::SignatureShare::deserialize(signature_share_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes)
            .map_err(|_| FrostError::DeserializationError)?;

            frost_core::verify_signature_share(
                identifier,
                &verifying_share,
                &signature_share,
                &signing_package,
                &verifying_key,
            ).map_err(|err| match err {
                f::Error::IdentityCommitment => FrostError::IdentityCommitment,
                f::Error::UnknownIdentifier => FrostError::UnknownIdentifier,
                f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier,
                f::Error::InvalidSignatureShare { .. } => FrostError::InvalidSignatureShare,
                _ => FrostError::Unknown,
            })
        })
    }

    pub fn aggregate_signature(
        &self,
        signing_package_bytes: &[u8],
        signature_shares_bytes: &BTreeMap<u16, Vec<u8>>,
        pubkeys_bytes: &[u8],
    ) -> Result<Vec<u8>, FrostError> {
        with_ciphersuite!(self, |f| {
            let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let mut signature_shares = BTreeMap::new();
            for (&index, signature_share_bytes) in signature_shares_bytes.iter() {
                let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
                let signature_share = f::round2::SignatureShare::deserialize(signature_share_bytes)
                    .map_err(|_| FrostError::DeserializationError)?;
                signature_shares.insert(identifier, signature_share);
            }

            let pubkeys = f::keys::PublicKeyPackage::deserialize(pubkeys_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let signature = f::aggregate(&signing_package, &signature_shares, &pubkeys)
                .map_err(|err| match err {
                    f::Error::UnknownIdentifier => FrostError::UnknownIdentifier,
                    f::Error::SerializationError => FrostError::SerializationError,
                    f::Error::IdentityCommitment => FrostError::IdentityCommitment,
                    f::Error::IncorrectNumberOfIdentifiers => FrostError::IncorrectNumberOfIdentifiers,
                    f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier,
                    f::Error::InvalidSecretShare { .. } => FrostError::InvalidSecretShare,
                    f::Error::InvalidSignature { .. } => FrostError::InvalidSignature,
                    _ => FrostError::Unknown,
                })?;

            let signature_bytes = signature.serialize()
                .map_err(|_| FrostError::SerializationError)?;

            Ok(signature_bytes)
        })
    }

    pub fn is_signature_valid(
        &self,
        pubkey_bytes: &[u8],
        signature_bytes: &[u8],
        message: &[u8],
    ) -> bool {
        with_ciphersuite!(self, |f| {
            let get_verification_result = || -> Result<(), FrostError> {
                let pubkeys = f::keys::PublicKeyPackage::deserialize(pubkey_bytes)
                    .map_err(|_| FrostError::DeserializationError)?;

                let signature = f::Signature::deserialize(signature_bytes)
                    .map_err(|_| FrostError::DeserializationError)?;

                pubkeys.verifying_key()
                    .verify(message, &signature)
                    .map_err(|_| FrostError::InvalidSignature)?;

                Ok(())
            };

            get_verification_result().is_ok()
        })
    }

    pub fn dkg_part1<R: RngCore + CryptoRng>(
        &self,
        index: u16,
        max_signers: u16,
        min_signers: u16,
        mut rng: R,
    ) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
        with_ciphersuite!(self, |f| {
            let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
            let (round1_secret_package, round1_package) = f::keys::dkg::part1(
                identifier,
                max_signers,
                min_signers,
                &mut rng,
            ).map_err(|err| {
                match err {
                    f::Error::InvalidMinSigners => FrostError::InvalidMinSigners(min_signers, max_signers),
                    f::Error::InvalidMaxSigners => FrostError::InvalidMaxSigners(max_signers),
                    f::Error::DKGNotSupported => FrostError::DKGNotSupported(*self),
                    _ => FrostError::Unknown,
                }
            })?;

            let round1_secret_package_bytes = round1_secret_package.serialize()
                .map_err(|_| FrostError::SerializationError)?;


            let round1_package_bytes = round1_package.serialize()
                .map_err(|_| FrostError::SerializationError)?;

            Ok((round1_secret_package_bytes, round1_package_bytes))
        })
    }

    fn convert_identifier_to_index(
        identifier_bytes: Vec<u8>
    ) -> Result<u16, FrostError> {
        const SIZE: usize = core::mem::size_of::<u16>();

        let start = identifier_bytes.len().checked_sub(SIZE)
            .ok_or(FrostError::InvalidParticipantId)?;

        let bytes_array = identifier_bytes.get(start..)
            .and_then(|slice| slice.try_into().ok())
            .ok_or(FrostError::InvalidParticipantId)?;

        Ok(u16::from_be_bytes(bytes_array))
    }

    pub fn dkg_part2(
        &self,
        round1_secret_package_bytes: &[u8],
        round1_packages_bytes: &BTreeMap<u16, Vec<u8>>,
    ) -> Result<(Vec<u8>, BTreeMap<u16, Vec<u8>>), FrostError> {
        with_ciphersuite!(self, |f| {
            let round1_secret_package = f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let mut round1_packages = BTreeMap::new();
            for (&index, round1_package_bytes) in round1_packages_bytes.iter() {
                let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
                let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
                    .map_err(|_| FrostError::DeserializationError)?;

                round1_packages.insert(identifier, round1_package);
            }

            let (round2_secret_package, round2_packages) = f::keys::dkg::part2(
                round1_secret_package,
                &round1_packages,
            ).map_err(|err| match err {
                f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments,
                f::Error::IncorrectNumberOfPackages => FrostError::IncorrectNumberOfPackages,
                f::Error::InvalidProofOfKnowledge { .. }  => FrostError::InvalidProofOfKnowledge,
                _ => FrostError::Unknown,
            })?;

            let round2_secret_package_bytes = round2_secret_package.serialize()
                .map_err(|_| FrostError::SerializationError)?;

            let mut round2_packages_bytes = BTreeMap::new();
            for (participant_identifier, round2_package) in round2_packages.iter() {
                let index = Self::convert_identifier_to_index(
                    participant_identifier.serialize(),
                )?;

                let round2_package_bytes = round2_package.serialize()
                .map_err(|_| FrostError::SerializationError)?;

                round2_packages_bytes.insert(index as u16, round2_package_bytes);
            }

            Ok((round2_secret_package_bytes, round2_packages_bytes))
        })
    }

    pub fn dkg_encrypt_round2_package<R: RngCore + CryptoRng>(
        &self,
        cipher: &EncryptionCipher,
        associated_data: &[u8],
        round2_package_bytes: &[u8],
        mut rng: R,
    ) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
        let payload = aead::Payload {
            msg: round2_package_bytes,
            aad: associated_data,
        };

        let nonce = EncryptionCipher::generate_nonce(&mut rng);
        let ciphertext = cipher.encrypt(&nonce, payload)
            .map_err(|_| FrostError::EncryptionFailed)?;

        Ok((ciphertext, nonce.to_vec()))
    }

    pub fn dkg_decrypt_round2_package(
        &self,
        cipher: &EncryptionCipher,
        encrypted_message: &EncryptedMessage,
        associated_data: &[u8],
    ) -> Result<Vec<u8>, FrostError> {
        let nonce = EncryptionNonce::from_iter(encrypted_message.nonce.iter().cloned());
        let payload = aead::Payload {
            msg: &encrypted_message.ciphertext,
            aad: associated_data,
        };

        cipher.decrypt(&nonce, payload)
            .map_err(|_| FrostError::DecryptionFailed)
    }

    pub fn prepare_additional_info(
        &self,
        sender_index: u16,
        receiver_index: u16,
        session_index: u16,
    ) -> Vec<u8> {
        let mut additional_info = match self {
            ExodusCurve::Secp256k1 => b"EXODUS-SECP256K1-DKG-V1".to_vec(),
        };

        additional_info.extend_from_slice(&sender_index.to_be_bytes());
        additional_info.extend_from_slice(&receiver_index.to_be_bytes());
        additional_info.extend_from_slice(&session_index.to_be_bytes());

        additional_info
    }

    pub fn prepare_cipher_from_keys(
        &self,
        round1_receiver_package_bytes: &[u8],
        round1_secret_package_bytes: &[u8],
        additional_info: &[u8],
    ) -> Result<EncryptionCipher, FrostError> {
        with_ciphersuite!(self, |f| {
            let coefficients =
                f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes)
                    .map_err(|_| FrostError::DeserializationError)?
                    .coefficients();

            let sender_secret_key = coefficients
                .first()
                .ok_or(FrostError::IncorrectNumberOfCoefficients)?;

            let receiver_public_key =
                f::keys::dkg::round1::Package::deserialize(round1_receiver_package_bytes)
                    .map_err(|_| FrostError::DeserializationError)?
                    .commitment()
                    .coefficients()
                    .first()
                    .ok_or(FrostError::MissingCommitment)?
                    .value();

            let shared_secret_bytes = f::VerifyingKey::new(receiver_public_key * sender_secret_key)
                .serialize()
                .map_err(|_| FrostError::SerializationError)?;

            let salt = Hashing::digest(&additional_info);
            let hk = KeyDerivation::<Hashing>::new(Some(&salt), &shared_secret_bytes);

            let mut encryption_key = EncryptionKey::default();
            hk.expand(additional_info, &mut encryption_key)
                .map_err(|_| FrostError::HKDFFailed)?;

            let cipher = EncryptionCipher::new_from_slice(&encryption_key)
                .expect("could not happen; length is already correct qed");

            Ok(cipher)
        })
    }

    pub fn dkg_verify_private_package(
        &self,
        index: u16,
        round1_package_bytes: &[u8],
        round2_package_bytes: &[u8],
    ) -> Result<(), FrostError> {
        with_ciphersuite!(self, |f| {
            let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
            let commitment = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
                .map(|round1_package| round1_package.commitment().clone())
                .map_err(|_| FrostError::DeserializationError)?;

            let secret_share = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
                .map(|round2_package| {
                    let signing_share = round2_package.signing_share().clone();
                    f::keys::SecretShare::new(identifier, signing_share, commitment)
                })
                .map_err(|_| FrostError::DeserializationError)?;

            let _ = secret_share.verify().map_err(|_| FrostError::InvalidSecretShare)?;

            Ok(())
        })
    }

    pub fn dkg_part3(
        &self,
        round2_secret_package_bytes: &[u8],
        round1_packages_bytes: &BTreeMap<u16, Vec<u8>>,
        round2_packages_bytes: &BTreeMap<u16, Vec<u8>>,
    ) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
        with_ciphersuite!(self, |f| {
            let round2_secret_package = f::keys::dkg::round2::SecretPackage::deserialize(round2_secret_package_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let mut round1_packages = BTreeMap::new();
            for (&index, round1_package_bytes) in round1_packages_bytes.iter() {
                let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
                let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes)
                    .map_err(|_| FrostError::DeserializationError)?;
                round1_packages.insert(identifier, round1_package);
            }

            let mut round2_packages = BTreeMap::new();
            for (&index, round2_package_bytes) in round2_packages_bytes.iter() {
                let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
                let round2_package = f::keys::dkg::round2::Package::deserialize(round2_package_bytes)
                    .map_err(|_| FrostError::DeserializationError)?;
                round2_packages.insert(identifier, round2_package);
            }

            let (key_package, pubkey_package) = f::keys::dkg::part3(
                &round2_secret_package,
                &round1_packages,
                &round2_packages,
            ).map_err(|err| match err {
                f::Error::IncorrectNumberOfPackages => FrostError::IncorrectNumberOfPackages,
                f::Error::IncorrectPackage => FrostError::IncorrectPackage,
                f::Error::PackageNotFound => FrostError::PackageNotFound,
                f::Error::InvalidSecretShare { .. } => FrostError::InvalidSecretShare,
                f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments,
                _ => FrostError::Unknown,
            })?;

            let key_package_bytes = key_package.serialize()
                .map_err(|_| FrostError::SerializationError)?;

            let pubkey_package_bytes = pubkey_package.serialize()
                .map_err(|_| FrostError::SerializationError)?;

            Ok((key_package_bytes, pubkey_package_bytes))
        })
    }

    pub fn generate_nonce<R: CryptoRng + RngCore>(
        &self,
        secret_key_package_bytes: &[u8],
        mut rng: R
    ) -> Result<(Vec<u8>, Vec<u8>), FrostError> {
        with_ciphersuite!(self, |f| {
            let signing_share = f::keys::KeyPackage::deserialize(secret_key_package_bytes)
                .map(|key_package| *key_package.signing_share())
                .map_err(|_| FrostError::DeserializationError)?;

            let (signing_nonces, signing_commitments) =
                f::round1::commit(&signing_share, &mut rng);

            let signing_nonces_bytes = signing_nonces.serialize()
                .map_err(|_| FrostError::SerializationError)?;

            let signing_commitments_bytes = signing_commitments.serialize()
                .map_err(|_| FrostError::SerializationError)?;

            Ok((signing_nonces_bytes, signing_commitments_bytes))
        })
    }

    pub fn generate_signing_package(
        &self,
        commitments_map_bytes: &BTreeMap<u16, Vec<u8>>,
        message: &[u8],
    ) -> Result<Vec<u8>, FrostError> {
        with_ciphersuite!(self, |f| {
            let mut commitments_map = BTreeMap::new();
            for (&index, commitment_bytes) in commitments_map_bytes.iter() {
                let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?;
                let commitment = f::round1::SigningCommitments::deserialize(commitment_bytes)
                    .map_err(|_| FrostError::DeserializationError)?;
                commitments_map.insert(identifier, commitment);
            }

            f::SigningPackage::new(commitments_map, message)
                .serialize()
                .map_err(|_| FrostError::SerializationError)
        })
    }

    pub fn sign_message(
        &self,
        signing_package_bytes: &[u8],
        signer_nonces_bytes: &[u8],
        secret_key_share_bytes: &[u8]
    ) -> Result<Vec<u8>, FrostError> {
        with_ciphersuite!(self, |f| {
            let signing_package = f::SigningPackage::deserialize(signing_package_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let signer_nonces = f::round1::SigningNonces::deserialize(signer_nonces_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let secret_key_share = f::keys::KeyPackage::deserialize(secret_key_share_bytes)
                .map_err(|_| FrostError::DeserializationError)?;

            let signature_share_bytes = f::round2::sign(&signing_package, &signer_nonces, &secret_key_share)
                .map(|signature_share| signature_share.serialize())
                .map_err(|err| match err {
                    f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments,
                    f::Error::MissingCommitment => FrostError::MissingCommitment,
                    f::Error::IncorrectCommitment => FrostError::IncorrectCommitment,
                    f::Error::SerializationError => FrostError::SerializationError,
                    f::Error::UnknownIdentifier => FrostError::UnknownIdentifier,
                    f::Error::IdentityCommitment => FrostError::IdentityCommitment,
                    f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier,
                    f::Error::IncorrectNumberOfIdentifiers => FrostError::IncorrectNumberOfIdentifiers,
                    _ => FrostError::Unknown,
                })?;

            Ok(signature_share_bytes)
        })
    }
}
