use crate::crypt::{Cipher, CryptAlgorithm};
use crate::dat::DatPayload;
use crate::dat_key::Kid;
use crate::error::DatError;
use crate::sign::{SignAlgorithm, SignKey, VerifyKey};
use crate::util::{decode_base64_url_no_pad, encode_base64_url_no_pad_out, get_dat_dot_indices, now_unix_timestamp, to_kid, to_utf8};
use std::fmt::Write;

#[allow(dead_code)]
pub struct DatKeyActive<T: Kid> {
    pub(crate) kid: T,
    pub(crate) sign_alg: SignAlgorithm,
    pub(crate) sign_key: Option<SignKey>,
    pub(crate) crypt_alg: CryptAlgorithm,
    pub(crate) verify_key: VerifyKey,
    pub(crate) cipher: Cipher,
    pub(crate) token_ttl: i64,
}

impl <T: Kid> DatKeyActive<T> {
    pub fn to_payload(&self, dat: &'_ str) -> Result<DatPayload, DatError> {
        self.to_payload_with_indices(dat, get_dat_dot_indices(dat)?)
    }
    pub fn to_payload_with_indices(&self, dat: &'_ str, di: [usize; 4]) -> Result<DatPayload, DatError> {
        let exp = dat[0 .. di[0]]
            .parse::<i64>()
            .map_err(|_| DatError::InvalidDatFormat)?;

        if exp < now_unix_timestamp() {
            return Err(DatError::InvalidDatFormat)
        }

        let kid = to_kid(&dat[di[0] + 1 .. di[1]])?;
        let plain_base64 = &dat[di[1] + 1 .. di[2]];
        let secure_base64 = &dat[di[2] + 1 .. di[3]];
        let sign = &dat[di[3] + 1 ..];
        if sign.is_empty() {
            return Err(DatError::InvalidDatFormat);
        }
        let body = &dat[0 ..di[3]];

        if self.kid != kid {
            return Err(DatError::InvalidDatFormat);
        }
        if self.verify_key.verify(body.as_bytes(), &*decode_base64_url_no_pad(sign)?).is_err() {
            return Err(DatError::InvalidDatFormat)
        }
        self.decode_payload_base64(plain_base64, secure_base64)
    }
    pub(crate) fn decode_payload_base64(&self, plain_base64: &str, secure_base64: &str) -> Result<DatPayload, DatError> {
        let plain = to_utf8(decode_base64_url_no_pad(plain_base64)?)?;
        let secure = to_utf8(self.cipher.decrypt(decode_base64_url_no_pad(secure_base64)?.as_slice())?)?;
        Ok((plain, secure))
    }
    pub fn to_dat<U: AsRef<[u8]>>(&self, plain: U, secure: U) -> Result<String, DatError> {
        // (byte size + 2) * 4 / 3 = base 64 size
        // 100 is padding : expire + kid + (dot * 4) + (base64 pad 12)...
        let sign_ref = self.sign_key.as_ref().ok_or_else(|| DatError::UnSupportSignKeyIsVerifyOnly)?;
        let mut dat = String::with_capacity(100 + ((plain.as_ref().len() + secure.as_ref().len() + self.sign_alg.sign_size()) * 4 / 3));
        write!(dat, "{}.{}.", now_unix_timestamp() + self.token_ttl, self.kid).unwrap();
        encode_base64_url_no_pad_out(plain.as_ref(), &mut dat); // plain
        dat.push('.');
        encode_base64_url_no_pad_out(self.cipher.encrypt(secure.as_ref())?, &mut dat); // secure
        dat.push('.');
        encode_base64_url_no_pad_out(sign_ref.sign(dat[0..dat.len() - 1].as_bytes()), &mut dat); // sign
        Ok(dat)
    }
}

