use crate::crypto::DatCryptoAlgorithm;
use crate::crypto::DatCryptoKey;
use crate::error::DatError;
use crate::signature::DatSignatureAlgorithm;
use crate::signature::{DatSignatureKey, DatSignatureKeyExportOption};
use crate::util::{decode_base64_url, encode_base64_url_out, now_unix_timestamp, to_hex_from_u64_out};
use std::cmp::PartialEq;
use std::str::FromStr;

pub struct DatCertificate {
    pub cid: u64,
    pub(crate) cid_pre_copy: String,
    pub(crate) signature_key: DatSignatureKey,
    pub(crate) crypto_key: DatCryptoKey,
    pub(crate) dat_issue_begin: u64,
    pub(crate) dat_issue_end: u64,
    pub(crate) dat_ttl: u64,
}

impl DatCertificate {
    pub fn generate(cid: u64, signature_algorithm: DatSignatureAlgorithm, crypto_algorithm: DatCryptoAlgorithm, issue_begin: u64, issue_end: u64, dat_ttl: u64) -> Result<Self, DatError> {
        Self::from(cid, DatSignatureKey::generate(signature_algorithm)?, DatCryptoKey::generate(crypto_algorithm), issue_begin, issue_end, dat_ttl)
    }

    pub fn from(cid: u64, signature_key: DatSignatureKey, crypto_key: DatCryptoKey, dat_issue_begin: u64, dat_issue_end: u64, dat_ttl: u64) -> Result<Self, DatError> {
        let mut cid_pre_copy = String::with_capacity(18);
        cid_pre_copy.push('.');
        to_hex_from_u64_out(cid, &mut cid_pre_copy);
        cid_pre_copy.push('.');

        Ok(DatCertificate {
            cid,
            cid_pre_copy,
            signature_key,
            crypto_key,
            dat_issue_begin,
            dat_issue_end,
            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.has_signing_key() && (self.dat_issue_begin..=self.dat_issue_end).contains(&now_unix_timestamp())
    }

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

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

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

    pub fn export(&self, signature_key_out_option: DatSignatureKeyExportOption) -> Result<String, DatError> {
        // TODO 길이 계산 필요.
        let mut v: String = String::with_capacity(300);

        to_hex_from_u64_out(self.cid, &mut v);
        v.push('.');
        v.push_str(&*self.signature_key.algorithm().to_string());
        v.push('.');
        let (sk, vk) = self.signature_key.to_bytes();
        if sk.is_empty() && signature_key_out_option != DatSignatureKeyExportOption::VERIFYING {
            return Err(DatError::VerifyOnlyCertificate)
        }
        match signature_key_out_option {
            DatSignatureKeyExportOption::PAIR => {
                encode_base64_url_out(sk, &mut v);
                v.push('~');
                encode_base64_url_out(vk, &mut v);
            }
            DatSignatureKeyExportOption::SIGNING => {
                encode_base64_url_out(sk, &mut v);
            },
            DatSignatureKeyExportOption::VERIFYING => {
                v.push('~');
                encode_base64_url_out(vk, &mut v);
            },
        };
        v.push('.');
        v.push_str(&*self.crypto_key.algorithm().to_string());
        v.push('.');
        encode_base64_url_out(self.crypto_key.to_bytes(), &mut v);
        v.push('.');
        v.push_str(self.dat_issue_begin.to_string().as_str());
        v.push('.');
        v.push_str(self.dat_issue_end.to_string().as_str());
        v.push('.');
        v.push_str(self.dat_ttl.to_string().as_str());
        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 signature_algorithm = DatSignatureAlgorithm::from_str(parts[1])
                .map_err(|_| DatError::UnknownSignatureAlgorithm)?;
            let signature_key_str = parts[2];
            let signature_key = if let Some(pos) = signature_key_str.find('~') {
                if pos == 0 { // verifying key only
                    DatSignatureKey::from_bytes(signature_algorithm, &[], &*decode_base64_url(signature_key_str[1..].as_bytes())?)
                } else { // signing key ~ verifying key
                    DatSignatureKey::from_bytes(signature_algorithm, &*decode_base64_url(signature_key_str[..pos].as_bytes())?, &*decode_base64_url(signature_key_str[pos + 1..].as_bytes())?)
                }
            } else { // signing key only
                DatSignatureKey::from_bytes(signature_algorithm, &*decode_base64_url(signature_key_str)?, &[])
            }?;
            let crypto_algorithm = DatCryptoAlgorithm::from_str(parts[3])
                .map_err(|_| DatError::UnknownCryptoAlgorithm)?;
            let crypto_key = DatCryptoKey::from_bytes(crypto_algorithm, &*decode_base64_url(parts[4])?)?;
            let issue_begin = parts[5].parse::<u64>().map_err(|_| DatError::InvalidCertificateFormat)?;
            let issue_end = parts[6].parse::<u64>().map_err(|_| DatError::InvalidCertificateFormat)?;
            let dat_ttl = parts[7].parse::<u64>().map_err(|_| DatError::InvalidCertificateFormat)?;
            DatCertificate::from(cid, signature_key, crypto_key, issue_begin, issue_end, dat_ttl)
        } 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,
            cid_pre_copy: self.cid_pre_copy.clone(),
            signature_key: self.signature_key.clone(),
            crypto_key: self.crypto_key.clone(),
            dat_issue_begin: self.dat_issue_begin,
            dat_issue_end: self.dat_issue_end,
            dat_ttl: self.dat_ttl,
        }
    }
}
