//! Cryptographic utilities for contracts.
//!
//! Provides access to hash functions and signature verification.

use crate::host;
use crate::types::Hash256;

/// Computes the SHA3-256 hash of data.
///
/// # Example
///
/// ```rust,ignore
/// use synor_sdk::crypto::sha3_hash;
///
/// let hash = sha3_hash(b"hello world");
/// ```
pub fn sha3_hash(data: &[u8]) -> Hash256 {
    Hash256(host::sha3(data))
}

/// Computes the Blake3 hash of data.
///
/// Blake3 is faster than SHA3 and is used internally by Synor.
///
/// # Example
///
/// ```rust,ignore
/// use synor_sdk::crypto::blake3_hash;
///
/// let hash = blake3_hash(b"hello world");
/// ```
pub fn blake3_hash(data: &[u8]) -> Hash256 {
    Hash256(host::blake3(data))
}

/// Verifies an Ed25519 signature.
///
/// # Arguments
///
/// * `message` - The message that was signed
/// * `signature` - The 64-byte Ed25519 signature
/// * `public_key` - The 32-byte Ed25519 public key
///
/// # Returns
///
/// `true` if the signature is valid, `false` otherwise.
///
/// # Example
///
/// ```rust,ignore
/// use synor_sdk::crypto::verify_ed25519;
///
/// let valid = verify_ed25519(&message, &signature, &public_key);
/// ```
pub fn verify_ed25519(message: &[u8], signature: &[u8; 64], public_key: &[u8; 32]) -> bool {
    host::verify_ed25519(message, signature, public_key)
}

/// Computes a domain-separated hash.
///
/// This is useful for creating unique hashes for different purposes.
///
/// # Example
///
/// ```rust,ignore
/// use synor_sdk::crypto::domain_hash;
///
/// let hash = domain_hash(b"transfer", &[sender, receiver, amount]);
/// ```
pub fn domain_hash(domain: &[u8], data: &[&[u8]]) -> Hash256 {
    use alloc::vec::Vec;

    let mut combined = Vec::with_capacity(32 + data.iter().map(|d| d.len()).sum::<usize>());
    combined.extend_from_slice(&blake3_hash(domain).0);
    for d in data {
        combined.extend_from_slice(d);
    }
    blake3_hash(&combined)
}

/// Computes a hash chain (hash of hash of hash...).
pub fn hash_chain(data: &[u8], iterations: u32) -> Hash256 {
    let mut hash = blake3_hash(data);
    for _ in 1..iterations {
        hash = blake3_hash(&hash.0);
    }
    hash
}

/// Computes a commitment (hash with salt).
pub fn commit(data: &[u8], salt: &[u8; 32]) -> Hash256 {
    use alloc::vec::Vec;

    let mut combined = Vec::with_capacity(data.len() + 32);
    combined.extend_from_slice(data);
    combined.extend_from_slice(salt);
    blake3_hash(&combined)
}

/// Verifies a commitment.
pub fn verify_commit(data: &[u8], salt: &[u8; 32], commitment: &Hash256) -> bool {
    commit(data, salt) == *commitment
}

/// Creates a unique identifier from multiple inputs.
pub fn create_id(parts: &[&[u8]]) -> Hash256 {
    use alloc::vec::Vec;

    let total_len: usize = parts.iter().map(|p| p.len() + 4).sum();
    let mut combined = Vec::with_capacity(total_len);

    for part in parts {
        // Include length prefix for unambiguous parsing
        combined.extend_from_slice(&(part.len() as u32).to_le_bytes());
        combined.extend_from_slice(part);
    }

    blake3_hash(&combined)
}

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

    #[test]
    fn test_sha3_hash() {
        let hash1 = sha3_hash(b"hello");
        let hash2 = sha3_hash(b"hello");
        let hash3 = sha3_hash(b"world");

        assert_eq!(hash1, hash2);
        assert_ne!(hash1, hash3);
    }

    #[test]
    fn test_blake3_hash() {
        let hash = blake3_hash(b"test data");
        assert_ne!(hash, Hash256::zero());
    }

    #[test]
    fn test_domain_hash() {
        let hash1 = domain_hash(b"domain1", &[b"data"]);
        let hash2 = domain_hash(b"domain2", &[b"data"]);

        assert_ne!(hash1, hash2);
    }

    #[test]
    fn test_commit_verify() {
        let data = b"secret data";
        let salt = [0x42u8; 32];

        let commitment = commit(data, &salt);
        assert!(verify_commit(data, &salt, &commitment));
        assert!(!verify_commit(b"wrong data", &salt, &commitment));
    }

    #[test]
    fn test_create_id() {
        let id1 = create_id(&[b"a", b"bc"]);
        let id2 = create_id(&[b"ab", b"c"]);

        // Different because of length prefixes
        assert_ne!(id1, id2);
    }
}
