//! HMAC-SHA256 message authentication
//!
//! Provides HMAC-SHA256 for:
//! - Message authentication codes (MACs)
//! - Sync token verification
//! - Integrity verification for audit logs

use hmac::{Hmac, Mac};
use sha2::Sha256;
use subtle::ConstantTimeEq;
use zeroize::Zeroizing;

use crate::error::{CryptoError, CryptoResult};

type HmacSha256 = Hmac<Sha256>;

/// Compute HMAC-SHA256 of a message
///
/// # Arguments
/// * `key` - The secret key (any length, but 32 bytes recommended)
/// * `message` - The message to authenticate
///
/// # Returns
/// 32-byte HMAC tag
///
/// # Example
/// ```
/// use pqc_py::keys::hmac::hmac_sha256;
///
/// let key = b"my-secret-key-32-bytes-long!!!!!";
/// let message = b"Hello, pqc-py!";
/// let tag = hmac_sha256(key, message);
/// assert_eq!(tag.len(), 32);
/// ```
pub fn hmac_sha256(key: &[u8], message: &[u8]) -> [u8; 32] {
    let mut mac = HmacSha256::new_from_slice(key)
        .expect("HMAC can accept keys of any length");
    mac.update(message);

    let result = mac.finalize();
    let bytes = result.into_bytes();

    let mut tag = [0u8; 32];
    tag.copy_from_slice(&bytes);
    tag
}

/// Verify an HMAC-SHA256 tag in constant time
///
/// # Arguments
/// * `key` - The secret key used to create the tag
/// * `message` - The message that was authenticated
/// * `tag` - The HMAC tag to verify
///
/// # Returns
/// `true` if the tag is valid, `false` otherwise
///
/// # Security
/// Uses constant-time comparison to prevent timing attacks.
///
/// # Example
/// ```
/// use pqc_py::keys::hmac::{hmac_sha256, verify_hmac};
///
/// let key = b"my-secret-key-32-bytes-long!!!!!";
/// let message = b"Hello, pqc-py!";
/// let tag = hmac_sha256(key, message);
///
/// assert!(verify_hmac(key, message, &tag));
/// assert!(!verify_hmac(key, b"Different message", &tag));
/// ```
pub fn verify_hmac(key: &[u8], message: &[u8], tag: &[u8]) -> bool {
    if tag.len() != 32 {
        return false;
    }

    let computed = hmac_sha256(key, message);
    computed.ct_eq(tag).into()
}

/// Verify an HMAC-SHA256 tag, returning a Result
///
/// # Arguments
/// * `key` - The secret key used to create the tag
/// * `message` - The message that was authenticated
/// * `tag` - The HMAC tag to verify
///
/// # Returns
/// `Ok(())` if valid, `Err(CryptoError::HmacVerificationFailed)` if invalid
pub fn verify_hmac_strict(key: &[u8], message: &[u8], tag: &[u8]) -> CryptoResult<()> {
    if verify_hmac(key, message, tag) {
        Ok(())
    } else {
        Err(CryptoError::HmacVerificationFailed)
    }
}

/// Compute HMAC-SHA256 and encode as hex string
///
/// Useful for logging and debugging purposes.
pub fn hmac_sha256_hex(key: &[u8], message: &[u8]) -> String {
    let tag = hmac_sha256(key, message);
    hex::encode(tag)
}

/// Create a truncated HMAC tag of specified length
///
/// # Arguments
/// * `key` - The secret key
/// * `message` - The message to authenticate
/// * `length` - Desired output length (max 32)
///
/// # Returns
/// Truncated HMAC tag, or error if length > 32
///
/// # Security Note
/// Truncating HMAC reduces security proportionally. Minimum 16 bytes recommended.
pub fn hmac_sha256_truncated(key: &[u8], message: &[u8], length: usize) -> CryptoResult<Vec<u8>> {
    if length > 32 {
        return Err(CryptoError::InvalidOutputLength(format!(
            "Requested {} bytes, maximum is 32",
            length
        )));
    }
    if length == 0 {
        return Err(CryptoError::InvalidOutputLength(
            "Output length must be greater than 0".to_string()
        ));
    }

    let full_tag = hmac_sha256(key, message);
    Ok(full_tag[..length].to_vec())
}

/// Compute HMAC with multiple message parts
///
/// Useful when the message is split across multiple buffers.
pub fn hmac_sha256_multi(key: &[u8], parts: &[&[u8]]) -> [u8; 32] {
    let mut mac = HmacSha256::new_from_slice(key)
        .expect("HMAC can accept keys of any length");

    for part in parts {
        mac.update(part);
    }

    let result = mac.finalize();
    let bytes = result.into_bytes();

    let mut tag = [0u8; 32];
    tag.copy_from_slice(&bytes);
    tag
}

/// Create a sync token for agent synchronization
///
/// # Arguments
/// * `key` - Shared key between agents
/// * `timestamp` - Unix timestamp
/// * `agent_id` - The agent creating the token
///
/// # Returns
/// 16-byte sync token
pub fn create_sync_token(key: &[u8], timestamp: u64, agent_id: &str) -> [u8; 16] {
    let message = format!("phantom-sync:{}:{}", timestamp, agent_id);
    let full_tag = hmac_sha256(key, message.as_bytes());

    let mut token = [0u8; 16];
    token.copy_from_slice(&full_tag[..16]);
    token
}

/// Verify a sync token
///
/// # Arguments
/// * `key` - Shared key between agents
/// * `token` - The token to verify
/// * `timestamp` - The claimed timestamp
/// * `agent_id` - The claimed agent ID
/// * `tolerance_secs` - Time tolerance in seconds (for clock drift)
///
/// # Returns
/// `true` if valid, `false` otherwise
pub fn verify_sync_token(
    key: &[u8],
    token: &[u8],
    timestamp: u64,
    agent_id: &str,
    tolerance_secs: u64,
) -> bool {
    if token.len() != 16 {
        return false;
    }

    // Check within tolerance window
    for offset in 0..=tolerance_secs {
        let expected_plus = create_sync_token(key, timestamp + offset, agent_id);
        if expected_plus.ct_eq(token).into() {
            return true;
        }

        if offset > 0 && timestamp >= offset {
            let expected_minus = create_sync_token(key, timestamp - offset, agent_id);
            if expected_minus.ct_eq(token).into() {
                return true;
            }
        }
    }

    false
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_hmac_basic() {
        let key = b"test-key-32-bytes-long-here!!!!";
        let message = b"Hello, World!";

        let tag1 = hmac_sha256(key, message);
        let tag2 = hmac_sha256(key, message);

        assert_eq!(tag1.len(), 32);
        assert_eq!(tag1, tag2); // Deterministic
    }

    #[test]
    fn test_hmac_different_keys() {
        let key1 = b"key-one-32-bytes-long-here!!!!!";
        let key2 = b"key-two-32-bytes-long-here!!!!!";
        let message = b"Same message";

        let tag1 = hmac_sha256(key1, message);
        let tag2 = hmac_sha256(key2, message);

        assert_ne!(tag1, tag2);
    }

    #[test]
    fn test_hmac_different_messages() {
        let key = b"same-key-32-bytes-long-here!!!!";

        let tag1 = hmac_sha256(key, b"Message 1");
        let tag2 = hmac_sha256(key, b"Message 2");

        assert_ne!(tag1, tag2);
    }

    #[test]
    fn test_verify_hmac_valid() {
        let key = b"test-key-32-bytes-long-here!!!!";
        let message = b"Test message";
        let tag = hmac_sha256(key, message);

        assert!(verify_hmac(key, message, &tag));
    }

    #[test]
    fn test_verify_hmac_invalid_tag() {
        let key = b"test-key-32-bytes-long-here!!!!";
        let message = b"Test message";
        let mut tag = hmac_sha256(key, message);

        tag[0] ^= 0xFF; // Corrupt one byte

        assert!(!verify_hmac(key, message, &tag));
    }

    #[test]
    fn test_verify_hmac_wrong_message() {
        let key = b"test-key-32-bytes-long-here!!!!";
        let tag = hmac_sha256(key, b"Original message");

        assert!(!verify_hmac(key, b"Different message", &tag));
    }

    #[test]
    fn test_verify_hmac_wrong_length() {
        let key = b"test-key-32-bytes-long-here!!!!";
        let message = b"Test";

        assert!(!verify_hmac(key, message, &[0u8; 16])); // Too short
        assert!(!verify_hmac(key, message, &[0u8; 64])); // Too long
    }

    #[test]
    fn test_verify_hmac_strict() {
        let key = b"test-key-32-bytes-long-here!!!!";
        let message = b"Test message";
        let tag = hmac_sha256(key, message);

        assert!(verify_hmac_strict(key, message, &tag).is_ok());

        let mut bad_tag = tag;
        bad_tag[0] ^= 0xFF;
        assert!(matches!(
            verify_hmac_strict(key, message, &bad_tag),
            Err(CryptoError::HmacVerificationFailed)
        ));
    }

    #[test]
    fn test_hmac_truncated() {
        let key = b"test-key";
        let message = b"test";

        let tag_16 = hmac_sha256_truncated(key, message, 16).unwrap();
        assert_eq!(tag_16.len(), 16);

        let tag_8 = hmac_sha256_truncated(key, message, 8).unwrap();
        assert_eq!(tag_8.len(), 8);

        // First bytes should match
        assert_eq!(&tag_16[..8], &tag_8[..]);
    }

    #[test]
    fn test_hmac_truncated_invalid_length() {
        let key = b"test-key";
        let message = b"test";

        assert!(hmac_sha256_truncated(key, message, 33).is_err());
        assert!(hmac_sha256_truncated(key, message, 0).is_err());
    }

    #[test]
    fn test_hmac_multi() {
        let key = b"test-key";

        // Single message
        let tag_single = hmac_sha256(key, b"Part1Part2Part3");

        // Multiple parts
        let tag_multi = hmac_sha256_multi(key, &[b"Part1", b"Part2", b"Part3"]);

        assert_eq!(tag_single, tag_multi);
    }

    #[test]
    fn test_sync_token() {
        let key = b"shared-agent-key-32-bytes!!!!!!";
        let timestamp = 1703980800u64;
        let agent_id = "agent-alpha";

        let token = create_sync_token(key, timestamp, agent_id);
        assert_eq!(token.len(), 16);

        // Same inputs produce same token
        let token2 = create_sync_token(key, timestamp, agent_id);
        assert_eq!(token, token2);
    }

    #[test]
    fn test_sync_token_verification() {
        let key = b"shared-agent-key-32-bytes!!!!!!";
        let timestamp = 1703980800u64;
        let agent_id = "agent-alpha";

        let token = create_sync_token(key, timestamp, agent_id);

        // Exact match
        assert!(verify_sync_token(key, &token, timestamp, agent_id, 0));

        // With tolerance
        assert!(verify_sync_token(key, &token, timestamp, agent_id, 5));
    }

    #[test]
    fn test_sync_token_tolerance() {
        let key = b"shared-agent-key-32-bytes!!!!!!";
        let timestamp = 1703980800u64;
        let agent_id = "agent-alpha";

        let token = create_sync_token(key, timestamp, agent_id);

        // Token created at timestamp should verify within tolerance
        assert!(verify_sync_token(key, &token, timestamp + 3, agent_id, 5));
        assert!(verify_sync_token(key, &token, timestamp - 3, agent_id, 5));

        // But not outside tolerance
        assert!(!verify_sync_token(key, &token, timestamp + 10, agent_id, 5));
    }

    #[test]
    fn test_sync_token_wrong_agent() {
        let key = b"shared-agent-key-32-bytes!!!!!!";
        let timestamp = 1703980800u64;

        let token = create_sync_token(key, timestamp, "agent-alpha");

        assert!(!verify_sync_token(key, &token, timestamp, "agent-beta", 0));
    }

    // RFC 2104 Test Vector 1
    #[test]
    fn test_hmac_rfc2104_vector1() {
        let key = vec![0x0bu8; 20];
        let data = b"Hi There";

        // Note: This is for HMAC-SHA256, not HMAC-MD5 from RFC 2104
        // We just verify the output is deterministic and correct length
        let tag = hmac_sha256(&key, data);
        assert_eq!(tag.len(), 32);
    }

    // HMAC-SHA256 test vector
    #[test]
    fn test_hmac_sha256_vector() {
        // Test vector from various sources
        let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
        let data = b"Hi There";

        let expected = hex::decode(
            "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
        ).unwrap();

        let tag = hmac_sha256(&key, data);
        assert_eq!(tag.as_slice(), expected.as_slice());
    }
}
