use crate::error::DatError;
use aes_gcm::aead::array::Array;
use aes_gcm::aead::common::Generate;
use aes_gcm::aead::consts::{U12, U16, U32};
use aes_gcm::aes::cipher::InOutBuf;
use aes_gcm::aes::Aes128;
use aes_gcm::{AeadInOut, Aes128Gcm, AesGcm, Nonce};
use aes_gcm::{Aes256Gcm, Key, KeyInit};
use std::str::FromStr;
use strum_macros::Display;

const NONCE_LEN: usize = 12;

#[repr(u8)]
#[derive(Debug, Display, Clone, Copy, Eq, PartialEq)]
pub enum DatCryptoAlgorithm {
    AES128GCMN,
    AES256GCMN,
}

impl FromStr for DatCryptoAlgorithm {
    type Err = DatError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "AES128GCMN" => Ok(DatCryptoAlgorithm::AES128GCMN),
            "AES256GCMN" => Ok(DatCryptoAlgorithm::AES256GCMN),
            _ => Err(DatError::UnknownCryptoAlgorithm),
        }
    }
}


pub enum DatCryptoKey {
    AES128GCMN(Array<u8, U16>, AesGcm<aes_gcm::aes::Aes128, U12>),
    AES256GCMN(Array<u8, U32>, AesGcm<aes_gcm::aes::Aes256, U12>),
}

impl DatCryptoKey {
    pub fn algorithm(&self) -> DatCryptoAlgorithm {
        match self {
            DatCryptoKey::AES128GCMN(_, _) => DatCryptoAlgorithm::AES128GCMN,
            DatCryptoKey::AES256GCMN(_, _) => DatCryptoAlgorithm::AES256GCMN,
        }
    }
    pub fn from_bytes(algorithm: DatCryptoAlgorithm, data: &[u8]) -> Result<DatCryptoKey, DatError> {
        Ok(match algorithm {
            DatCryptoAlgorithm::AES128GCMN => {
                let bytes = Array::<u8, U16>::try_from(data)
                    .map_err(|_| DatError::InvalidCryptoKey)?;
                DatCryptoKey::AES128GCMN(bytes, Aes128Gcm::new(&bytes))
            },
            DatCryptoAlgorithm::AES256GCMN => {
                let bytes = Array::<u8, U32>::try_from(data)
                    .map_err(|_| DatError::InvalidCryptoKey)?;
                DatCryptoKey::AES256GCMN(bytes, Aes256Gcm::new(&bytes))
            },
        })
    }
    pub fn to_bytes(&self) -> Box<[u8]> {
        match self {
            DatCryptoKey::AES128GCMN(c, _) => Box::from(c.as_slice()),
            DatCryptoKey::AES256GCMN(c, _) => Box::from(c.as_slice()),
        }
    }
    pub fn generate(algorithm: DatCryptoAlgorithm) -> Self {
        match algorithm {
            DatCryptoAlgorithm::AES128GCMN => {
                let bytes = Key::<Aes128Gcm>::generate();
                DatCryptoKey::AES128GCMN(bytes, Aes128Gcm::new(&bytes))
            },
            DatCryptoAlgorithm::AES256GCMN => {
                let bytes = Key::<Aes256Gcm>::generate();
                DatCryptoKey::AES256GCMN(bytes, Aes256Gcm::new(&bytes))
            },
        }
    }

    pub fn encrypt(&self, body: &[u8]) -> Result<Vec<u8>, DatError> {
        if body.is_empty() {
            return Ok(vec![]);
        }
        let nonce: Array<u8, U12> = Nonce::generate();
        // 64 = nonce (12) + tag + space
        let mut enc_data = Vec::with_capacity(64 + body.len());
        enc_data.extend_from_slice(&nonce);
        enc_data.extend_from_slice(body);
        let inout = InOutBuf::from(&mut enc_data[NONCE_LEN..]);

        match self {
            DatCryptoKey::AES128GCMN(_, c) => {
                let tag = c.encrypt_inout_detached(&nonce, &[], inout)
                    .map_err(|_| DatError::EncryptError)?;
                enc_data.extend_from_slice(&tag.as_slice());
            },
            DatCryptoKey::AES256GCMN(_, c) => {
                let tag = c.encrypt_inout_detached(&nonce, &[], inout)
                    .map_err(|_| DatError::EncryptError)?;
                enc_data.extend_from_slice(&tag.as_slice());
            },
        };

        Ok(enc_data)
    }

    pub fn decrypt(&self, mut data: Vec<u8>) -> Result<Vec<u8>, DatError> {
        if data.is_empty() {
            return Ok(Vec::with_capacity(0));
        }
        if data.len() <= NONCE_LEN {
            return Err(DatError::DecryptError);
        }
        let mut secure = data.split_off(NONCE_LEN);
        let nonce: aes_gcm::aead::Nonce<AesGcm<Aes128, U12>> = data.as_slice().try_into().map_err(|_| DatError::DecryptError)?;

        match self {
            DatCryptoKey::AES128GCMN(_, c) => {
                c.decrypt_in_place(&nonce, &[], &mut secure).map_err(|_| DatError::DecryptError)
            },
            DatCryptoKey::AES256GCMN(_, c) => {
                c.decrypt_in_place(&nonce, &[], &mut secure).map_err(|_| DatError::DecryptError)
            },
        }?;
        Ok(secure)
    }
}

impl Clone for DatCryptoKey {
    fn clone(&self) -> Self {
        match self {
            DatCryptoKey::AES128GCMN(b, c) => DatCryptoKey::AES128GCMN(b.clone(), c.clone()),
            DatCryptoKey::AES256GCMN(b, c) => DatCryptoKey::AES256GCMN(b.clone(), c.clone()),
        }
    }
}
