use crate::crypt::{Cipher, CryptAlgorithm, CryptKey};
use crate::dat::DatPayload;
use crate::error::DatError;
use crate::sign::{SignAlgorithm, SignKey, VerifyKey};
use crate::util::{decode_base64_url_no_pad, encode_base64_url_no_pad, now_unix_timestamp, to_offset_date_time};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use time::OffsetDateTime;

pub type DatSplit<'a> = [&'a str; 5];

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DatKeySet {
    pub kid: String,
    pub sign_alg: String,
    pub sign_key: String,
    pub crypt_alg: String,
    pub crypt_key: String,
    pub issue_begin: i64,
    pub issue_end: i64,
    pub token_lifetime: i64,
}

pub struct DatKeySetActive {
    pub kid: String,
    pub sign_key: SignKey,
    pub verify_key: VerifyKey,
    pub cipher: Cipher,
    pub issue_begin: OffsetDateTime,
    pub issue_end: OffsetDateTime,
    pub token_lifetime: i64,
}

impl DatKeySet {
    pub fn generate(kid: String, sign_alg: SignAlgorithm, crypt_alg: CryptAlgorithm, issue_begin: i64, issue_end: i64, token_lifetime: i64) -> Result<Self, DatError> {
        Ok(DatKeySet {
            kid,
            sign_alg: sign_alg.to_str().to_string(),
            crypt_alg: crypt_alg.to_str().to_string(),
            sign_key: encode_base64_url_no_pad(SignKey::generate(sign_alg).to_bytes()),
            crypt_key: encode_base64_url_no_pad(CryptKey::generate(crypt_alg).to_bytes()),
            issue_begin,
            issue_end,
            token_lifetime,
        })
    }

    pub fn from_json(key_json: &str) -> Result<Self, DatError> {
        serde_json::from_str::<DatKeySet>(&key_json)
            .map_err(|e| DatError::KeyError(format!("from_json error: {e}")))
    }

    pub fn to_json(&self) -> Result<String, DatError> {
        serde_json::to_string(self)
            .map_err(|e| DatError::KeyError(format!("to_json error: {e}")))
    }

    pub fn into_active(self) -> Result<DatKeySetActive, DatError> {
        let sign_key = SignKey::from_bytes(SignAlgorithm::from_str(&self.sign_alg)?, &*decode_base64_url_no_pad(self.sign_key)?)?;
        let verify_key = sign_key.to_verify_key();
        let crypt_key = CryptKey::from_bytes(CryptAlgorithm::from_str(&self.crypt_alg)?, &*decode_base64_url_no_pad(self.crypt_key)?)?;
        Ok(DatKeySetActive {
            kid: self.kid,
            sign_key,
            verify_key,
            cipher: crypt_key.to_cipher(),
            issue_begin: to_offset_date_time(self.issue_begin)?,
            issue_end: to_offset_date_time(self.issue_end)?,
            token_lifetime: self.token_lifetime,
        })
    }
}

impl PartialEq for DatKeySet {
    fn eq(&self, other: &Self) -> bool {
        self.kid == other.kid
    }
}

impl DatKeySetActive {
    pub fn split(dat: &'_ str) -> Result<DatSplit<'_>, DatError> {
        let mut ptr = dat.split(".");

        let exp = ptr.next()
            .ok_or_else(|| DatError::DatError("format error".to_string()))?
            .parse::<i64>()
            .map_err(|_| DatError::DatError("format error".to_string()))?;

        if exp < now_unix_timestamp()? {
            return Err(DatError::DatError("expired".to_string()));
        }

        let kid = ptr.next().ok_or_else(|| DatError::DatError("format error".to_string()))?;
        let payload = ptr.next().ok_or_else(|| DatError::DatError("format error".to_string()))?;
        let secure_payload = ptr.next().ok_or_else(|| DatError::DatError("format error".to_string()))?;
        let signature = ptr.next().ok_or_else(|| DatError::DatError("format error".to_string()))?;
        let signature_pos = signature.as_ptr() as usize - dat.as_ptr() as usize;
        if ptr.next().is_some() {
            return Err(DatError::DatError("format error".to_string()));
        }
        let body = &dat[0 .. signature_pos];

        Ok([
            kid,
            payload,
            secure_payload,
            signature,
            body,
        ])
    }

    pub fn verify(&self, dat: String) -> Result<DatPayload, DatError> {
        self.verify_by_split_unsafe(Self::split(&dat)?)
    }

    pub fn verify_by_split_unsafe(&self, split: DatSplit) -> Result<DatPayload, DatError> {
        // kid[0], payload[1], secure_payload[2], signature[3], body[4]
        if self.kid != split[0] {
            return Err(DatError::DatError("kid is not equal".to_string()));
        }
        if self.verify_key.verify(split[4].as_bytes(), &*decode_base64_url_no_pad(split[3])?).is_err() {
            return Err(DatError::DatError("invalid sign".to_string()))
        }
        let plain = to_utf8(decode_base64_url_no_pad(split[1])?)?;
        let secure = to_utf8(self.cipher.decrypt(decode_base64_url_no_pad(split[2])?.as_slice())?)?;
        Ok((plain, secure))
    }

    pub fn to_dat(&self, payload: DatPayload) -> Result<String, DatError> {
        let expire = now_unix_timestamp()? + self.token_lifetime;
        let kid = &self.kid;

        let (plain, secure) = payload;
        let plain = encode_base64_url_no_pad(plain.as_bytes());
        let secure = encode_base64_url_no_pad(self.cipher.encrypt(secure.as_bytes())?);

        let mut body = String::with_capacity(100 + plain.len() + secure.len());
        write!(body, "{expire}.{kid}.{plain}.{secure}.")
            .map_err(|e| DatError::KeyError(format!("to_dat io error: {e}")))?;

        let sign = encode_base64_url_no_pad(&*self.sign_key.sign(body.as_bytes()));
        body.write_str(&sign)
            .map_err(|e| DatError::KeyError(format!("to_dat io error: {e}")))?;
        Ok(body)
    }
}

fn to_utf8(vec: Vec<u8>) -> Result<String, DatError> {
    String::from_utf8(vec).map_err(|e| DatError::KeyError(format!("to_string utf8 error: {e}")))
}
