//! kHeavyHash Proof of Work mining for Synor blockchain.
//!
//! This crate implements Kaspa-style optical-ASIC resistant mining using the
//! kHeavyHash algorithm, which combines SHA3 with matrix multiplication in GF(2^8).
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────┐
//! │                      MINING SYSTEM                               │
//! ├─────────────────────────────────────────────────────────────────┤
//! │                                                                  │
//! │  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────┐  │
//! │  │  kHeavyHash  │  │    Block     │  │   Mining Manager     │  │
//! │  │  Algorithm   │  │   Template   │  │   (coordination)     │  │
//! │  └──────┬───────┘  └──────┬───────┘  └──────────┬───────────┘  │
//! │         │                 │                      │              │
//! │         └─────────────────┼──────────────────────┘              │
//! │                           │                                     │
//! │                    ┌──────▼───────┐                            │
//! │                    │   Stratum    │                            │
//! │                    │   Protocol   │                            │
//! │                    └──────────────┘                            │
//! │                                                                  │
//! └─────────────────────────────────────────────────────────────────┘
//! ```
//!
//! # kHeavyHash Algorithm
//!
//! 1. SHA3-256(header) → 256 bits
//! 2. Matrix multiply in GF(2^8) with 64x64 matrix
//! 3. XOR with Blake3(nonce)
//! 4. Final SHA3-256
//! 5. Compare with target

#![allow(dead_code)]

pub mod kheavyhash;
pub mod matrix;
pub mod miner;
pub mod stratum;
pub mod template;

pub use kheavyhash::{KHeavyHash, PowHash};
pub use matrix::HeavyMatrix;
pub use miner::{
    BlockMiner, MinerCommand, MinerConfig, MinerEvent, MiningResult, ParallelBlockMiner,
};
pub use stratum::{StratumClient, StratumJob, StratumServer};
pub use template::{
    BlockTemplate, BlockTemplateBuilder, CoinbaseBuilder, CoinbaseData, TemplateTransaction,
};

use synor_types::{Address, Hash256};

/// Mining-related constants.
pub mod constants {
    /// Target block time in milliseconds (100ms = 10 blocks per second).
    pub const TARGET_BLOCK_TIME_MS: u64 = 100;

    /// Maximum extra nonce size.
    pub const MAX_EXTRA_NONCE_SIZE: usize = 8;

    /// Default mining threads (0 = auto-detect).
    pub const DEFAULT_MINING_THREADS: usize = 0;

    /// Maximum nonce value before extra nonce increment.
    pub const MAX_NONCE: u64 = u64::MAX;

    /// Stratum protocol version.
    pub const STRATUM_VERSION: &str = "2.0.0";

    /// Default stratum port.
    pub const DEFAULT_STRATUM_PORT: u16 = 16111;
}

/// Difficulty target operations.
#[derive(Clone, Copy, Debug)]
pub struct Target(pub [u8; 32]);

impl Target {
    /// Creates target from difficulty bits (compact form).
    pub fn from_bits(bits: u32) -> Self {
        let mut target = [0u8; 32];

        // Compact format: [1 byte exponent][3 bytes mantissa]
        let exponent = (bits >> 24) as usize;
        let mantissa = bits & 0x00FFFFFF;

        if exponent <= 3 {
            let shift = 8 * (3 - exponent);
            let value = mantissa >> shift;
            target[31] = (value & 0xFF) as u8;
            if exponent >= 1 {
                target[30] = ((value >> 8) & 0xFF) as u8;
            }
            if exponent >= 2 {
                target[29] = ((value >> 16) & 0xFF) as u8;
            }
        } else {
            let byte_index = 32 - exponent;
            if byte_index < 32 {
                target[byte_index] = (mantissa & 0xFF) as u8;
            }
            if byte_index + 1 < 32 {
                target[byte_index + 1] = ((mantissa >> 8) & 0xFF) as u8;
            }
            if byte_index + 2 < 32 {
                target[byte_index + 2] = ((mantissa >> 16) & 0xFF) as u8;
            }
        }

        Target(target)
    }

    /// Creates target from raw bytes.
    pub fn from_bytes(bytes: [u8; 32]) -> Self {
        Target(bytes)
    }

    /// Maximum target (minimum difficulty).
    pub fn max() -> Self {
        let mut target = [0xFFu8; 32];
        target[0] = 0x00; // Keep one leading zero
        target[1] = 0xFF;
        Target(target)
    }

    /// Checks if a hash meets this target.
    #[allow(clippy::needless_range_loop)]
    pub fn is_met_by(&self, hash: &Hash256) -> bool {
        // Hash must be <= target (big-endian comparison)
        let hash_bytes = hash.as_bytes();
        for i in 0..32 {
            if hash_bytes[i] < self.0[i] {
                return true;
            }
            if hash_bytes[i] > self.0[i] {
                return false;
            }
        }
        true // Equal is valid
    }

    /// Converts to difficulty (ratio to max target).
    pub fn to_difficulty(&self) -> f64 {
        let max = Self::max();
        let max_val = u256_to_f64(&max.0);
        let target_val = u256_to_f64(&self.0);
        if target_val == 0.0 {
            f64::MAX
        } else {
            max_val / target_val
        }
    }

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

/// Converts 256-bit value to f64 (approximate).
fn u256_to_f64(bytes: &[u8; 32]) -> f64 {
    let mut result = 0.0f64;
    for (i, &byte) in bytes.iter().enumerate() {
        result += (byte as f64) * 2.0f64.powi((31 - i as i32) * 8);
    }
    result
}

/// Mining statistics.
#[derive(Clone, Debug, Default)]
pub struct MiningStats {
    /// Total hashes computed.
    pub total_hashes: u64,
    /// Blocks found.
    pub blocks_found: u64,
    /// Current hashrate (H/s).
    pub hashrate: f64,
    /// Average time to find block (ms).
    pub avg_block_time_ms: f64,
    /// Last block found timestamp.
    pub last_block_time: u64,
    /// Rejected shares (pool mining).
    pub rejected_shares: u64,
    /// Accepted shares (pool mining).
    pub accepted_shares: u64,
}

impl MiningStats {
    /// Updates hashrate based on time and hashes.
    pub fn update_hashrate(&mut self, hashes: u64, elapsed_ms: u64) {
        if elapsed_ms > 0 {
            self.hashrate = (hashes as f64 * 1000.0) / elapsed_ms as f64;
        }
        self.total_hashes += hashes;
    }

    /// Records a found block.
    pub fn record_block(&mut self, timestamp: u64) {
        if self.last_block_time > 0 {
            let elapsed = timestamp - self.last_block_time;
            let n = self.blocks_found as f64;
            self.avg_block_time_ms = (self.avg_block_time_ms * n + elapsed as f64) / (n + 1.0);
        }
        self.blocks_found += 1;
        self.last_block_time = timestamp;
    }

    /// Formats hashrate for display.
    pub fn formatted_hashrate(&self) -> String {
        if self.hashrate >= 1_000_000_000_000.0 {
            format!("{:.2} TH/s", self.hashrate / 1_000_000_000_000.0)
        } else if self.hashrate >= 1_000_000_000.0 {
            format!("{:.2} GH/s", self.hashrate / 1_000_000_000.0)
        } else if self.hashrate >= 1_000_000.0 {
            format!("{:.2} MH/s", self.hashrate / 1_000_000.0)
        } else if self.hashrate >= 1_000.0 {
            format!("{:.2} KH/s", self.hashrate / 1_000.0)
        } else {
            format!("{:.2} H/s", self.hashrate)
        }
    }
}

/// Mining work representing a job to solve.
#[derive(Clone, Debug)]
pub struct MiningWork {
    /// Block header hash (pre-PoW).
    pub pre_pow_hash: Hash256,
    /// Current target.
    pub target: Target,
    /// Timestamp for the block.
    pub timestamp: u64,
    /// Extra nonce for pool mining.
    pub extra_nonce: u64,
    /// Associated block template ID.
    pub template_id: u64,
    /// Miner address for coinbase.
    pub miner_address: Address,
}

/// Result of mining work.
#[derive(Clone, Debug)]
pub struct WorkResult {
    /// The nonce that solved the puzzle.
    pub nonce: u64,
    /// The resulting PoW hash.
    pub pow_hash: Hash256,
    /// Template ID this solves.
    pub template_id: u64,
    /// Time taken to find (ms).
    pub solve_time_ms: u64,
    /// Hashes attempted.
    pub hashes_tried: u64,
}

/// Mining errors.
#[derive(Debug, Clone, thiserror::Error)]
pub enum MiningError {
    /// Invalid block template.
    #[error("Invalid block template: {0}")]
    InvalidTemplate(String),

    /// Target not met.
    #[error("Target not met")]
    TargetNotMet,

    /// Mining cancelled.
    #[error("Mining cancelled")]
    Cancelled,

    /// No work available.
    #[error("No work available")]
    NoWork,

    /// Stratum error.
    #[error("Stratum error: {0}")]
    Stratum(String),

    /// Invalid nonce.
    #[error("Invalid nonce")]
    InvalidNonce,

    /// Hash verification failed.
    #[error("Hash verification failed")]
    HashMismatch,
}

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

    #[test]
    fn test_target_from_bits() {
        let target = Target::from_bits(0x1d00ffff);
        assert!(target.0[0] == 0 || target.0[1] == 0);
    }

    #[test]
    fn test_target_comparison() {
        let target = Target::from_bytes([
            0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff,
        ]);
        let easy_hash = Hash256::from_bytes([
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x01,
        ]);
        assert!(target.is_met_by(&easy_hash));
        let hard_hash = Hash256::from_bytes([
            0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ]);
        assert!(!target.is_met_by(&hard_hash));
    }

    #[test]
    fn test_mining_stats() {
        let mut stats = MiningStats::default();
        stats.update_hashrate(1_000_000, 1000);
        assert!((stats.hashrate - 1_000_000.0).abs() < 0.1);
        assert_eq!(stats.formatted_hashrate(), "1.00 MH/s");
        stats.record_block(1000);
        stats.record_block(2000);
        assert_eq!(stats.blocks_found, 2);
    }

    #[test]
    fn test_target_max_returns_correct_value() {
        let target = Target::max();
        assert_eq!(target.0[0], 0x00);
        assert_eq!(target.0[1], 0xFF);
        for i in 2..32 {
            assert_eq!(target.0[i], 0xFF);
        }
    }

    #[test]
    fn test_target_from_bits_various_difficulties() {
        let target = Target::from_bits(0x1d00ffff);
        assert!(target.0[0] == 0 || target.0[1] == 0);
        let easy_target = Target::from_bits(0x207fffff);
        assert!(easy_target.0[0] == 0 || easy_target.0[1] != 0);
        let target2 = Target::from_bits(0x1c0fffff);
        assert!(target2.0[0] == 0);
    }

    #[test]
    fn test_target_from_bits_edge_cases() {
        let target = Target::from_bits(0x1d000000);
        let sum: u32 = target.0.iter().map(|&x| x as u32).sum();
        assert!(sum < 256);
        let target2 = Target::from_bits(0x01010000);
        assert!(target2.0.iter().any(|&x| x != 0) || target2.0.iter().all(|&x| x == 0));
    }

    #[test]
    fn test_target_from_bytes() {
        let bytes = [0x42u8; 32];
        let target = Target::from_bytes(bytes);
        assert_eq!(target.0, bytes);
    }

    #[test]
    fn test_target_as_bytes() {
        let bytes = [0xAB; 32];
        let target = Target::from_bytes(bytes);
        assert_eq!(target.as_bytes(), &bytes);
    }

    #[test]
    fn test_target_to_difficulty_max_target() {
        let target = Target::max();
        let difficulty = target.to_difficulty();
        assert!(difficulty >= 0.9 && difficulty <= 1.1);
    }

    #[test]
    fn test_target_to_difficulty_harder_target() {
        let max = Target::max();
        let mut harder_bytes = max.0;
        harder_bytes[1] = 0x7F;
        let harder = Target::from_bytes(harder_bytes);
        let difficulty = harder.to_difficulty();
        assert!(difficulty > 1.5);
    }

    #[test]
    fn test_target_to_difficulty_zero_target() {
        let zero = Target::from_bytes([0u8; 32]);
        let difficulty = zero.to_difficulty();
        assert_eq!(difficulty, f64::MAX);
    }

    #[test]
    fn test_target_is_met_by_exact_match() {
        let target_bytes = [
            0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff,
        ];
        let target = Target::from_bytes(target_bytes);
        let hash = Hash256::from_bytes(target_bytes);
        assert!(target.is_met_by(&hash));
    }

    #[test]
    fn test_target_is_met_by_slightly_lower() {
        let target = Target::from_bytes([
            0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff,
        ]);
        let hash = Hash256::from_bytes([
            0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff,
        ]);
        assert!(target.is_met_by(&hash));
    }

    #[test]
    fn test_target_is_met_by_too_high() {
        let target = Target::from_bytes([
            0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff,
        ]);
        let hash = Hash256::from_bytes([
            0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ]);
        assert!(!target.is_met_by(&hash));
    }

    #[test]
    fn test_target_is_met_by_all_zeros_hash() {
        let target = Target::from_bytes([0x01; 32]);
        let hash = Hash256::from_bytes([0u8; 32]);
        assert!(target.is_met_by(&hash));
    }

    #[test]
    fn test_target_is_met_by_first_byte_difference() {
        let target = Target::from_bytes([
            0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ]);
        let hash_high = Hash256::from_bytes([
            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00,
        ]);
        assert!(!target.is_met_by(&hash_high));
    }

    #[test]
    fn test_target_copy_and_clone() {
        let target1 = Target::from_bits(0x1d00ffff);
        let target2 = target1;
        let target3 = target1.clone();
        assert_eq!(target1.0, target2.0);
        assert_eq!(target1.0, target3.0);
    }

    #[test]
    fn test_mining_stats_default() {
        let stats = MiningStats::default();
        assert_eq!(stats.total_hashes, 0);
        assert_eq!(stats.blocks_found, 0);
        assert_eq!(stats.hashrate, 0.0);
        assert_eq!(stats.avg_block_time_ms, 0.0);
        assert_eq!(stats.last_block_time, 0);
        assert_eq!(stats.rejected_shares, 0);
        assert_eq!(stats.accepted_shares, 0);
    }

    #[test]
    fn test_mining_stats_update_hashrate_calculation() {
        let mut stats = MiningStats::default();
        stats.update_hashrate(1000, 1000);
        assert!((stats.hashrate - 1000.0).abs() < 0.1);
        assert_eq!(stats.total_hashes, 1000);
    }

    #[test]
    fn test_mining_stats_update_hashrate_accumulates() {
        let mut stats = MiningStats::default();
        stats.update_hashrate(1000, 1000);
        stats.update_hashrate(2000, 1000);
        assert_eq!(stats.total_hashes, 3000);
        assert!((stats.hashrate - 2000.0).abs() < 0.1);
    }

    #[test]
    fn test_mining_stats_update_hashrate_zero_elapsed() {
        let mut stats = MiningStats::default();
        stats.update_hashrate(1000, 0);
        assert_eq!(stats.hashrate, 0.0);
        assert_eq!(stats.total_hashes, 1000);
    }

    #[test]
    fn test_mining_stats_record_block_first_block() {
        let mut stats = MiningStats::default();
        stats.record_block(1000);
        assert_eq!(stats.blocks_found, 1);
        assert_eq!(stats.last_block_time, 1000);
        assert_eq!(stats.avg_block_time_ms, 0.0);
    }

    #[test]
    fn test_mining_stats_record_block_average_time() {
        let mut stats = MiningStats::default();
        stats.record_block(1000);
        stats.record_block(2000);
        assert_eq!(stats.blocks_found, 2);
        assert_eq!(stats.last_block_time, 2000);
        // First block (n=1): avg = (0 * 1 + 1000) / 2 = 500
        assert!((stats.avg_block_time_ms - 500.0).abs() < 0.1);
    }

    #[test]
    fn test_mining_stats_record_block_running_average() {
        let mut stats = MiningStats::default();
        stats.record_block(100); // First block at t=100
        stats.record_block(1100); // 1000ms gap
        stats.record_block(3100); // 2000ms gap
        assert_eq!(stats.blocks_found, 3);
        // Block 2 (n=1): elapsed=1000, avg = (0 * 1 + 1000) / 2 = 500
        // Block 3 (n=2): elapsed=2000, avg = (500 * 2 + 2000) / 3 = 1000
        assert!((stats.avg_block_time_ms - 1000.0).abs() < 1.0);
    }

    #[test]
    fn test_mining_stats_formatted_hashrate_h_s() {
        let mut stats = MiningStats::default();
        stats.hashrate = 500.0;
        assert_eq!(stats.formatted_hashrate(), "500.00 H/s");
    }

    #[test]
    fn test_mining_stats_formatted_hashrate_kh_s() {
        let mut stats = MiningStats::default();
        stats.hashrate = 1_500.0;
        assert_eq!(stats.formatted_hashrate(), "1.50 KH/s");
    }

    #[test]
    fn test_mining_stats_formatted_hashrate_mh_s() {
        let mut stats = MiningStats::default();
        stats.hashrate = 1_500_000.0;
        assert_eq!(stats.formatted_hashrate(), "1.50 MH/s");
    }

    #[test]
    fn test_mining_stats_formatted_hashrate_gh_s() {
        let mut stats = MiningStats::default();
        stats.hashrate = 1_500_000_000.0;
        assert_eq!(stats.formatted_hashrate(), "1.50 GH/s");
    }

    #[test]
    fn test_mining_stats_formatted_hashrate_th_s() {
        let mut stats = MiningStats::default();
        stats.hashrate = 1_500_000_000_000.0;
        assert_eq!(stats.formatted_hashrate(), "1.50 TH/s");
    }

    #[test]
    fn test_mining_stats_formatted_hashrate_boundaries() {
        let mut stats = MiningStats::default();
        stats.hashrate = 1000.0;
        assert_eq!(stats.formatted_hashrate(), "1.00 KH/s");
        stats.hashrate = 1_000_000.0;
        assert_eq!(stats.formatted_hashrate(), "1.00 MH/s");
        stats.hashrate = 1_000_000_000.0;
        assert_eq!(stats.formatted_hashrate(), "1.00 GH/s");
        stats.hashrate = 1_000_000_000_000.0;
        assert_eq!(stats.formatted_hashrate(), "1.00 TH/s");
    }

    #[test]
    fn test_mining_stats_clone() {
        let mut stats = MiningStats::default();
        stats.total_hashes = 1000;
        stats.blocks_found = 5;
        stats.hashrate = 500.0;
        let cloned = stats.clone();
        assert_eq!(cloned.total_hashes, stats.total_hashes);
        assert_eq!(cloned.blocks_found, stats.blocks_found);
        assert!((cloned.hashrate - stats.hashrate).abs() < 0.1);
    }

    #[test]
    fn test_mining_work_structure_fields() {
        let miner_addr = Address::from_ed25519_pubkey(Network::Mainnet, &[0x42; 32]);
        let work = MiningWork {
            pre_pow_hash: Hash256::from_bytes([1u8; 32]),
            target: Target::max(),
            timestamp: 1234567890,
            extra_nonce: 42,
            template_id: 123,
            miner_address: miner_addr,
        };
        assert_eq!(work.pre_pow_hash.as_bytes(), &[1u8; 32]);
        assert_eq!(work.timestamp, 1234567890);
        assert_eq!(work.extra_nonce, 42);
        assert_eq!(work.template_id, 123);
    }

    #[test]
    fn test_mining_work_clone() {
        let miner_addr = Address::from_ed25519_pubkey(Network::Mainnet, &[0x42; 32]);
        let work = MiningWork {
            pre_pow_hash: Hash256::from_bytes([1u8; 32]),
            target: Target::max(),
            timestamp: 1234567890,
            extra_nonce: 42,
            template_id: 123,
            miner_address: miner_addr,
        };
        let cloned = work.clone();
        assert_eq!(cloned.pre_pow_hash, work.pre_pow_hash);
        assert_eq!(cloned.timestamp, work.timestamp);
        assert_eq!(cloned.extra_nonce, work.extra_nonce);
        assert_eq!(cloned.template_id, work.template_id);
    }

    #[test]
    fn test_work_result_structure_fields() {
        let result = WorkResult {
            nonce: 12345,
            pow_hash: Hash256::from_bytes([2u8; 32]),
            template_id: 456,
            solve_time_ms: 1000,
            hashes_tried: 50000,
        };
        assert_eq!(result.nonce, 12345);
        assert_eq!(result.pow_hash.as_bytes(), &[2u8; 32]);
        assert_eq!(result.template_id, 456);
        assert_eq!(result.solve_time_ms, 1000);
        assert_eq!(result.hashes_tried, 50000);
    }

    #[test]
    fn test_work_result_clone() {
        let result = WorkResult {
            nonce: 12345,
            pow_hash: Hash256::from_bytes([2u8; 32]),
            template_id: 456,
            solve_time_ms: 1000,
            hashes_tried: 50000,
        };
        let cloned = result.clone();
        assert_eq!(cloned.nonce, result.nonce);
        assert_eq!(cloned.pow_hash, result.pow_hash);
        assert_eq!(cloned.template_id, result.template_id);
    }

    #[test]
    fn test_mining_error_invalid_template_display() {
        let error = MiningError::InvalidTemplate("missing coinbase".to_string());
        assert_eq!(format!("{}", error), "Invalid block template: missing coinbase");
    }

    #[test]
    fn test_mining_error_target_not_met_display() {
        let error = MiningError::TargetNotMet;
        assert_eq!(format!("{}", error), "Target not met");
    }

    #[test]
    fn test_mining_error_cancelled_display() {
        let error = MiningError::Cancelled;
        assert_eq!(format!("{}", error), "Mining cancelled");
    }

    #[test]
    fn test_mining_error_no_work_display() {
        let error = MiningError::NoWork;
        assert_eq!(format!("{}", error), "No work available");
    }

    #[test]
    fn test_mining_error_stratum_display() {
        let error = MiningError::Stratum("connection failed".to_string());
        assert_eq!(format!("{}", error), "Stratum error: connection failed");
    }

    #[test]
    fn test_mining_error_invalid_nonce_display() {
        let error = MiningError::InvalidNonce;
        assert_eq!(format!("{}", error), "Invalid nonce");
    }

    #[test]
    fn test_mining_error_hash_mismatch_display() {
        let error = MiningError::HashMismatch;
        assert_eq!(format!("{}", error), "Hash verification failed");
    }

    #[test]
    fn test_mining_error_clone() {
        let error = MiningError::InvalidTemplate("test".to_string());
        let cloned = error.clone();
        assert_eq!(format!("{}", error), format!("{}", cloned));
    }

    #[test]
    fn test_u256_to_f64_zero() {
        let bytes = [0u8; 32];
        let result = u256_to_f64(&bytes);
        assert_eq!(result, 0.0);
    }

    #[test]
    fn test_u256_to_f64_one() {
        let mut bytes = [0u8; 32];
        bytes[31] = 1;
        let result = u256_to_f64(&bytes);
        assert!(result > 0.0 && result < 10.0);
    }

    #[test]
    fn test_u256_to_f64_max() {
        let bytes = [0xFFu8; 32];
        let result = u256_to_f64(&bytes);
        assert!(result > 0.0);
        assert!(result.is_finite());
    }

    #[test]
    fn test_constants_target_block_time() {
        assert_eq!(constants::TARGET_BLOCK_TIME_MS, 100);
    }

    #[test]
    fn test_constants_max_extra_nonce_size() {
        assert_eq!(constants::MAX_EXTRA_NONCE_SIZE, 8);
    }

    #[test]
    fn test_constants_default_mining_threads() {
        assert_eq!(constants::DEFAULT_MINING_THREADS, 0);
    }

    #[test]
    fn test_constants_max_nonce() {
        assert_eq!(constants::MAX_NONCE, u64::MAX);
    }

    #[test]
    fn test_constants_stratum_version() {
        assert_eq!(constants::STRATUM_VERSION, "2.0.0");
    }

    #[test]
    fn test_constants_default_stratum_port() {
        assert_eq!(constants::DEFAULT_STRATUM_PORT, 16111);
    }
}
