//! kHeavyHash Proof of Work algorithm.
//!
//! The kHeavyHash algorithm is designed to be ASIC-resistant by combining:
//! - SHA3-256 for cryptographic hashing
//! - Matrix multiplication in GF(2^8) for memory hardness
//! - Blake3 for fast nonce mixing
//!
//! Algorithm steps:
//! 1. pre_hash = SHA3-256(header_without_nonce)
//! 2. nonce_hash = Blake3(pre_hash || nonce)
//! 3. matrix_out = HeavyMatrix × pre_hash (in GF(2^8))
//! 4. mixed = matrix_out XOR nonce_hash
//! 5. pow_hash = SHA3-256(mixed)

use sha3::{Digest, Sha3_256};
use synor_types::Hash256;

use crate::matrix::OptimizedMatrix;
use crate::Target;

/// The kHeavyHash algorithm implementation.
pub struct KHeavyHash {
    /// The optimized matrix for fast multiplication.
    matrix: OptimizedMatrix,
}

impl KHeavyHash {
    /// Creates a new kHeavyHash instance.
    pub fn new() -> Self {
        KHeavyHash {
            matrix: OptimizedMatrix::new(),
        }
    }

    /// Creates with a custom matrix seed.
    pub fn with_seed(seed: &[u8; 32]) -> Self {
        KHeavyHash {
            matrix: OptimizedMatrix::from_seed(seed),
        }
    }

    /// Computes the full kHeavyHash PoW.
    ///
    /// # Arguments
    /// * `header` - Block header bytes (without nonce)
    /// * `nonce` - The nonce to try
    ///
    /// # Returns
    /// The PoW hash that must be compared against target.
    pub fn hash(&self, header: &[u8], nonce: u64) -> PowHash {
        // Step 1: Pre-hash the header
        let pre_hash = self.pre_hash(header);

        // Step 2-5: Compute final PoW hash with nonce
        self.finalize(&pre_hash, nonce)
    }

    /// Pre-hashes the block header (can be cached).
    ///
    /// This is SHA3-256 of the header without the nonce.
    pub fn pre_hash(&self, header: &[u8]) -> Hash256 {
        let mut hasher = Sha3_256::new();
        hasher.update(header);
        let result: [u8; 32] = hasher.finalize().into();
        Hash256::from_bytes(result)
    }

    /// Finalizes the PoW hash given a pre-hash and nonce.
    ///
    /// This allows mining to cache the pre-hash and only vary the nonce.
    pub fn finalize(&self, pre_hash: &Hash256, nonce: u64) -> PowHash {
        // Step 2: Hash nonce with pre_hash using Blake3
        let nonce_bytes = nonce.to_le_bytes();
        let mut nonce_input = [0u8; 40]; // 32 + 8
        nonce_input[..32].copy_from_slice(pre_hash.as_bytes());
        nonce_input[32..].copy_from_slice(&nonce_bytes);
        let nonce_hash: [u8; 32] = blake3::hash(&nonce_input).into();

        // Step 3: Matrix multiplication
        let matrix_out = self.matrix.multiply(pre_hash.as_bytes());

        // Step 4: XOR matrix output with nonce hash
        let mut mixed = [0u8; 32];
        for i in 0..32 {
            mixed[i] = matrix_out[i] ^ nonce_hash[i];
        }

        // Step 5: Final SHA3-256
        let mut hasher = Sha3_256::new();
        hasher.update(mixed);
        let final_hash: [u8; 32] = hasher.finalize().into();

        PowHash {
            hash: Hash256::from_bytes(final_hash),
            nonce,
            pre_hash: *pre_hash,
        }
    }

    /// Verifies a PoW solution.
    pub fn verify(&self, header: &[u8], nonce: u64, target: &Target) -> bool {
        let pow = self.hash(header, nonce);
        target.is_met_by(&pow.hash)
    }

    /// Mines a block by trying nonces until target is met.
    ///
    /// # Arguments
    /// * `header` - Block header bytes
    /// * `target` - Difficulty target
    /// * `start_nonce` - Starting nonce
    /// * `max_tries` - Maximum nonces to try (0 = unlimited)
    ///
    /// # Returns
    /// The solving nonce and PoW hash, or None if not found.
    pub fn mine(
        &self,
        header: &[u8],
        target: &Target,
        start_nonce: u64,
        max_tries: u64,
    ) -> Option<PowHash> {
        let pre_hash = self.pre_hash(header);
        let limit = if max_tries == 0 {
            u64::MAX
        } else {
            start_nonce.saturating_add(max_tries)
        };

        for nonce in start_nonce..limit {
            let pow = self.finalize(&pre_hash, nonce);
            if target.is_met_by(&pow.hash) {
                return Some(pow);
            }
        }

        None
    }

    /// Mines with a callback for progress reporting.
    pub fn mine_with_callback<F>(
        &self,
        header: &[u8],
        target: &Target,
        start_nonce: u64,
        max_tries: u64,
        mut callback: F,
    ) -> Option<PowHash>
    where
        F: FnMut(u64, u64) -> bool, // (nonces_tried, current_nonce) -> continue?
    {
        let pre_hash = self.pre_hash(header);
        let limit = if max_tries == 0 {
            u64::MAX
        } else {
            start_nonce.saturating_add(max_tries)
        };

        let mut tried = 0u64;
        for nonce in start_nonce..limit {
            let pow = self.finalize(&pre_hash, nonce);
            tried += 1;

            if target.is_met_by(&pow.hash) {
                return Some(pow);
            }

            // Report progress every 10000 hashes
            if tried % 10000 == 0 && !callback(tried, nonce) {
                return None; // Cancelled
            }
        }

        None
    }

    /// Returns the matrix used by this hasher.
    pub fn matrix(&self) -> &OptimizedMatrix {
        &self.matrix
    }
}

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

impl Clone for KHeavyHash {
    fn clone(&self) -> Self {
        // Matrix is large, but we need to clone for multi-threading
        KHeavyHash {
            matrix: OptimizedMatrix::new(), // Recreate from default seed
        }
    }
}

/// Result of a PoW computation.
#[derive(Clone, Debug)]
pub struct PowHash {
    /// The final PoW hash.
    pub hash: Hash256,
    /// The nonce that produced this hash.
    pub nonce: u64,
    /// The pre-hash (for verification).
    pub pre_hash: Hash256,
}

impl PowHash {
    /// Checks if this PoW meets a target.
    pub fn meets_target(&self, target: &Target) -> bool {
        target.is_met_by(&self.hash)
    }

    /// Returns the hash as bytes.
    pub fn as_bytes(&self) -> &[u8; 32] {
        self.hash.as_bytes()
    }
}

/// Parallel mining using multiple threads.
pub struct ParallelMiner {
    /// kHeavyHash instances per thread.
    hashers: Vec<KHeavyHash>,
    /// Number of threads.
    num_threads: usize,
}

impl ParallelMiner {
    /// Creates a parallel miner with the specified number of threads.
    pub fn new(num_threads: usize) -> Self {
        let threads = if num_threads == 0 {
            num_cpus()
        } else {
            num_threads
        };

        let hashers = (0..threads).map(|_| KHeavyHash::new()).collect();

        ParallelMiner {
            hashers,
            num_threads: threads,
        }
    }

    /// Returns the number of threads.
    pub fn num_threads(&self) -> usize {
        self.num_threads
    }

    /// Mines using all threads.
    ///
    /// Each thread gets a different nonce range to search.
    pub fn mine(&self, header: &[u8], target: &Target, start_nonce: u64) -> Option<PowHash> {
        use std::sync::atomic::{AtomicBool, Ordering};
        use std::sync::Arc;

        let found = Arc::new(AtomicBool::new(false));
        let result = Arc::new(parking_lot::Mutex::new(None));

        let nonce_step = u64::MAX / self.num_threads as u64;

        std::thread::scope(|s| {
            for (i, hasher) in self.hashers.iter().enumerate() {
                let thread_start = start_nonce.wrapping_add(i as u64 * nonce_step);
                let found = Arc::clone(&found);
                let result = Arc::clone(&result);
                let header = header.to_vec();
                let target = *target;

                s.spawn(move || {
                    let pre_hash = hasher.pre_hash(&header);
                    let mut nonce = thread_start;

                    loop {
                        // Check if another thread found it
                        if found.load(Ordering::Relaxed) {
                            return;
                        }

                        let pow = hasher.finalize(&pre_hash, nonce);

                        if target.is_met_by(&pow.hash) {
                            found.store(true, Ordering::Relaxed);
                            *result.lock() = Some(pow);
                            return;
                        }

                        nonce = nonce.wrapping_add(1);

                        // Don't wrap into another thread's range
                        if nonce == thread_start.wrapping_add(nonce_step) {
                            return;
                        }
                    }
                });
            }
        });

        Arc::try_unwrap(result).ok()?.into_inner()
    }
}

/// Returns the number of CPU cores.
fn num_cpus() -> usize {
    std::thread::available_parallelism()
        .map(|n| n.get())
        .unwrap_or(1)
}

/// Benchmark helper for hashrate measurement.
pub struct HashrateBenchmark {
    hasher: KHeavyHash,
}

impl HashrateBenchmark {
    /// Creates a new benchmark.
    pub fn new() -> Self {
        HashrateBenchmark {
            hasher: KHeavyHash::new(),
        }
    }

    /// Runs a benchmark for the specified duration.
    ///
    /// Returns hashes per second.
    pub fn run(&self, duration_ms: u64) -> f64 {
        let header = [0x42u8; 80]; // Dummy header
        let pre_hash = self.hasher.pre_hash(&header);

        let start = std::time::Instant::now();
        let mut count = 0u64;
        let mut nonce = 0u64;

        while start.elapsed().as_millis() < duration_ms as u128 {
            let _ = self.hasher.finalize(&pre_hash, nonce);
            nonce = nonce.wrapping_add(1);
            count += 1;
        }

        let elapsed = start.elapsed().as_secs_f64();
        count as f64 / elapsed
    }
}

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

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

    #[test]
    fn test_kheavyhash_deterministic() {
        let hasher = KHeavyHash::new();
        let header = b"test block header data";
        let nonce = 12345u64;
        let hash1 = hasher.hash(header, nonce);
        let hash2 = hasher.hash(header, nonce);
        assert_eq!(hash1.hash, hash2.hash);
        assert_eq!(hash1.nonce, hash2.nonce);
    }

    #[test]
    fn test_kheavyhash_deterministic_different_instances() {
        let hasher1 = KHeavyHash::new();
        let hasher2 = KHeavyHash::new();
        let header = b"test block header data";
        let nonce = 12345u64;
        let hash1 = hasher1.hash(header, nonce);
        let hash2 = hasher2.hash(header, nonce);
        assert_eq!(hash1.hash, hash2.hash);
    }

    #[test]
    fn test_kheavyhash_different_nonces() {
        let hasher = KHeavyHash::new();
        let header = b"test block header data";
        let hash1 = hasher.hash(header, 1);
        let hash2 = hasher.hash(header, 2);
        assert_ne!(hash1.hash, hash2.hash);
    }

    #[test]
    fn test_kheavyhash_different_nonces_sequential() {
        let hasher = KHeavyHash::new();
        let header = b"sequential nonce test";
        let mut hashes = Vec::new();
        for nonce in 0..10 {
            let hash = hasher.hash(header, nonce);
            hashes.push(hash.hash);
        }
        for i in 0..hashes.len() {
            for j in (i + 1)..hashes.len() {
                assert_ne!(hashes[i], hashes[j]);
            }
        }
    }

    #[test]
    fn test_kheavyhash_different_headers_same_nonce() {
        let hasher = KHeavyHash::new();
        let nonce = 12345u64;
        let hash1 = hasher.hash(b"header one", nonce);
        let hash2 = hasher.hash(b"header two", nonce);
        assert_ne!(hash1.hash, hash2.hash);
    }

    #[test]
    fn test_kheavyhash_different_headers_produce_different_hashes() {
        let hasher = KHeavyHash::new();
        let nonce = 0;
        let headers = [
            b"header A".as_slice(),
            b"header B".as_slice(),
            b"header C".as_slice(),
            b"longer header".as_slice(),
        ];
        let mut hashes = Vec::new();
        for header in &headers {
            let hash = hasher.hash(header, nonce);
            hashes.push(hash.hash);
        }
        for i in 0..hashes.len() {
            for j in (i + 1)..hashes.len() {
                assert_ne!(hashes[i], hashes[j]);
            }
        }
    }

    #[test]
    fn test_kheavyhash_empty_header() {
        let hasher = KHeavyHash::new();
        let hash = hasher.hash(b"", 0);
        assert!(hash.hash.as_bytes().len() == 32);
    }

    #[test]
    fn test_kheavyhash_large_header() {
        let hasher = KHeavyHash::new();
        let large_header = vec![0x42u8; 1024];
        let hash = hasher.hash(&large_header, 0);
        assert!(hash.hash.as_bytes().len() == 32);
    }

    #[test]
    fn test_pre_hash_caching() {
        let hasher = KHeavyHash::new();
        let header = b"test block header data";
        let pre_hash = hasher.pre_hash(header);
        let pow1 = hasher.finalize(&pre_hash, 1);
        let pow2 = hasher.finalize(&pre_hash, 2);
        assert_eq!(pow1.pre_hash, pow2.pre_hash);
        assert_ne!(pow1.hash, pow2.hash);
    }

    #[test]
    fn test_pre_hash_deterministic() {
        let hasher = KHeavyHash::new();
        let header = b"deterministic pre-hash test";
        let pre_hash1 = hasher.pre_hash(header);
        let pre_hash2 = hasher.pre_hash(header);
        assert_eq!(pre_hash1, pre_hash2);
    }

    #[test]
    fn test_pre_hash_different_headers() {
        let hasher = KHeavyHash::new();
        let pre_hash1 = hasher.pre_hash(b"header one");
        let pre_hash2 = hasher.pre_hash(b"header two");
        assert_ne!(pre_hash1, pre_hash2);
    }

    #[test]
    fn test_finalize_same_pre_hash_different_nonces() {
        let hasher = KHeavyHash::new();
        let pre_hash = hasher.pre_hash(b"test header");
        let pow1 = hasher.finalize(&pre_hash, 100);
        let pow2 = hasher.finalize(&pre_hash, 200);
        assert_eq!(pow1.pre_hash, pow2.pre_hash);
        assert_ne!(pow1.hash, pow2.hash);
        assert_eq!(pow1.nonce, 100);
        assert_eq!(pow2.nonce, 200);
    }

    #[test]
    fn test_finalize_consistency_with_hash() {
        let hasher = KHeavyHash::new();
        let header = b"consistency test";
        let nonce = 42u64;
        let direct = hasher.hash(header, nonce);
        let pre_hash = hasher.pre_hash(header);
        let indirect = hasher.finalize(&pre_hash, nonce);
        assert_eq!(direct.hash, indirect.hash);
        assert_eq!(direct.nonce, indirect.nonce);
        assert_eq!(direct.pre_hash, indirect.pre_hash);
    }

    #[test]
    fn test_verify_valid_solution() {
        let hasher = KHeavyHash::new();
        let header = b"verify test";
        let target = Target::max();
        let pow = hasher.mine(header, &target, 0, 10000).unwrap();
        assert!(hasher.verify(header, pow.nonce, &target));
    }

    #[test]
    fn test_verify_returns_correct_bool() {
        let hasher = KHeavyHash::new();
        let header = b"verify bool test";
        let target = Target::max();
        let pow = hasher.mine(header, &target, 0, 10000).unwrap();
        let result = hasher.verify(header, pow.nonce, &target);
        assert!(result);
    }

    #[test]
    fn test_with_seed_creates_different_hasher() {
        let seed1 = [0x01u8; 32];
        let seed2 = [0x02u8; 32];
        let hasher1 = KHeavyHash::with_seed(&seed1);
        let hasher2 = KHeavyHash::with_seed(&seed2);
        let header = b"seed test";
        let nonce = 0u64;
        let hash1 = hasher1.hash(header, nonce);
        let hash2 = hasher2.hash(header, nonce);
        assert_ne!(hash1.hash, hash2.hash);
    }

    #[test]
    fn test_with_seed_deterministic() {
        let seed = [0x42u8; 32];
        let hasher1 = KHeavyHash::with_seed(&seed);
        let hasher2 = KHeavyHash::with_seed(&seed);
        let header = b"seed deterministic test";
        let nonce = 0u64;
        let hash1 = hasher1.hash(header, nonce);
        let hash2 = hasher2.hash(header, nonce);
        assert_eq!(hash1.hash, hash2.hash);
    }

    #[test]
    fn test_kheavyhash_default_trait() {
        let hasher1 = KHeavyHash::default();
        let hasher2 = KHeavyHash::new();
        let header = b"default trait test";
        let hash1 = hasher1.hash(header, 0);
        let hash2 = hasher2.hash(header, 0);
        assert_eq!(hash1.hash, hash2.hash);
    }

    #[test]
    fn test_kheavyhash_clone() {
        let hasher1 = KHeavyHash::new();
        let hasher2 = hasher1.clone();
        let header = b"clone test";
        let hash1 = hasher1.hash(header, 0);
        let hash2 = hasher2.hash(header, 0);
        assert_eq!(hash1.hash, hash2.hash);
    }

    #[test]
    fn test_matrix_accessor() {
        let hasher = KHeavyHash::new();
        let matrix = hasher.matrix();
        let m1 = matrix.multiply(&[0x42u8; 32]);
        let m2 = matrix.multiply(&[0x42u8; 32]);
        assert_eq!(m1, m2);
    }

    #[test]
    fn test_mine_easy_target() {
        let hasher = KHeavyHash::new();
        let header = b"mine me";
        let target = Target::max();
        let result = hasher.mine(header, &target, 0, 10000);
        assert!(result.is_some());
        let pow = result.unwrap();
        assert!(target.is_met_by(&pow.hash));
    }

    #[test]
    fn test_mine_returns_valid_nonce() {
        let hasher = KHeavyHash::new();
        let header = b"mining nonce test";
        let target = Target::max();
        let result = hasher.mine(header, &target, 0, 10000).unwrap();
        let verification = hasher.hash(header, result.nonce);
        assert!(target.is_met_by(&verification.hash));
    }

    #[test]
    fn test_mine_with_start_nonce() {
        let hasher = KHeavyHash::new();
        let header = b"start nonce test";
        let target = Target::max();
        let start_nonce = 5000;
        let result = hasher.mine(header, &target, start_nonce, 10000);
        assert!(result.is_some());
        let pow = result.unwrap();
        assert!(pow.nonce >= start_nonce);
    }

    #[test]
    fn test_mine_impossible_target() {
        let hasher = KHeavyHash::new();
        let header = b"impossible test";
        let target = Target::from_bytes([0u8; 32]);
        let result = hasher.mine(header, &target, 0, 100);
        assert!(result.is_none());
    }

    #[test]
    fn test_mine_with_callback_finds_solution() {
        let hasher = KHeavyHash::new();
        let header = b"callback test";
        let target = Target::max();
        let result = hasher.mine_with_callback(header, &target, 0, 50000, |_, _| true);
        assert!(result.is_some());
    }

    #[test]
    fn test_mine_with_callback_can_cancel() {
        let hasher = KHeavyHash::new();
        let header = b"cancel test";
        let target = Target::from_bytes([0u8; 32]);
        let mut called = false;
        let result = hasher.mine_with_callback(header, &target, 0, 100000, |_, _| {
            if called {
                false
            } else {
                called = true;
                true
            }
        });
        assert!(result.is_none());
    }

    #[test]
    fn test_pow_hash_meets_target() {
        let hasher = KHeavyHash::new();
        let header = b"pow hash test";
        let target = Target::max();
        let pow = hasher.mine(header, &target, 0, 10000).unwrap();
        assert!(pow.meets_target(&target));
    }

    #[test]
    fn test_pow_hash_as_bytes() {
        let hasher = KHeavyHash::new();
        let pow = hasher.hash(b"test", 0);
        let bytes = pow.as_bytes();
        assert_eq!(bytes.len(), 32);
        assert_eq!(bytes, pow.hash.as_bytes());
    }

    #[test]
    fn test_pow_hash_clone() {
        let hasher = KHeavyHash::new();
        let pow = hasher.hash(b"clone test", 42);
        let cloned = pow.clone();
        assert_eq!(cloned.hash, pow.hash);
        assert_eq!(cloned.nonce, pow.nonce);
        assert_eq!(cloned.pre_hash, pow.pre_hash);
    }

    #[test]
    fn test_pow_hash_debug() {
        let hasher = KHeavyHash::new();
        let pow = hasher.hash(b"debug test", 0);
        let debug_str = format!("{:?}", pow);
        assert!(debug_str.contains("PowHash"));
    }

    #[test]
    fn test_parallel_miner_creation_with_thread_count() {
        let miner = ParallelMiner::new(4);
        assert_eq!(miner.num_threads(), 4);
    }

    #[test]
    fn test_parallel_miner_creation_single_thread() {
        let miner = ParallelMiner::new(1);
        assert_eq!(miner.num_threads(), 1);
    }

    #[test]
    fn test_parallel_miner_auto_thread_detection() {
        let miner = ParallelMiner::new(0);
        assert!(miner.num_threads() >= 1);
    }

    #[test]
    fn test_parallel_miner_mine_finds_solution() {
        let miner = ParallelMiner::new(2);
        let header = b"parallel mining test";
        let target = Target::max();
        let result = miner.mine(header, &target, 0);
        assert!(result.is_some());
    }

    #[test]
    fn test_parallel_miner_solution_meets_target() {
        let miner = ParallelMiner::new(2);
        let header = b"parallel target test";
        let target = Target::max();
        let result = miner.mine(header, &target, 0).unwrap();
        assert!(target.is_met_by(&result.hash));
    }

    #[test]
    fn test_parallel_miner_solution_is_valid() {
        let miner = ParallelMiner::new(2);
        let header = b"parallel valid test";
        let target = Target::max();
        let result = miner.mine(header, &target, 0).unwrap();
        let hasher = KHeavyHash::new();
        let verification = hasher.hash(header, result.nonce);
        assert_eq!(verification.hash, result.hash);
    }

    #[test]
    fn test_parallel_miner_different_start_nonce() {
        let miner = ParallelMiner::new(2);
        let header = b"start nonce parallel test";
        let target = Target::max();
        let result = miner.mine(header, &target, 1000);
        assert!(result.is_some());
    }

    #[test]
    fn test_hashrate_benchmark_returns_positive() {
        let bench = HashrateBenchmark::new();
        let hashrate = bench.run(100);
        assert!(hashrate > 0.0);
    }

    #[test]
    fn test_hashrate_benchmark_is_finite() {
        let bench = HashrateBenchmark::new();
        let hashrate = bench.run(100);
        assert!(hashrate.is_finite());
    }

    #[test]
    fn test_hashrate_benchmark_consistency() {
        let bench = HashrateBenchmark::new();
        let h1 = bench.run(100);
        let h2 = bench.run(100);
        assert!(h1 > 0.0);
        assert!(h2 > 0.0);
        let ratio = h1 / h2;
        assert!(ratio > 0.1 && ratio < 10.0);
    }

    #[test]
    fn test_hashrate_benchmark_default_trait() {
        let bench1 = HashrateBenchmark::default();
        let bench2 = HashrateBenchmark::new();
        let h1 = bench1.run(50);
        let h2 = bench2.run(50);
        assert!(h1 > 0.0);
        assert!(h2 > 0.0);
    }

    #[test]
    fn test_hashrate_benchmark_short_duration() {
        let bench = HashrateBenchmark::new();
        let hashrate = bench.run(10);
        assert!(hashrate > 0.0);
    }

    #[test]
    fn test_hashrate_benchmark_longer_duration() {
        let bench = HashrateBenchmark::new();
        let hashrate = bench.run(200);
        assert!(hashrate > 0.0);
    }
}
