#[cfg(test)]
mod password_hashers {
    use std::path::Path;
    use cas_lib::{
        password_hashers::{argon2::CASArgon, bcrypt::CASBCrypt, pbkdf2, scrypt::CASScrypt},
        symmetric::{
            aes::{CASAES128, CASAES256},
            cas_symmetric_encryption::{CASAES128Encryption, CASAES256Encryption},
        },
    };
    
    #[test]
    pub fn argon2_hash_with_parameters() {
        let password = "BadPassword".to_string();
        let hash = CASArgon::hash_password_parameters(1024, 5, 5, password.clone()).unwrap();
        let verification = CASArgon::verify_password(hash, password).unwrap();
        assert_eq!(true, verification);
    }

    #[test]
    pub fn argon2_derive_aes_128_and_encrypt() {
        let password = b"BadPassword".to_vec(); // do not use this as a password.
        let key = CASArgon::derive_aes_128_key(password).unwrap();
        let nonce = CASAES128::generate_nonce();
        let path = Path::new("tests/test.docx");
        let file_bytes: Vec<u8> = std::fs::read(path).unwrap();

        let encrypted = CASAES128::encrypt_plaintext(key.clone(), nonce.clone(), file_bytes.clone()).unwrap();
        let decrypted = CASAES128::decrypt_ciphertext(key, nonce, encrypted).unwrap();
        assert_eq!(file_bytes, decrypted);
    }

    #[test]
    pub fn argon2_derive_aes_256_and_encrypt() {
        let password = b"BadPassword".to_vec(); // do not use this as a password.
        let key = CASArgon::derive_aes_256_key(password).unwrap();
        let nonce = CASAES128::generate_nonce();
        let path = Path::new("tests/test.docx");
        let file_bytes: Vec<u8> = std::fs::read(path).unwrap();

        let encrypted = CASAES256::encrypt_plaintext(key.clone(), nonce.clone(), file_bytes.clone()).unwrap();
        let decrypted = CASAES256::decrypt_ciphertext(key, nonce, encrypted).unwrap();
        assert_eq!(file_bytes, decrypted);
    }

    #[test]
    pub fn argon2_hash_password() {
        let password = "BadPassword".to_string();
        let hash = CASArgon::hash_password(password.clone()).unwrap();
        let verification = CASArgon::verify_password(hash, password).unwrap();
        assert_eq!(true, verification);
    }

    #[test]
    pub fn scrypt_hash_password() {
        let password = "DoNotUseThisPassword".to_string();
        let hash = CASScrypt::hash_password(password.clone()).unwrap();
        let verification = CASScrypt::verify_password(hash, password).unwrap();
        assert_eq!(true, verification);
    }

    #[test]
    pub fn scrypt_hash_password_customized() {
        let password = "DoNotUseThisPassword".to_string();
        let hash = CASScrypt::hash_password_customized(password.clone(), 17, 8, 1).unwrap();
        let verification = CASScrypt::verify_password(hash, password).unwrap();
        assert_eq!(true, verification);
    }

    #[test]
    pub fn bcrypt_hash_password() {
        let password = "DoNotUseThisPassword".to_string();
        let hash = CASBCrypt::hash_password(password.clone()).unwrap();
        let verification = CASBCrypt::verify_password(hash, password).unwrap();
        assert_eq!(true, verification);
    }

    #[test]
    pub fn bcrypt_hash_password_customized() {
        let password = "DoNotUseThisPassword".to_string();
        let hash = CASBCrypt::hash_password_customized(password.clone(), 12).unwrap();
        let verification = CASBCrypt::verify_password(hash, password).unwrap();
        assert_eq!(true, verification);
    }

    fn hex_to_bytes(hex: &str) -> Vec<u8> {
        let hex = hex.trim();
        assert_eq!(hex.len() % 2, 0, "hex input must have an even number of characters");

        let mut out = Vec::with_capacity(hex.len() / 2);
        for i in (0..hex.len()).step_by(2) {
            let byte = u8::from_str_radix(&hex[i..i + 2], 16).expect("invalid hex digit");
            out.push(byte);
        }

        out
    }

    macro_rules! pbkdf2_vectors {
        ($($password:expr, $salt:expr, $rounds:expr, $expected_hex:expr;)*) => {
            $(
                let derived = pbkdf2::derivation_with_salt($password.to_vec(), $rounds, $salt.to_vec());
                assert_eq!(
                    derived,
                    hex_to_bytes($expected_hex),
                    "PBKDF2-SHA3 mismatch for password {:?}, salt {:?}, iterations {}",
                    $password,
                    $salt,
                    $rounds
                );
            )*
        };
    }

    // Borrowed from RustCrypto's pbkdf2 crate tests, which in turn are based on the test vectors from RFC 6070.
    // https://www.rfc-editor.org/rfc/rfc6070
    #[test]
    pub fn pbkdf2_sha3_rfc6070_style_vectors() {
        pbkdf2_vectors!(
            b"password", b"salt", 1, "94613f3ee2ea730e0b06754f3fc816d4f87c9be9cbd8556b5d59b52330e333a8";
            b"password", b"salt", 2, "4c915baedd1773383e77fcfe38114ca7514010adec24b47290ec170208423f76";
            b"password", b"salt", 4096, "778b6e237a0f49621549ff70d218d2080756b9fb38d71b5d7ef447fa2254af61";
            b"passwordPASSWORDpassword", b"saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096,
                "7aef8f1ad8c7f12205334f624d4af9e2863121618f7a0b3209bef3934801c39f";
            b"pass\0word", b"sa\0lt", 4096, "98e5503130ffdd69603da78cbb12e9becb948efa1445a639569cc1b042e643fd";
        );
    }

    #[test]
    pub fn pbkdf2_derivation_round_trip_reuses_salt() {
        let password = b"BadPassword".to_vec();
        let rounds = 8_192;
        let result = pbkdf2::derivation(password.clone(), rounds);

        assert_eq!(result.password.len(), 32);
        assert!(!result.salt.is_empty());

        let recomputed = pbkdf2::derivation_with_salt(password, rounds, result.salt.clone());
        assert_eq!(recomputed, result.password);
    }
}
