//! 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::*;

    #[test]
    fn test_target_from_bits() {
        // Test compact difficulty encoding
        let target = Target::from_bits(0x1d00ffff); // Bitcoin genesis-like
        assert!(target.0[0] == 0 || target.0[1] == 0); // Should have leading zeros
    }

    #[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,
        ]);

        // Hash with more leading zeros should pass
        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));

        // Hash with fewer leading zeros should fail
        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); // 1M hashes in 1 second
        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);
    }
}
