//! SHA-256 Hash Chains for Audit Logging
//!
//! Provides tamper-evident hash chains for audit logs.
//! Each entry is cryptographically linked to the previous entry, making
//! it impossible to modify or delete entries without detection.

use sha2::{Sha256, Digest};
use serde::{Deserialize, Serialize};

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

/// A single entry in the hash chain
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HashChainEntry {
    /// Index in the chain (0-based)
    pub index: u64,
    /// Unix timestamp when entry was created
    pub timestamp: u64,
    /// The data being logged
    pub data: Vec<u8>,
    /// Hash of this entry (includes previous hash)
    pub hash: [u8; 32],
    /// Hash of the previous entry (zeros for genesis)
    pub prev_hash: [u8; 32],
}

impl HashChainEntry {
    /// Compute the hash for this entry
    fn compute_hash(
        index: u64,
        timestamp: u64,
        data: &[u8],
        prev_hash: &[u8; 32],
    ) -> [u8; 32] {
        let mut hasher = Sha256::new();
        hasher.update(index.to_le_bytes());
        hasher.update(timestamp.to_le_bytes());
        hasher.update((data.len() as u64).to_le_bytes());
        hasher.update(data);
        hasher.update(prev_hash);

        let result = hasher.finalize();
        let mut hash = [0u8; 32];
        hash.copy_from_slice(&result);
        hash
    }

    /// Verify the integrity of this entry
    pub fn verify(&self) -> bool {
        let expected = Self::compute_hash(
            self.index,
            self.timestamp,
            &self.data,
            &self.prev_hash,
        );
        self.hash == expected
    }

    /// Get the hash as a hex string
    pub fn hash_hex(&self) -> String {
        hex::encode(self.hash)
    }

    /// Get the previous hash as a hex string
    pub fn prev_hash_hex(&self) -> String {
        hex::encode(self.prev_hash)
    }
}

/// A tamper-evident hash chain for audit logging
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HashChain {
    /// All entries in the chain
    entries: Vec<HashChainEntry>,
    /// Optional chain identifier
    chain_id: Option<String>,
}

impl Default for HashChain {
    fn default() -> Self {
        Self::new()
    }
}

impl HashChain {
    /// Create a new empty hash chain
    pub fn new() -> Self {
        Self {
            entries: Vec::new(),
            chain_id: None,
        }
    }

    /// Create a new hash chain with an identifier
    pub fn with_id(chain_id: impl Into<String>) -> Self {
        Self {
            entries: Vec::new(),
            chain_id: Some(chain_id.into()),
        }
    }

    /// Get the chain identifier
    pub fn chain_id(&self) -> Option<&str> {
        self.chain_id.as_deref()
    }

    /// Get the number of entries in the chain
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Check if the chain is empty
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// Get the last entry in the chain
    pub fn last(&self) -> Option<&HashChainEntry> {
        self.entries.last()
    }

    /// Get an entry by index
    pub fn get(&self, index: usize) -> Option<&HashChainEntry> {
        self.entries.get(index)
    }

    /// Get all entries
    pub fn entries(&self) -> &[HashChainEntry] {
        &self.entries
    }

    /// Add a new entry to the chain
    ///
    /// # Arguments
    /// * `timestamp` - Unix timestamp for the entry
    /// * `data` - The data to log
    ///
    /// # Returns
    /// The newly created entry
    pub fn append(&mut self, timestamp: u64, data: Vec<u8>) -> &HashChainEntry {
        let index = self.entries.len() as u64;
        let prev_hash = self.entries.last()
            .map(|e| e.hash)
            .unwrap_or([0u8; 32]);

        let hash = HashChainEntry::compute_hash(index, timestamp, &data, &prev_hash);

        let entry = HashChainEntry {
            index,
            timestamp,
            data,
            hash,
            prev_hash,
        };

        self.entries.push(entry);
        self.entries.last().unwrap()
    }

    /// Append a string message to the chain
    pub fn append_message(&mut self, timestamp: u64, message: &str) -> &HashChainEntry {
        self.append(timestamp, message.as_bytes().to_vec())
    }

    /// Verify the entire chain
    ///
    /// Checks that:
    /// 1. Each entry's hash is valid
    /// 2. Each entry's prev_hash matches the previous entry's hash
    /// 3. Indices are sequential
    ///
    /// # Returns
    /// `Ok(())` if valid, `Err` with the index of the first invalid entry
    pub fn verify(&self) -> CryptoResult<()> {
        let mut prev_hash = [0u8; 32];

        for (i, entry) in self.entries.iter().enumerate() {
            // Check index
            if entry.index != i as u64 {
                return Err(CryptoError::HashChainVerificationFailed { index: i });
            }

            // Check prev_hash links to previous entry
            if entry.prev_hash != prev_hash {
                return Err(CryptoError::HashChainVerificationFailed { index: i });
            }

            // Verify the entry's hash
            if !entry.verify() {
                return Err(CryptoError::HashChainVerificationFailed { index: i });
            }

            prev_hash = entry.hash;
        }

        Ok(())
    }

    /// Verify a range of entries
    ///
    /// Useful for incremental verification of large chains.
    pub fn verify_range(&self, start: usize, end: usize) -> CryptoResult<()> {
        let end = end.min(self.entries.len());

        if start >= end {
            return Ok(());
        }

        // Get the expected prev_hash for the start entry
        let mut prev_hash = if start == 0 {
            [0u8; 32]
        } else {
            self.entries.get(start - 1)
                .map(|e| e.hash)
                .unwrap_or([0u8; 32])
        };

        for entry in &self.entries[start..end] {
            if entry.prev_hash != prev_hash {
                return Err(CryptoError::HashChainVerificationFailed {
                    index: entry.index as usize,
                });
            }

            if !entry.verify() {
                return Err(CryptoError::HashChainVerificationFailed {
                    index: entry.index as usize,
                });
            }

            prev_hash = entry.hash;
        }

        Ok(())
    }

    /// Get the current chain head hash
    pub fn head_hash(&self) -> [u8; 32] {
        self.entries.last()
            .map(|e| e.hash)
            .unwrap_or([0u8; 32])
    }

    /// Get the current chain head hash as hex
    pub fn head_hash_hex(&self) -> String {
        hex::encode(self.head_hash())
    }

    /// Export the chain to JSON
    pub fn to_json(&self) -> CryptoResult<String> {
        serde_json::to_string(self)
            .map_err(|e| CryptoError::SerializationFailed(e.to_string()))
    }

    /// Export the chain to pretty-printed JSON
    pub fn to_json_pretty(&self) -> CryptoResult<String> {
        serde_json::to_string_pretty(self)
            .map_err(|e| CryptoError::SerializationFailed(e.to_string()))
    }

    /// Import a chain from JSON
    pub fn from_json(json: &str) -> CryptoResult<Self> {
        serde_json::from_str(json)
            .map_err(|e| CryptoError::DeserializationFailed(e.to_string()))
    }

    /// Create an audit chain for a channel
    pub fn phantom_audit(channel: &str) -> Self {
        Self::with_id(format!("phantom-audit:{}", channel))
    }
}

/// Helper to create a hash chain with initial entries
pub fn create_audit_chain(
    channel: &str,
    initial_entries: &[(u64, &str)],
) -> HashChain {
    let mut chain = HashChain::phantom_audit(channel);
    for (timestamp, message) in initial_entries {
        chain.append_message(*timestamp, message);
    }
    chain
}

/// Compute SHA-256 hash of arbitrary data
pub fn sha256(data: &[u8]) -> [u8; 32] {
    let mut hasher = Sha256::new();
    hasher.update(data);
    let result = hasher.finalize();
    let mut hash = [0u8; 32];
    hash.copy_from_slice(&result);
    hash
}

/// Compute SHA-256 hash and return as hex string
pub fn sha256_hex(data: &[u8]) -> String {
    hex::encode(sha256(data))
}

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

    #[test]
    fn test_empty_chain() {
        let chain = HashChain::new();
        assert!(chain.is_empty());
        assert_eq!(chain.len(), 0);
        assert!(chain.verify().is_ok());
    }

    #[test]
    fn test_single_entry() {
        let mut chain = HashChain::new();
        chain.append(1000, b"First entry".to_vec());

        assert_eq!(chain.len(), 1);
        assert!(!chain.is_empty());

        let entry = chain.get(0).unwrap();
        assert_eq!(entry.index, 0);
        assert_eq!(entry.timestamp, 1000);
        assert_eq!(entry.data, b"First entry");
        assert_eq!(entry.prev_hash, [0u8; 32]);
        assert!(entry.verify());

        assert!(chain.verify().is_ok());
    }

    #[test]
    fn test_multiple_entries() {
        let mut chain = HashChain::new();
        chain.append(1000, b"First".to_vec());
        chain.append(2000, b"Second".to_vec());
        chain.append(3000, b"Third".to_vec());

        assert_eq!(chain.len(), 3);

        // Verify chain links
        let first = chain.get(0).unwrap();
        let second = chain.get(1).unwrap();
        let third = chain.get(2).unwrap();

        assert_eq!(second.prev_hash, first.hash);
        assert_eq!(third.prev_hash, second.hash);

        assert!(chain.verify().is_ok());
    }

    #[test]
    fn test_append_message() {
        let mut chain = HashChain::new();
        chain.append_message(1000, "Hello, World!");

        let entry = chain.get(0).unwrap();
        assert_eq!(entry.data, b"Hello, World!");
    }

    #[test]
    fn test_chain_with_id() {
        let chain = HashChain::with_id("test-chain");
        assert_eq!(chain.chain_id(), Some("test-chain"));
    }

    #[test]
    fn test_phantom_audit_chain() {
        let chain = HashChain::phantom_audit("TCOE");
        assert_eq!(chain.chain_id(), Some("phantom-audit:TCOE"));
    }

    #[test]
    fn test_tamper_detection_modify_data() {
        let mut chain = HashChain::new();
        chain.append(1000, b"Original data".to_vec());
        chain.append(2000, b"Second entry".to_vec());

        // Tamper with the first entry's data
        chain.entries[0].data = b"Tampered data".to_vec();

        // Verification should fail
        let result = chain.verify();
        assert!(matches!(result, Err(CryptoError::HashChainVerificationFailed { index: 0 })));
    }

    #[test]
    fn test_tamper_detection_modify_hash() {
        let mut chain = HashChain::new();
        chain.append(1000, b"First".to_vec());
        chain.append(2000, b"Second".to_vec());

        // Tamper with the first entry's hash
        chain.entries[0].hash[0] ^= 0xFF;

        // First entry itself is invalid
        assert!(!chain.entries[0].verify());

        // Chain verification should fail at index 0 (hash mismatch)
        // or at index 1 (prev_hash mismatch)
        let result = chain.verify();
        assert!(result.is_err());
    }

    #[test]
    fn test_tamper_detection_modify_prev_hash() {
        let mut chain = HashChain::new();
        chain.append(1000, b"First".to_vec());
        chain.append(2000, b"Second".to_vec());

        // Tamper with second entry's prev_hash
        chain.entries[1].prev_hash[0] ^= 0xFF;

        let result = chain.verify();
        assert!(matches!(result, Err(CryptoError::HashChainVerificationFailed { index: 1 })));
    }

    #[test]
    fn test_tamper_detection_insert_entry() {
        let mut chain = HashChain::new();
        chain.append(1000, b"First".to_vec());
        chain.append(3000, b"Third".to_vec());

        // Try to insert an entry in the middle
        let fake_entry = HashChainEntry {
            index: 1,
            timestamp: 2000,
            data: b"Inserted".to_vec(),
            hash: [0xAA; 32],
            prev_hash: chain.entries[0].hash,
        };
        chain.entries.insert(1, fake_entry);
        chain.entries[2].index = 2; // Fix index

        // Verification should fail because fake entry hash is wrong
        let result = chain.verify();
        assert!(result.is_err());
    }

    #[test]
    fn test_verify_range() {
        let mut chain = HashChain::new();
        for i in 0..10 {
            chain.append(i * 1000, format!("Entry {}", i).into_bytes());
        }

        // Verify full range
        assert!(chain.verify_range(0, 10).is_ok());

        // Verify partial ranges
        assert!(chain.verify_range(0, 5).is_ok());
        assert!(chain.verify_range(5, 10).is_ok());
        assert!(chain.verify_range(3, 7).is_ok());

        // Empty range is OK
        assert!(chain.verify_range(5, 5).is_ok());
        assert!(chain.verify_range(10, 5).is_ok());
    }

    #[test]
    fn test_head_hash() {
        let mut chain = HashChain::new();

        // Empty chain has zero head hash
        assert_eq!(chain.head_hash(), [0u8; 32]);

        chain.append(1000, b"First".to_vec());
        let first_hash = chain.head_hash();
        assert_ne!(first_hash, [0u8; 32]);

        chain.append(2000, b"Second".to_vec());
        let second_hash = chain.head_hash();
        assert_ne!(second_hash, first_hash);
    }

    #[test]
    fn test_json_serialization() {
        let mut chain = HashChain::with_id("test");
        chain.append(1000, b"Entry 1".to_vec());
        chain.append(2000, b"Entry 2".to_vec());

        let json = chain.to_json().unwrap();
        let restored = HashChain::from_json(&json).unwrap();

        assert_eq!(restored.len(), 2);
        assert_eq!(restored.chain_id(), Some("test"));
        assert!(restored.verify().is_ok());

        // Verify entries match
        for i in 0..chain.len() {
            assert_eq!(restored.get(i), chain.get(i));
        }
    }

    #[test]
    fn test_create_audit_chain() {
        let entries = [
            (1000, "Agent initialized"),
            (2000, "Key generated"),
            (3000, "Message encoded"),
        ];

        let chain = create_audit_chain("TCOE", &entries);

        assert_eq!(chain.len(), 3);
        assert!(chain.verify().is_ok());
    }

    #[test]
    fn test_sha256_function() {
        let hash = sha256(b"Hello, World!");
        assert_eq!(hash.len(), 32);

        // Known test vector
        let expected = hex::decode(
            "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"
        ).unwrap();
        assert_eq!(hash.as_slice(), expected.as_slice());
    }

    #[test]
    fn test_sha256_hex() {
        let hash_hex = sha256_hex(b"Hello, World!");
        assert_eq!(
            hash_hex,
            "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"
        );
    }

    #[test]
    fn test_entry_hash_hex() {
        let mut chain = HashChain::new();
        chain.append(1000, b"Test".to_vec());

        let entry = chain.get(0).unwrap();
        let hash_hex = entry.hash_hex();

        assert_eq!(hash_hex.len(), 64); // 32 bytes = 64 hex chars
        assert!(hash_hex.chars().all(|c| c.is_ascii_hexdigit()));
    }

    #[test]
    fn test_deterministic_hashing() {
        let mut chain1 = HashChain::new();
        chain1.append(1000, b"Data".to_vec());

        let mut chain2 = HashChain::new();
        chain2.append(1000, b"Data".to_vec());

        // Same inputs produce same hash
        assert_eq!(chain1.get(0).unwrap().hash, chain2.get(0).unwrap().hash);
    }
}
