use rand::rngs::{StdRng, SysRng};
use rand_core::{Rng as _, SeedableRng as _};

use crate::crypto::common::hmac_sha1;
use crate::crypto::utils::usage_ki;
use crate::crypto::{
    ChecksumSuite, Cipher, CipherSuite, DecryptWithoutChecksum, EncryptWithoutChecksum, KerberosCryptoResult,
};

use super::decrypt::{decrypt_message, decrypt_message_no_checksum};
use super::encrypt::{encrypt_message, encrypt_message_no_checksum};
use super::key_derivation::random_to_key;
use super::{DES3_BLOCK_SIZE, DES3_KEY_SIZE, DES3_MAC_SIZE, DES3_SEED_LEN, derive_key, derive_key_from_password};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Des3CbcSha1Kd;

impl Des3CbcSha1Kd {
    pub fn new() -> Self {
        Self
    }
}

impl Cipher for Des3CbcSha1Kd {
    fn key_size(&self) -> usize {
        DES3_KEY_SIZE
    }

    fn seed_bit_len(&self) -> usize {
        DES3_SEED_LEN * 8
    }

    fn random_to_key(&self, key: Vec<u8>) -> Vec<u8> {
        random_to_key(&key)
    }

    fn cipher_type(&self) -> CipherSuite {
        CipherSuite::Des3CbcSha1Kd
    }

    fn checksum_type(&self) -> ChecksumSuite {
        ChecksumSuite::HmacSha1Des3Kd
    }

    fn encrypt(&self, key: &[u8], key_usage: i32, payload: &[u8]) -> KerberosCryptoResult<Vec<u8>> {
        let mut cofounder = [0; DES3_BLOCK_SIZE];
        StdRng::try_from_rng(&mut SysRng)?.fill_bytes(&mut cofounder);
        encrypt_message(key, key_usage, payload, cofounder)
    }

    fn encrypt_no_checksum(
        &self,
        key: &[u8],
        key_usage: i32,
        payload: &[u8],
    ) -> KerberosCryptoResult<EncryptWithoutChecksum> {
        let mut cofounder = [0; DES3_BLOCK_SIZE];
        StdRng::try_from_rng(&mut SysRng)?.fill_bytes(&mut cofounder);
        encrypt_message_no_checksum(key, key_usage, payload, cofounder)
    }

    fn decrypt(&self, key: &[u8], key_usage: i32, cipher_data: &[u8]) -> KerberosCryptoResult<Vec<u8>> {
        decrypt_message(key, key_usage, cipher_data)
    }

    fn decrypt_no_checksum(
        &self,
        key: &[u8],
        key_usage: i32,
        cipher_data: &[u8],
    ) -> KerberosCryptoResult<DecryptWithoutChecksum> {
        decrypt_message_no_checksum(key, key_usage, cipher_data)
    }

    fn encryption_checksum(&self, key: &[u8], key_usage: i32, payload: &[u8]) -> KerberosCryptoResult<Vec<u8>> {
        let ki = derive_key(key, &usage_ki(key_usage))?;

        Ok(hmac_sha1(&ki, payload, DES3_MAC_SIZE))
    }

    fn generate_key_from_password(&self, password: &[u8], salt: &[u8]) -> KerberosCryptoResult<Vec<u8>> {
        derive_key_from_password(password, salt)
    }
}

#[cfg(test)]
mod tests {
    use crate::crypto::common::hmac_sha1;
    use crate::crypto::des::decrypt::{decrypt_message, decrypt_message_no_checksum};
    use crate::crypto::des::encrypt::{encrypt_message, encrypt_message_no_checksum};
    use crate::crypto::des::{DES3_BLOCK_SIZE, DES3_MAC_SIZE};

    #[test]
    fn encrypt() {
        let key = [
            115, 248, 21, 32, 230, 42, 157, 138, 158, 254, 157, 145, 13, 110, 64, 107, 173, 206, 247, 93, 55, 146, 167,
            138,
        ];
        let plaintext = [
            97, 101, 115, 50, 53, 54, 95, 99, 116, 115, 95, 104, 109, 97, 99, 95, 115, 104, 97, 49, 95, 57, 54,
        ];
        let confounder = [161, 52, 157, 33, 238, 232, 185, 93];

        let cipher_data = encrypt_message(&key, 5, &plaintext, confounder).unwrap();

        assert_eq!(
            &[
                126, 136, 43, 80, 62, 251, 57, 122, 225, 31, 122, 177, 228, 203, 192, 209, 209, 50, 207, 26, 25, 42,
                111, 102, 243, 28, 130, 32, 30, 129, 155, 136, 93, 10, 246, 56, 89, 215, 120, 254, 207, 136, 121, 74,
                156, 20, 56, 227, 234, 98, 203, 221
            ],
            cipher_data.as_slice()
        );
    }

    #[test]
    fn decrypt() {
        let key = [
            115, 248, 21, 32, 230, 42, 157, 138, 158, 254, 157, 145, 13, 110, 64, 107, 173, 206, 247, 93, 55, 146, 167,
            138,
        ];
        let payload = [
            126, 136, 43, 80, 62, 251, 57, 122, 225, 31, 122, 177, 228, 203, 192, 209, 209, 50, 207, 26, 25, 42, 111,
            102, 243, 28, 130, 32, 30, 129, 155, 136, 93, 10, 246, 56, 89, 215, 120, 254, 207, 136, 121, 74, 156, 20,
            56, 227, 234, 98, 203, 221,
        ];

        let plaintext = decrypt_message(&key, 5, &payload).unwrap();

        assert_eq!(
            &[
                97, 101, 115, 50, 53, 54, 95, 99, 116, 115, 95, 104, 109, 97, 99, 95, 115, 104, 97, 49, 95, 57, 54, 0
            ],
            plaintext.as_slice()
        );
    }

    #[test]
    fn encrypt_no_checksum() {
        let key = [
            115, 248, 21, 32, 230, 42, 157, 138, 158, 254, 157, 145, 13, 110, 64, 107, 173, 206, 247, 93, 55, 146, 167,
            138,
        ];
        let plaintext = [
            97, 101, 115, 50, 53, 54, 95, 99, 116, 115, 95, 104, 109, 97, 99, 95, 115, 104, 97, 49, 95, 57, 54,
        ];
        let confounder = [161, 52, 157, 33, 238, 232, 185, 93];

        let encryption_result = encrypt_message_no_checksum(&key, 5, &plaintext, confounder).unwrap();

        assert_eq!(confounder, encryption_result.confounder.as_slice());

        let expected_encrypted_data_with_checksum = &[
            126, 136, 43, 80, 62, 251, 57, 122, 225, 31, 122, 177, 228, 203, 192, 209, 209, 50, 207, 26, 25, 42, 111,
            102, 243, 28, 130, 32, 30, 129, 155, 136, 93, 10, 246, 56, 89, 215, 120, 254, 207, 136, 121, 74, 156, 20,
            56, 227, 234, 98, 203, 221,
        ];

        let mut conf_with_plaintext: Vec<u8> = encryption_result.confounder;
        conf_with_plaintext.extend_from_slice(&plaintext);
        let pad_len = (DES3_BLOCK_SIZE - (conf_with_plaintext.len() % DES3_BLOCK_SIZE)) % DES3_BLOCK_SIZE;
        conf_with_plaintext.resize(conf_with_plaintext.len() + pad_len, 0);

        assert_eq!(
            hmac_sha1(&encryption_result.ki, &conf_with_plaintext, DES3_MAC_SIZE),
            expected_encrypted_data_with_checksum[expected_encrypted_data_with_checksum.len() - DES3_MAC_SIZE..]
        );

        let mut cipher_data_with_checksum = encryption_result.encrypted;
        cipher_data_with_checksum.extend(hmac_sha1(&encryption_result.ki, &conf_with_plaintext, DES3_MAC_SIZE));

        assert_eq!(cipher_data_with_checksum, expected_encrypted_data_with_checksum);

        assert_eq!(
            cipher_data_with_checksum,
            encrypt_message(&key, 5, &plaintext, confounder).unwrap()
        );
    }

    #[test]
    fn decrypt_with_checksum() {
        let key = [
            115, 248, 21, 32, 230, 42, 157, 138, 158, 254, 157, 145, 13, 110, 64, 107, 173, 206, 247, 93, 55, 146, 167,
            138,
        ];

        let confounder = [161, 52, 157, 33, 238, 232, 185, 93];

        let encrypted_with_checksum = &[
            126, 136, 43, 80, 62, 251, 57, 122, 225, 31, 122, 177, 228, 203, 192, 209, 209, 50, 207, 26, 25, 42, 111,
            102, 243, 28, 130, 32, 30, 129, 155, 136, 93, 10, 246, 56, 89, 215, 120, 254, 207, 136, 121, 74, 156, 20,
            56, 227, 234, 98, 203, 221,
        ];

        let decryption_result = decrypt_message_no_checksum(&key, 5, encrypted_with_checksum).unwrap();

        assert_eq!(
            decrypt_message(&key, 5, encrypted_with_checksum).unwrap(),
            decryption_result.plaintext
        );

        assert_eq!(confounder, decryption_result.confounder.as_slice());

        let mut conf_with_padded_plaintext = decryption_result.confounder;
        conf_with_padded_plaintext.extend(decryption_result.plaintext);

        assert_eq!(
            hmac_sha1(&decryption_result.ki, &conf_with_padded_plaintext, DES3_MAC_SIZE),
            decryption_result.checksum
        );
    }
}
