use crate::error::DatError;
use aws_lc_rs::signature::{EcdsaKeyPair, UnparsedPublicKey, ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED, ECDSA_P384_SHA384_FIXED_SIGNING, ECDSA_P521_SHA512_FIXED, ECDSA_P521_SHA512_FIXED_SIGNING};
use std::str::FromStr;
use strum_macros::{Display, EnumString};

#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, Eq, PartialEq)]
pub enum DatSignatureAlgorithm {
    P256,
    P384,
    P521,
}

impl FromStr for DatSignatureAlgorithm {
    type Err = DatError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "P256" => Ok(DatSignatureAlgorithm::P256),
            "P384" => Ok(DatSignatureAlgorithm::P384),
            "P521" => Ok(DatSignatureAlgorithm::P521),
            _ => Err(DatError::UnknownSignatureAlgorithm),
        }
    }
}


#[repr(u8)]
#[derive(Debug, Display, EnumString, Clone, Copy, Eq, PartialEq)]
pub enum DatSignatureKeyExportOption {
    PAIR,
    SIGNING,
    VERIFYING,
}

pub struct DatSignatureKey {
    algorithm: DatSignatureAlgorithm,
    signing_key: Option<EcdsaKeyPair>,
    signing_key_bytes: Vec<u8>,
    verifying_key: UnparsedPublicKey<Vec<u8>>,
}

impl DatSignatureKey {
    pub fn generate(algorithm: DatSignatureAlgorithm) -> Result<Self, DatError> {
        Self::from_or_new(true, algorithm, &[], &[])
    }

    pub fn from_bytes(algorithm: DatSignatureAlgorithm, signing_key_bytes: &[u8], verifying_key_bytes: &[u8]) -> Result<DatSignatureKey, DatError> {
        Self::from_or_new(false, algorithm, signing_key_bytes, verifying_key_bytes)
    }
    fn from_or_new(new: bool, algorithm: DatSignatureAlgorithm, signing_key_bytes: &[u8], verifying_key_bytes: &[u8]) -> Result<DatSignatureKey, DatError> {
        let (sa, va) = match algorithm {
            DatSignatureAlgorithm::P256 => &(ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P256_SHA256_FIXED),
            DatSignatureAlgorithm::P384 => &(ECDSA_P384_SHA384_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED),
            DatSignatureAlgorithm::P521 => &(ECDSA_P521_SHA512_FIXED_SIGNING, ECDSA_P521_SHA512_FIXED),
        };
        let mut signing_key_bytes = Box::from(signing_key_bytes);
        let mut verifying_key_bytes = Box::from(verifying_key_bytes);

        if new {
            let pkcs8v1_pair = EcdsaKeyPair::generate(sa)
                .map_err(|_| DatError::GenerateSigningKeyError)?
                .to_pkcs8v1()
                .map_err(|_| DatError::GenerateSigningKeyError)?;
            let (sk, vk) = to_raw_pkcs8v1(algorithm, pkcs8v1_pair.as_ref())?;
            signing_key_bytes = sk;
            verifying_key_bytes = vk;
        } else if verifying_key_bytes.is_empty() {
            if signing_key_bytes.is_empty() {
                return Err(DatError::InvalidSignatureKey);
            }
            verifying_key_bytes = calc_public_key(algorithm, &*signing_key_bytes)?;
        }

        let signing_key: Option<EcdsaKeyPair> = if signing_key_bytes.is_empty() {
            None
        } else {
            Some(EcdsaKeyPair::from_private_key_and_public_key(sa, &*signing_key_bytes, &*verifying_key_bytes)
                .map_err(|_| { DatError::InvalidSignatureKey })?)
        };

        Ok(DatSignatureKey {
            algorithm,
            signing_key,
            signing_key_bytes: Vec::from(signing_key_bytes),
            verifying_key: UnparsedPublicKey::new(va, Vec::from(verifying_key_bytes)),
        })
    }

    #[inline]
    pub fn algorithm(&self) -> DatSignatureAlgorithm {
        self.algorithm
    }

    #[inline]
    pub fn signature_size(&self) -> usize {
        match self.algorithm {
            DatSignatureAlgorithm::P256 => 64,
            DatSignatureAlgorithm::P384 => 96,
            DatSignatureAlgorithm::P521 => 132,
        }
    }

    #[inline]
    pub fn raw_signing_key_size(&self) -> usize {
        match self.algorithm {
            DatSignatureAlgorithm::P256 => 32,
            DatSignatureAlgorithm::P384 => 48,
            DatSignatureAlgorithm::P521 => 66,
        }
    }

    #[inline]
    pub fn to_bytes(&self) -> (Box<[u8]>, Box<[u8]>) {
        (Box::from(&*self.signing_key_bytes), Box::from(&*self.verifying_key.as_ref()))
    }

    pub fn sign(&self, data: &[u8]) -> Result<Box<[u8]>, DatError> {
        self.signing_key.as_ref().ok_or_else(|| DatError::VerifyOnlyCertificate)?
            .sign(&aws_lc_rs::rand::SystemRandom::new(), data)
            .map(|x| Box::from(x.as_ref()))
            .map_err(|_| DatError::SignError)
    }

    pub fn verify(&self, body: &[u8], sign: &[u8]) -> Result<(), DatError> {
        self.verifying_key.verify(body, sign).map_err(|_| DatError::InvalidDat)
    }

    #[inline]
    pub fn has_signing_key(&self) -> bool {
        self.signing_key.is_some()
    }
}

impl Clone for DatSignatureKey {
    fn clone(&self) -> Self {
        let (k, v) = self.to_bytes();
        DatSignatureKey::from_bytes(self.algorithm(), &*k, &*v).unwrap()
    }
}

pub fn calc_public_key(algorithm: DatSignatureAlgorithm, signing_key: &[u8]) -> Result<Box<[u8]>, DatError> {
    match algorithm {
        DatSignatureAlgorithm::P256 => { p256::ecdsa::SigningKey::from_slice(signing_key).map(|x| x.verifying_key().to_sec1_bytes()) },
        DatSignatureAlgorithm::P384 => { p384::ecdsa::SigningKey::from_slice(signing_key).map(|x| x.verifying_key().to_sec1_bytes()) },
        DatSignatureAlgorithm::P521 => { p521::ecdsa::SigningKey::from_slice(signing_key).map(|x| x.verifying_key().to_sec1_bytes()) },
    }.map_err(|_| DatError::InvalidSignatureKey)
}

pub fn to_raw_pkcs8v1(algorithm: DatSignatureAlgorithm, pkcs8_der: &[u8]) -> Result<(Box<[u8]>, Box<[u8]>), DatError> {
    let (offset, private_len, public_len) = match algorithm {
        DatSignatureAlgorithm::P256 => (36, 68, 65),
        DatSignatureAlgorithm::P384 => (35, 83 ,97),
        DatSignatureAlgorithm::P521 => (35, 101, 133),
    };
    if pkcs8_der.len() > public_len {
        Ok((Box::from(&pkcs8_der[offset..private_len]), Box::from(&pkcs8_der[pkcs8_der.len() - public_len..])))
    } else {
        Err(DatError::InvalidSignatureKey)
    }
}
