use std::error::Error;

use chacha20poly1305::{
    AeadCore, ChaCha20Poly1305, Nonce,
    aead::{Aead, OsRng},
};

pub(crate) enum Encryption {
    #[allow(unused)]
    Ccp(ChaCha20Poly1305),
}

impl super::PackUnpack for Encryption {
    fn pack(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
        match self {
            Encryption::Ccp(c) => {
                let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
                let mut result = nonce.to_vec();
                result.extend_from_slice(&c.encrypt(&nonce, data).map_err(|_| "Error encrypting")?);
                Ok(result)
            }
        }
    }

    fn unpack(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error>> {
        match self {
            Encryption::Ccp(c) => {
                if data.len() < 12 {
                    return Err("not enough data for the nonce".into());
                }
                let nonce_block: [u8; 12] = data[..12].try_into()?;
                let nonce: Nonce = nonce_block.into();
                Ok(c.decrypt(&nonce, &data[12..])
                    .map_err(|_| "Error decrypting")?)
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::super::PackUnpack;
    use super::*;
    use chacha20poly1305::KeyInit;
    use quickcheck::quickcheck;

    #[test]
    fn check_encryption_cc20p1305() {
        fn check_array(data: Vec<u8>) -> bool {
            let k = ChaCha20Poly1305::generate_key(&mut OsRng);
            let c = Encryption::Ccp(ChaCha20Poly1305::new(&k));
            let packed = c.pack(&data).unwrap();
            let unpacked = c.unpack(&packed).unwrap();
            data == unpacked
        }

        quickcheck(check_array as fn(Vec<u8>) -> bool);
    }
}
