use crate::crypto::{DatCrypto, DatCryptoAlgorithm};
use crate::error::DatError;
use crate::signature::{DatSignature, DatSignatureAlgorithm};
use crate::util::{decode_base64_url, encode_base64_url_out, now_unix_timestamp};
use std::cmp::PartialEq;
use std::str::FromStr;

pub struct DatCertificate {
    pub cid: u64,
    pub(crate) signature: DatSignature,
    pub(crate) crypto: DatCrypto,
    pub(crate) dat_issue_begin: u64,
    pub(crate) dat_issue_end: u64,
    pub(crate) dat_ttl: u64,
}

impl DatCertificate {
    pub fn generate(cid: u64, issued_at: u64, issuance_duration: u64, dat_ttl: u64, signature_algorithm: DatSignatureAlgorithm, crypto_algorithm: DatCryptoAlgorithm) -> Result<Self, DatError> {
        Self::from(cid, issued_at, issuance_duration, dat_ttl, DatSignature::generate(signature_algorithm)?, DatCrypto::generate(crypto_algorithm))
    }

    pub fn from(cid: u64, issued_at: u64, issuance_duration: u64, dat_ttl: u64, signature_key: DatSignature, crypto_key: DatCrypto) -> Result<Self, DatError> {
        if dat_ttl == 0 {
            return Err(DatError::InvalidDatTtl);
        }

        if issuance_duration < dat_ttl * 2 && issuance_duration < (dat_ttl + 3600) {
            return Err(DatError::InvalidIssuanceDuration);
        }

        Ok(DatCertificate {
            cid,
            signature: signature_key,
            crypto: crypto_key,
            dat_issue_begin: issued_at,
            dat_issue_end: issued_at + issuance_duration,
            dat_ttl,
        })
    }

    #[inline]
    pub fn expired(&self) -> bool {
        (self.dat_issue_end + self.dat_ttl) < now_unix_timestamp()
    }

    #[inline]
    pub fn issuable(&self) -> bool {
        self.signable() && (self.dat_issue_begin..=self.dat_issue_end).contains(&now_unix_timestamp())
    }

    #[inline]
    pub fn signable(&self) -> bool {
        self.signature.signable()
    }

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

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

    pub fn export(&self, verify_only: bool) -> Result<String, DatError> {
        let mut ib = itoa::Buffer::new();
        // cid[16].issued_at[20].issuance_duration[20].dat_ttl[20].signature.crypto
        // spare size = 4 + 16 + 20  + 20 + 20 = 80
        let mut v: String = String::with_capacity(
            80 +
                self.signature.key_base64_len() + self.crypto.key_base64_len()
        );

        v.push_str(ib.format(self.cid));
        v.push('.');
        v.push_str(ib.format(self.dat_issue_begin));
        v.push('.');
        v.push_str(ib.format(self.dat_issue_end - self.dat_issue_begin));
        v.push('.');
        v.push_str(ib.format(self.dat_ttl));
        v.push('.');
        v.push_str(&*self.signature.algorithm().as_str());
        v.push('.');
        v.push_str(&*self.crypto.algorithm().as_str());
        v.push('.');
        encode_base64_url_out(self.signature.export_key_option(verify_only)?, &mut v);
        v.push('.');
        encode_base64_url_out(self.crypto.export_key(), &mut v);

        Ok(v)
    }
}

impl FromStr for DatCertificate {
    type Err = DatError;
    fn from_str(format: &str) -> Result<Self, Self::Err> {
        let parts = format.split(".").collect::<Vec<&str>>();
        let count = parts.len();
        if count == 8 {
            let cid = u64::from_str_radix(parts[0], 16).map_err(|_| DatError::InvalidDat)?;
            let issued_at = parts[1].parse::<u64>().map_err(|_| DatError::InvalidCertificateFormat)?;
            let issuance_duration = parts[2].parse::<u64>().map_err(|_| DatError::InvalidCertificateFormat)?;
            let dat_ttl = parts[3].parse::<u64>().map_err(|_| DatError::InvalidCertificateFormat)?;
            let signature_algorithm = DatSignatureAlgorithm::from_str(parts[4])?;
            let crypto_algorithm = DatCryptoAlgorithm::from_str(parts[5])?;
            let signature = DatSignature::from_key(signature_algorithm, &*decode_base64_url(parts[6])?)?;
            let crypto = DatCrypto::from_key(crypto_algorithm, &*decode_base64_url(parts[7])?)?;
            DatCertificate::from(cid, issued_at, issuance_duration, dat_ttl, signature, crypto)
        } else {
            Err(DatError::InvalidCertificateFormat)
        }
    }
}

impl PartialEq<DatCertificate> for DatCertificate {
    fn eq(&self, other: &DatCertificate) -> bool {
        self.cid.eq(&other.cid)
    }
}

impl PartialEq<u64> for DatCertificate {
    fn eq(&self, other: &u64) -> bool {
        self.cid.eq(other)
    }
}

impl Clone for DatCertificate {
    fn clone(&self) -> Self {
        DatCertificate {
            cid: self.cid,
            signature: self.signature.clone(),
            crypto: self.crypto.clone(),
            dat_issue_begin: self.dat_issue_begin,
            dat_issue_end: self.dat_issue_end,
            dat_ttl: self.dat_ttl,
        }
    }
}
