//! Mining coordinator
//!
//! Handles block mining, template generation, and mining coordination.

use crate::utils::current_timestamp;
use anyhow::Result;
use blvm_protocol::{Block, BlockHeader, Transaction};
use std::collections::HashMap;
use tracing::{debug, info, warn};

/// Mempool provider trait for dependency injection
pub trait MempoolProvider: Send + Sync {
    /// Get transactions from mempool
    fn get_transactions(&self) -> Vec<Transaction>;

    /// Get transaction by hash
    fn get_transaction(&self, hash: &[u8; 32]) -> Option<Transaction>;

    /// Get mempool size
    fn get_mempool_size(&self) -> usize;

    /// Get prioritized transactions (by fee rate)
    /// Requires UTXO set for accurate fee calculation
    fn get_prioritized_transactions(
        &self,
        limit: usize,
        utxo_set: &blvm_protocol::UtxoSet,
    ) -> Vec<Transaction>;

    /// Remove transaction from mempool
    fn remove_transaction(&mut self, hash: &[u8; 32]) -> bool;
}

/// Transaction selector for block building
pub struct TransactionSelector {
    /// Maximum block size
    max_block_size: usize,
    /// Maximum block weight
    max_block_weight: u64,
    /// Minimum fee rate (satoshis per byte)
    min_fee_rate: u64,
}

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

impl TransactionSelector {
    /// Create a new transaction selector
    pub fn new() -> Self {
        Self {
            max_block_size: 1_000_000,   // 1MB
            max_block_weight: 4_000_000, // 4M weight units
            min_fee_rate: 1,             // 1 satoshi per byte
        }
    }

    /// Create with custom parameters
    pub fn with_params(max_block_size: usize, max_block_weight: u64, min_fee_rate: u64) -> Self {
        Self {
            max_block_size,
            max_block_weight,
            min_fee_rate,
        }
    }

    /// Select transactions for block
    /// Note: Requires UTXO set for fee calculation - caller must provide it
    pub fn select_transactions(
        &self,
        mempool: &dyn MempoolProvider,
        utxo_set: &blvm_protocol::UtxoSet,
    ) -> Vec<Transaction> {
        let mut selected = Vec::new();
        let mut current_size = 0;
        let mut current_weight = 0;

        // Get prioritized transactions (with UTXO set for fee calculation)
        // MempoolManager.get_prioritized_transactions() already returns transactions
        // sorted by fee rate (descending) calculated with real UTXO set
        let transactions = mempool.get_prioritized_transactions(1000, utxo_set);

        for tx in transactions {
            let tx_size = self.calculate_transaction_size(&tx);
            let tx_weight = self.calculate_transaction_weight(&tx);

            // Check if adding this transaction would exceed limits
            if current_size + tx_size > self.max_block_size
                || current_weight + tx_weight > self.max_block_weight
            {
                break;
            }

            // Check minimum fee rate using real UTXO set
            // Since transactions are already prioritized by fee rate from MempoolManager,
            // we can calculate the actual fee rate here for the final check
            let fee_rate = self.calculate_fee_rate_with_utxo(&tx, utxo_set);
            if fee_rate < self.min_fee_rate {
                continue;
            }

            selected.push(tx);
            current_size += tx_size;
            current_weight += tx_weight;
        }

        selected
    }

    /// Calculate transaction size in bytes
    fn calculate_transaction_size(&self, tx: &Transaction) -> usize {
        // Simplified calculation - in real implementation would serialize
        tx.inputs.len() * 148 + tx.outputs.len() * 34 + 10
    }

    /// Calculate transaction weight
    fn calculate_transaction_weight(&self, tx: &Transaction) -> u64 {
        // Simplified calculation - in real implementation would use proper weight calculation
        self.calculate_transaction_size(tx) as u64 * 4
    }

    /// Calculate fee rate (satoshis per byte) using UTXO set
    fn calculate_fee_rate_with_utxo(
        &self,
        tx: &Transaction,
        utxo_set: &blvm_protocol::UtxoSet,
    ) -> u64 {
        let size = self.calculate_transaction_size(tx);
        if size == 0 {
            return 0;
        }

        // Calculate actual fee using UTXO set
        let mut input_total = 0u64;
        for input in &tx.inputs {
            if let Some(utxo) = utxo_set.get(&input.prevout) {
                input_total += utxo.value as u64;
            }
        }

        let output_total: u64 = tx.outputs.iter().map(|out| out.value as u64).sum();
        let fee = input_total.saturating_sub(output_total);

        fee / size as u64
    }

    /// Get maximum block size
    pub fn max_block_size(&self) -> usize {
        self.max_block_size
    }

    /// Get maximum block weight
    pub fn max_block_weight(&self) -> u64 {
        self.max_block_weight
    }

    /// Get minimum fee rate
    pub fn min_fee_rate(&self) -> u64 {
        self.min_fee_rate
    }
}

/// Mining engine for block mining
pub struct MiningEngine {
    /// Mining enabled flag
    mining_enabled: bool,
    /// Mining threads
    mining_threads: u32,
    /// Current block template
    block_template: Option<Block>,
    /// Mining statistics
    stats: MiningStats,
}

#[derive(Debug, Clone)]
pub struct MiningStats {
    pub blocks_mined: u64,
    pub total_hashrate: f64,
    pub average_block_time: f64,
    pub last_block_time: Option<u64>,
}

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

impl MiningEngine {
    /// Create a new mining engine
    pub fn new() -> Self {
        Self {
            mining_enabled: false,
            mining_threads: 1,
            block_template: None,
            stats: MiningStats {
                blocks_mined: 0,
                total_hashrate: 0.0,
                average_block_time: 0.0,
                last_block_time: None,
            },
        }
    }

    /// Create with custom thread count
    pub fn with_threads(threads: u32) -> Self {
        Self {
            mining_enabled: false,
            mining_threads: threads,
            block_template: None,
            stats: MiningStats {
                blocks_mined: 0,
                total_hashrate: 0.0,
                average_block_time: 0.0,
                last_block_time: None,
            },
        }
    }

    /// Enable mining
    pub fn enable_mining(&mut self) {
        self.mining_enabled = true;
        info!("Mining enabled with {} threads", self.mining_threads);
    }

    /// Disable mining
    pub fn disable_mining(&mut self) {
        self.mining_enabled = false;
        info!("Mining disabled");
    }

    /// Check if mining is enabled
    pub fn is_mining_enabled(&self) -> bool {
        self.mining_enabled
    }

    /// Get mining statistics
    pub fn get_stats(&self) -> &MiningStats {
        &self.stats
    }

    /// Get mining threads
    pub fn get_threads(&self) -> u32 {
        self.mining_threads
    }

    /// Set mining threads
    pub fn set_threads(&mut self, threads: u32) {
        self.mining_threads = threads;
    }

    /// Mine a block template using actual proof of work (async, multithreaded)
    pub async fn mine_template(&mut self, template: Block) -> Result<Block> {
        debug!("Mining block template with {} threads", self.mining_threads);

        // Update template
        self.block_template = Some(template.clone());

        // Use consensus layer to mine the block (actual PoW)
        use blvm_protocol::ConsensusProof;
        let consensus = ConsensusProof::new();

        // Calculate max attempts per thread based on difficulty
        // For regtest: low difficulty, should find nonce quickly
        // For testnet/mainnet: high difficulty, may need many attempts
        let max_attempts_per_thread = 1_000_000u64; // Reasonable limit per thread

        // Multi-threaded mining: spawn tasks for each thread
        if self.mining_threads > 1 {
            self.mine_template_multithreaded(template, max_attempts_per_thread, &consensus)
                .await
        } else {
            // Single-threaded: use blocking task to avoid blocking async runtime
            let template_clone = template.clone();
            let (mined_block, result) = tokio::task::spawn_blocking(move || {
                consensus.mine_block(template_clone, max_attempts_per_thread)
            })
            .await
            .map_err(|e| anyhow::anyhow!("Mining task panicked: {}", e))?
            .map_err(|e| anyhow::anyhow!("Mining failed: {}", e))?;

            self.handle_mining_result(mined_block, result)
        }
    }

    /// Multi-threaded mining implementation
    async fn mine_template_multithreaded(
        &mut self,
        template: Block,
        max_attempts_per_thread: u64,
        _consensus: &blvm_protocol::ConsensusProof,
    ) -> Result<Block> {
        use blvm_protocol::mining::MiningResult;
        use blvm_protocol::pow::check_proof_of_work;
        use tokio::sync::oneshot;

        // Calculate nonce range per thread
        let nonces_per_thread = max_attempts_per_thread;
        let total_threads = self.mining_threads as u64;

        // Spawn mining tasks for each thread
        let mut handles = Vec::new();
        for thread_id in 0..total_threads {
            let template_clone = template.clone();
            let start_nonce = thread_id * nonces_per_thread;
            let end_nonce = start_nonce + nonces_per_thread;

            let (tx, rx) = oneshot::channel();

            // Spawn blocking task for CPU-bound mining work
            let handle = tokio::task::spawn_blocking(move || {
                // Try nonces in this thread's range
                for nonce in start_nonce..end_nonce {
                    let mut block = template_clone.clone();
                    block.header.nonce = nonce;

                    // Check proof of work using standalone function
                    if let Ok(valid) = check_proof_of_work(&block.header) {
                        if valid {
                            let _ = tx.send(Ok((block, MiningResult::Success)));
                            return;
                        }
                    }
                }

                // No valid nonce found in this range
                let mut block = template_clone;
                block.header.nonce = start_nonce; // Last nonce tried (failure result)
                let _ = tx.send(Ok((block, MiningResult::Failure)));
            });

            handles.push((handle, rx));
        }

        // Wait for first successful result or all failures
        let mut results = Vec::new();
        for (handle, rx) in handles {
            // Wait for task completion
            handle
                .await
                .map_err(|e| anyhow::anyhow!("Mining task panicked: {}", e))?;

            // Get result
            match rx.await {
                Ok(Ok((block, result))) => {
                    if matches!(result, MiningResult::Success) {
                        // Found valid nonce! Return immediately
                        return self.handle_mining_result(block, result);
                    }
                    results.push((block, result));
                }
                Ok(Err(e)) => return Err(e),
                Err(_) => {
                    // Channel closed, task may have found solution
                    continue;
                }
            }
        }

        // All threads failed
        if let Some((block, _)) = results.first() {
            self.handle_mining_result(block.clone(), MiningResult::Failure)
        } else {
            Err(anyhow::anyhow!("Mining failed: all threads exhausted"))
        }
    }

    /// Handle mining result and update statistics
    fn handle_mining_result(
        &mut self,
        mined_block: Block,
        result: blvm_protocol::mining::MiningResult,
    ) -> Result<Block> {
        use blvm_protocol::mining::MiningResult;

        match result {
            MiningResult::Success => {
                info!(
                    "Successfully mined block with nonce {}",
                    mined_block.header.nonce
                );

                // Update statistics
                self.stats.blocks_mined += 1;
                self.stats.last_block_time = Some(current_timestamp());

                Ok(mined_block)
            }
            MiningResult::Failure => {
                // Could not find valid nonce in max_attempts
                // This is normal for high difficulty (mainnet)
                warn!("Could not find valid nonce (difficulty may be too high)");
                Err(anyhow::anyhow!("Mining failed: could not find valid nonce"))
            }
        }
    }

    /// Get current block template
    pub fn get_block_template(&self) -> Option<&Block> {
        self.block_template.as_ref()
    }

    /// Clear block template
    pub fn clear_template(&mut self) {
        self.block_template = None;
    }

    /// Update hashrate
    pub fn update_hashrate(&mut self, hashrate: f64) {
        self.stats.total_hashrate = hashrate;
    }

    /// Update average block time
    pub fn update_average_block_time(&mut self, block_time: f64) {
        self.stats.average_block_time = block_time;
    }
}

/// Mining coordinator
pub struct MiningCoordinator {
    /// Mining engine
    mining_engine: MiningEngine,
    /// Transaction selector
    transaction_selector: TransactionSelector,
    /// Mempool manager (real implementation)
    mempool: std::sync::Arc<crate::node::mempool::MempoolManager>,
    /// Storage for UTXO set access
    storage: Option<std::sync::Arc<crate::storage::Storage>>,
}

impl MiningCoordinator {
    /// Create a new mining coordinator with real mempool and storage
    pub fn new(
        mempool: std::sync::Arc<crate::node::mempool::MempoolManager>,
        storage: Option<std::sync::Arc<crate::storage::Storage>>,
    ) -> Self {
        Self {
            mining_engine: MiningEngine::new(),
            transaction_selector: TransactionSelector::new(),
            mempool,
            storage,
        }
    }

    /// Create with custom parameters
    pub fn with_params(
        mempool: std::sync::Arc<crate::node::mempool::MempoolManager>,
        storage: Option<std::sync::Arc<crate::storage::Storage>>,
        threads: u32,
        max_block_size: usize,
        max_block_weight: u64,
        min_fee_rate: u64,
    ) -> Self {
        Self {
            mining_engine: MiningEngine::with_threads(threads),
            transaction_selector: TransactionSelector::with_params(
                max_block_size,
                max_block_weight,
                min_fee_rate,
            ),
            mempool,
            storage,
        }
    }

    /// Start the mining coordinator
    pub async fn start(&mut self) -> Result<()> {
        info!("Starting mining coordinator");

        // Initialize mining
        self.initialize_mining().await?;

        // Start mining loop
        self.mining_loop().await?;

        Ok(())
    }

    /// Initialize mining
    async fn initialize_mining(&mut self) -> Result<()> {
        debug!("Initializing mining");

        // Check if mining should be enabled
        // In a real implementation, this would check configuration

        Ok(())
    }

    /// Main mining loop
    async fn mining_loop(&mut self) -> Result<()> {
        loop {
            if self.mining_engine.is_mining_enabled() {
                self.mine_block().await?;
            } else {
                // Wait for mining to be enabled
                tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
            }
        }
    }

    /// Mine a block
    async fn mine_block(&mut self) -> Result<()> {
        debug!("Mining block");

        // Generate block template
        let template = self.generate_block_template().await?;

        // Mine the block
        let mined_block = self.mining_engine.mine_template(template).await?;

        // Submit the block
        self.submit_block(mined_block).await?;

        Ok(())
    }

    /// Generate block template
    pub async fn generate_block_template(&mut self) -> Result<Block> {
        debug!("Generating block template");

        // Get chain tip from storage for prev_block_hash and difficulty
        let (prev_block_hash, bits, height) = if let Some(ref storage) = self.storage {
            if let Some(tip_header) = storage
                .chain()
                .get_tip_header()
                .map_err(|e| anyhow::anyhow!("Failed to get tip header: {}", e))?
            {
                let tip_hash = storage
                    .chain()
                    .get_tip_hash()
                    .map_err(|e| anyhow::anyhow!("Failed to get tip hash: {}", e))?
                    .unwrap_or([0u8; 32]);
                let chain_height = storage
                    .chain()
                    .get_height()
                    .map_err(|e| anyhow::anyhow!("Failed to get chain height: {}", e))?
                    .unwrap_or(0);
                (tip_hash, tip_header.bits, chain_height)
            } else {
                // No chain tip - use genesis defaults
                ([0u8; 32], 0x1d00ffff, 0)
            }
        } else {
            // No storage - use defaults
            ([0u8; 32], 0x1d00ffff, 0)
        };

        // Get UTXO set from storage for fee calculation
        let utxo_set = if let Some(ref storage) = self.storage {
            storage
                .utxos()
                .get_all_utxos()
                .map_err(|e| anyhow::anyhow!("Failed to get UTXO set: {}", e))?
        } else {
            // No storage - use empty UTXO set (will result in 0 fees)
            blvm_protocol::UtxoSet::default()
        };

        // Select transactions from mempool (with UTXO set for accurate fee calculation)
        let transactions = self
            .transaction_selector
            .select_transactions(&*self.mempool as &dyn MempoolProvider, &utxo_set);

        // Create coinbase transaction with subsidy + fees
        let coinbase_tx = self
            .create_coinbase_transaction(height + 1, &transactions, &utxo_set)
            .await?;

        // Build transaction list (coinbase first)
        let mut all_transactions = vec![coinbase_tx];
        all_transactions.extend(transactions);

        // Calculate merkle root from transactions (we own all_transactions, so we can mutate it)
        use blvm_protocol::mining::calculate_merkle_root;
        let merkle_root = calculate_merkle_root(&all_transactions)
            .map_err(|e| anyhow::anyhow!("Failed to calculate merkle root: {}", e))?;

        // Get current timestamp
        let timestamp = current_timestamp();

        // Build block template
        let template = Block {
            header: BlockHeader {
                version: 1,
                prev_block_hash,
                merkle_root,
                timestamp,
                bits,
                nonce: 0,
            },
            transactions: all_transactions.into_boxed_slice(),
        };

        debug!("Generated block template: height={}, prev_hash={:?}, {} transactions, merkle_root={:?}",
               height + 1, prev_block_hash, template.transactions.len(), merkle_root);

        Ok(template)
    }

    /// Create coinbase transaction with subsidy + fees
    async fn create_coinbase_transaction(
        &self,
        height: u64,
        selected_transactions: &[Transaction],
        utxo_set: &blvm_protocol::UtxoSet,
    ) -> Result<Transaction> {
        use blvm_protocol::ConsensusProof;

        // 1. Get block subsidy from consensus layer
        let consensus = ConsensusProof::new();
        let subsidy = consensus.get_block_subsidy(height) as u64;

        // 2. Calculate total fees from selected transactions
        let total_fees: u64 = selected_transactions
            .iter()
            .map(|tx| self.mempool.calculate_transaction_fee(tx, utxo_set))
            .sum();

        // 3. Coinbase value = subsidy + fees
        let coinbase_value = subsidy.checked_add(total_fees).ok_or_else(|| {
            anyhow::anyhow!(
                "Coinbase value overflow: subsidy {} + fees {}",
                subsidy,
                total_fees
            )
        })?;

        debug!(
            "Creating coinbase: height={}, subsidy={}, fees={}, total={}",
            height, subsidy, total_fees, coinbase_value
        );

        // 4. Create coinbase transaction (BIP34 height in scriptSig; BIP54: lock_time = height - 13, sequence != 0xffffffff)
        let lock_time = height.saturating_sub(13);
        let h = height.min(u64::from(u32::MAX));
        let mut height_bytes = Vec::new();
        let mut x = h;
        while x > 0 {
            height_bytes.push((x & 0xff) as u8);
            x >>= 8;
        }
        if height_bytes.is_empty() {
            height_bytes.push(0);
        }
        let mut script_sig = vec![height_bytes.len() as u8];
        script_sig.extend(height_bytes);
        if script_sig.len() < 2 {
            script_sig = vec![0x01, 0x00];
        }
        Ok(Transaction {
            version: 1,
            inputs: vec![blvm_protocol::TransactionInput {
                prevout: blvm_protocol::OutPoint {
                    hash: [0u8; 32],
                    index: 0xffffffff,
                },
                script_sig,
                sequence: 0xfffffffe,
            }]
            .into(),
            outputs: crate::tx_outputs![blvm_protocol::TransactionOutput {
                value: coinbase_value as i64,
                script_pubkey: vec![
                    blvm_protocol::opcodes::OP_DUP,
                    blvm_protocol::opcodes::OP_HASH160,
                    blvm_protocol::opcodes::PUSH_20_BYTES,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    blvm_protocol::opcodes::OP_EQUALVERIFY,
                    blvm_protocol::opcodes::OP_CHECKSIG,
                ],
            }],
            lock_time,
        })
    }

    /// Submit mined block
    async fn submit_block(&self, _block: Block) -> Result<()> {
        debug!("Submitting mined block");

        // In a real implementation, this would:
        // 1. Validate the block using blvm-consensus
        // 2. Add to blockchain
        // 3. Relay to peers

        Ok(())
    }

    /// Enable mining
    pub fn enable_mining(&mut self) {
        self.mining_engine.enable_mining();
    }

    /// Disable mining
    pub fn disable_mining(&mut self) {
        self.mining_engine.disable_mining();
    }

    /// Check if mining is enabled
    pub fn is_mining_enabled(&self) -> bool {
        self.mining_engine.is_mining_enabled()
    }

    /// Get mining info
    pub fn get_mining_info(&self) -> MiningInfo {
        MiningInfo {
            enabled: self.mining_engine.is_mining_enabled(),
            threads: self.mining_engine.get_threads(),
            has_template: self.mining_engine.get_block_template().is_some(),
        }
    }

    /// Get mining statistics
    pub fn get_mining_stats(&self) -> &MiningStats {
        self.mining_engine.get_stats()
    }

    /// Get access to the mining engine
    pub fn mining_engine(&self) -> &MiningEngine {
        &self.mining_engine
    }

    /// Get mutable access to the mining engine
    pub fn mining_engine_mut(&mut self) -> &mut MiningEngine {
        &mut self.mining_engine
    }

    /// Get access to the transaction selector
    pub fn transaction_selector(&self) -> &TransactionSelector {
        &self.transaction_selector
    }

    /// Get mutable access to the transaction selector
    pub fn transaction_selector_mut(&mut self) -> &mut TransactionSelector {
        &mut self.transaction_selector
    }

    /// Get mempool size
    pub fn get_mempool_size(&self) -> usize {
        self.mempool.size()
    }
}

/// Mining information
#[derive(Debug, Clone)]
pub struct MiningInfo {
    pub enabled: bool,
    pub threads: u32,
    pub has_template: bool,
}

/// Mock mempool provider for testing
pub struct MockMempoolProvider {
    transactions: HashMap<[u8; 32], Transaction>,
    prioritized_transactions: Vec<(Transaction, u64)>,
}

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

impl MockMempoolProvider {
    pub fn new() -> Self {
        Self {
            transactions: HashMap::new(),
            prioritized_transactions: Vec::new(),
        }
    }

    pub fn add_transaction(&mut self, tx: Transaction) {
        let hash = self.calculate_tx_hash(&tx);
        let fee_rate = self.calculate_fee_rate(&tx);
        self.transactions.insert(hash, tx.clone());
        self.prioritized_transactions.push((tx, fee_rate));
        // Sort by fee rate (simplified)
        self.prioritized_transactions.sort_by(|a, b| b.1.cmp(&a.1));
    }

    pub fn clear(&mut self) {
        self.transactions.clear();
        self.prioritized_transactions.clear();
    }

    fn calculate_tx_hash(&self, tx: &Transaction) -> [u8; 32] {
        // Simplified hash calculation
        let mut hash = [0u8; 32];
        hash[0] = tx.version as u8;
        hash[1] = tx.inputs.len() as u8;
        hash[2] = tx.outputs.len() as u8;
        hash
    }

    fn calculate_fee_rate(&self, tx: &Transaction) -> u64 {
        // Simplified fee rate calculation - make it vary by version
        let total_output_value: u64 = tx.outputs.iter().map(|out| out.value as u64).sum();
        let total_input_value = total_output_value + (tx.version * 1000); // Mock input value varies by version
        let fee = total_input_value - total_output_value;
        let size = tx.inputs.len() * 148 + tx.outputs.len() * 34 + 10;
        if size == 0 {
            return 0;
        }
        fee / size as u64
    }
}

impl MempoolProvider for MockMempoolProvider {
    fn get_transactions(&self) -> Vec<Transaction> {
        self.transactions.values().cloned().collect()
    }

    fn get_transaction(&self, hash: &[u8; 32]) -> Option<Transaction> {
        self.transactions.get(hash).cloned()
    }

    fn get_mempool_size(&self) -> usize {
        self.transactions.len()
    }

    fn get_prioritized_transactions(
        &self,
        limit: usize,
        _utxo_set: &blvm_protocol::UtxoSet,
    ) -> Vec<Transaction> {
        // Mock implementation ignores UTXO set and uses pre-calculated priorities
        self.prioritized_transactions
            .iter()
            .take(limit)
            .map(|(tx, _)| tx.clone())
            .collect()
    }

    fn remove_transaction(&mut self, hash: &[u8; 32]) -> bool {
        if let Some(tx) = self.transactions.remove(hash) {
            self.prioritized_transactions.retain(|(t, _)| t != &tx);
            true
        } else {
            false
        }
    }
}

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

    #[test]
    fn test_transaction_selector_creation() {
        let selector = TransactionSelector::new();
        assert_eq!(selector.max_block_size(), 1_000_000);
        assert_eq!(selector.max_block_weight(), 4_000_000);
        assert_eq!(selector.min_fee_rate(), 1);
    }

    #[test]
    fn test_transaction_selector_with_params() {
        let selector = TransactionSelector::with_params(2_000_000, 8_000_000, 5);
        assert_eq!(selector.max_block_size(), 2_000_000);
        assert_eq!(selector.max_block_weight(), 8_000_000);
        assert_eq!(selector.min_fee_rate(), 5);
    }

    #[test]
    fn test_transaction_selector_transaction_selection() {
        let selector = TransactionSelector::new();
        let mut mempool = MockMempoolProvider::new();

        // Add some test transactions
        let tx1 = create_test_transaction(1, 1000);
        let tx2 = create_test_transaction(2, 2000);
        let tx3 = create_test_transaction(3, 500);

        mempool.add_transaction(tx1);
        mempool.add_transaction(tx2);
        mempool.add_transaction(tx3);

        // UTXO set must contain inputs for fee calculation (create_test_transaction uses [0;32], index 0)
        let mut utxo_set = blvm_protocol::UtxoSet::default();
        let outpoint = blvm_protocol::OutPoint {
            hash: [0u8; 32],
            index: 0,
        };
        utxo_set.insert(
            outpoint,
            std::sync::Arc::new(blvm_protocol::UTXO {
                value: 100_000_000, // 1 BTC - enough for all test tx outputs
                script_pubkey: vec![blvm_protocol::opcodes::OP_1].into(),
                height: 0,
                is_coinbase: false,
            }),
        );

        let selected = selector.select_transactions(&mempool, &utxo_set);
        assert!(!selected.is_empty());
        assert!(selected.len() <= 3);
    }

    #[test]
    fn test_transaction_selector_size_calculation() {
        let selector = TransactionSelector::new();
        let tx = create_test_transaction(1, 1000);

        let size = selector.calculate_transaction_size(&tx);
        assert!(size > 0);

        let weight = selector.calculate_transaction_weight(&tx);
        assert!(weight > 0);

        // Test fee rate calculation with UTXO set
        let mut utxo_set = blvm_protocol::UtxoSet::default();
        let outpoint = blvm_protocol::OutPoint {
            hash: [0u8; 32],
            index: 0,
        };
        utxo_set.insert(
            outpoint,
            std::sync::Arc::new(blvm_protocol::UTXO {
                value: 10000,
                script_pubkey: vec![blvm_protocol::opcodes::OP_1].into(),
                height: 0,
                is_coinbase: false,
            }),
        );
        let fee_rate = selector.calculate_fee_rate_with_utxo(&tx, &utxo_set);
        assert!(fee_rate > 0);
    }

    #[test]
    fn test_mining_engine_creation() {
        let engine = MiningEngine::new();
        assert!(!engine.is_mining_enabled());
        assert_eq!(engine.get_threads(), 1);
        assert!(engine.get_block_template().is_none());
        assert_eq!(engine.get_stats().blocks_mined, 0);
    }

    #[test]
    fn test_mining_engine_with_threads() {
        let engine = MiningEngine::with_threads(4);
        assert!(!engine.is_mining_enabled());
        assert_eq!(engine.get_threads(), 4);
    }

    #[test]
    fn test_mining_engine_enable_disable() {
        let mut engine = MiningEngine::new();

        assert!(!engine.is_mining_enabled());
        engine.enable_mining();
        assert!(engine.is_mining_enabled());

        engine.disable_mining();
        assert!(!engine.is_mining_enabled());
    }

    #[test]
    fn test_mining_engine_thread_management() {
        let mut engine = MiningEngine::new();

        assert_eq!(engine.get_threads(), 1);
        engine.set_threads(8);
        assert_eq!(engine.get_threads(), 8);
    }

    #[tokio::test]
    async fn test_mining_engine_mine_template() {
        let mut engine = MiningEngine::new();
        let template = create_test_block();

        let result = engine.mine_template(template.clone()).await;

        // Mining may succeed (if low difficulty) or fail (if high difficulty)
        // Both are valid outcomes for real PoW mining
        if let Ok(mined_block) = result {
            // Successfully mined - verify the block
            assert_eq!(mined_block.header.version, template.header.version);
            assert_ne!(mined_block.header.nonce, template.header.nonce); // Nonce should change

            // Verify proof of work
            use blvm_protocol::pow::check_proof_of_work;
            let pow_valid = check_proof_of_work(&mined_block.header).unwrap();
            assert!(pow_valid, "Mined block should have valid proof of work");

            // Check that template was stored
            assert!(engine.get_block_template().is_some());
            assert_eq!(engine.get_stats().blocks_mined, 1);
        } else {
            // Mining failed (high difficulty) - this is expected for mainnet difficulty
            // Just verify the template was stored
            assert!(engine.get_block_template().is_some());
        }
    }

    #[tokio::test]
    async fn test_mining_engine_mine_template_multithreaded() {
        let mut engine = MiningEngine::with_threads(4);
        let template = create_test_block();

        let result = engine.mine_template(template.clone()).await;

        // Mining may succeed (if low difficulty) or fail (if high difficulty)
        if let Ok(mined_block) = result {
            // Successfully mined - verify the block
            assert_eq!(mined_block.header.version, template.header.version);

            // Verify proof of work
            use blvm_protocol::pow::check_proof_of_work;
            let pow_valid = check_proof_of_work(&mined_block.header).unwrap();
            assert!(pow_valid, "Mined block should have valid proof of work");

            // Check that template was stored
            assert!(engine.get_block_template().is_some());
            assert_eq!(engine.get_stats().blocks_mined, 1);
        } else {
            // Mining failed (high difficulty) - this is expected
            assert!(engine.get_block_template().is_some());
        }
    }

    #[tokio::test]
    async fn test_mining_engine_mine_template_regtest_difficulty() {
        // Test with mainnet difficulty - mining may succeed or fail depending on luck
        // This tests that the mining infrastructure works correctly
        let mut engine = MiningEngine::new();
        let template = create_test_block();
        // template already has bits: 0x1d00ffff (mainnet difficulty)

        let result = engine.mine_template(template.clone()).await;

        // Mining may succeed (if we find a nonce) or fail (if we don't within max_attempts)
        // Both are valid outcomes for real PoW mining
        if let Ok(mined_block) = result {
            // Successfully mined - verify the block
            assert_eq!(mined_block.header.version, template.header.version);
            assert_ne!(mined_block.header.nonce, template.header.nonce);

            // Verify proof of work
            use blvm_protocol::pow::check_proof_of_work;
            let pow_valid = check_proof_of_work(&mined_block.header).unwrap();
            assert!(pow_valid, "Mined block should have valid proof of work");

            // Check statistics
            assert_eq!(engine.get_stats().blocks_mined, 1);
        } else {
            // Mining failed (didn't find nonce within max_attempts) - this is expected
            // The important thing is that the mining infrastructure worked correctly
            assert!(engine.get_block_template().is_some());
        }
    }

    #[test]
    fn test_mining_engine_template_management() {
        let mut engine = MiningEngine::new();

        assert!(engine.get_block_template().is_none());

        let template = create_test_block();
        engine.block_template = Some(template.clone());

        assert!(engine.get_block_template().is_some());
        assert_eq!(
            engine.get_block_template().unwrap().header.version,
            template.header.version
        );

        engine.clear_template();
        assert!(engine.get_block_template().is_none());
    }

    #[test]
    fn test_mining_engine_statistics() {
        let mut engine = MiningEngine::new();
        let stats = engine.get_stats();

        assert_eq!(stats.blocks_mined, 0);
        assert_eq!(stats.total_hashrate, 0.0);
        assert_eq!(stats.average_block_time, 0.0);
        assert!(stats.last_block_time.is_none());

        engine.update_hashrate(1000.0);
        assert_eq!(engine.get_stats().total_hashrate, 1000.0);

        engine.update_average_block_time(600.0);
        assert_eq!(engine.get_stats().average_block_time, 600.0);
    }

    #[test]
    fn test_mock_mempool_provider_creation() {
        let mempool = MockMempoolProvider::new();
        assert_eq!(mempool.get_mempool_size(), 0);
        assert!(mempool.get_transactions().is_empty());
        let empty_utxo_set = blvm_protocol::UtxoSet::default();
        assert!(mempool
            .get_prioritized_transactions(10, &empty_utxo_set)
            .is_empty());
    }

    #[test]
    fn test_mock_mempool_provider_transaction_management() {
        let mut mempool = MockMempoolProvider::new();

        let tx1 = create_test_transaction(1, 1000);
        let tx2 = create_test_transaction(2, 2000);

        mempool.add_transaction(tx1.clone());
        mempool.add_transaction(tx2.clone());

        assert_eq!(mempool.get_mempool_size(), 2);
        assert_eq!(mempool.get_transactions().len(), 2);

        let empty_utxo_set = blvm_protocol::UtxoSet::default();
        let prioritized = mempool.get_prioritized_transactions(10, &empty_utxo_set);
        assert_eq!(prioritized.len(), 2);

        // Test transaction removal
        let hash = mempool.calculate_tx_hash(&tx1);
        assert!(mempool.remove_transaction(&hash));
        assert_eq!(mempool.get_mempool_size(), 1);

        // Test removal of non-existent transaction
        let fake_hash = [0u8; 32];
        assert!(!mempool.remove_transaction(&fake_hash));
    }

    #[test]
    fn test_mock_mempool_provider_prioritization() {
        let mut mempool = MockMempoolProvider::new();

        // Add transactions with different fee rates
        let tx_low_fee = create_test_transaction(1, 100); // Low fee
        let tx_high_fee = create_test_transaction(2, 5000); // High fee
        let tx_medium_fee = create_test_transaction(3, 1000); // Medium fee

        mempool.add_transaction(tx_low_fee);
        mempool.add_transaction(tx_high_fee);
        mempool.add_transaction(tx_medium_fee);

        let empty_utxo_set = blvm_protocol::UtxoSet::default();
        let prioritized = mempool.get_prioritized_transactions(10, &empty_utxo_set);
        assert_eq!(prioritized.len(), 3);

        // Transactions should be sorted by fee rate (descending)
        // Version 3 (medium fee) should be first, then version 2 (high fee), then version 1 (low fee)
        assert_eq!(prioritized[0].version, 3);
        assert_eq!(prioritized[1].version, 2);
        assert_eq!(prioritized[2].version, 1);
    }

    #[test]
    fn test_mock_mempool_provider_clear() {
        let mut mempool = MockMempoolProvider::new();

        let tx = create_test_transaction(1, 1000);
        mempool.add_transaction(tx);

        assert_eq!(mempool.get_mempool_size(), 1);

        mempool.clear();
        assert_eq!(mempool.get_mempool_size(), 0);
        assert!(mempool.get_transactions().is_empty());
    }

    #[test]
    fn test_mining_coordinator_creation() {
        use std::sync::Arc;
        let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
        let coordinator = MiningCoordinator::new(mempool, None);

        assert!(!coordinator.is_mining_enabled());
        assert_eq!(coordinator.get_mempool_size(), 0);
        assert_eq!(coordinator.mining_engine().get_threads(), 1);
        assert_eq!(
            coordinator.transaction_selector().max_block_size(),
            1_000_000
        );
    }

    #[test]
    fn test_mining_coordinator_with_params() {
        use std::sync::Arc;
        let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
        let coordinator = MiningCoordinator::with_params(mempool, None, 4, 2_000_000, 8_000_000, 5);

        assert_eq!(coordinator.mining_engine().get_threads(), 4);
        assert_eq!(
            coordinator.transaction_selector().max_block_size(),
            2_000_000
        );
        assert_eq!(
            coordinator.transaction_selector().max_block_weight(),
            8_000_000
        );
        assert_eq!(coordinator.transaction_selector().min_fee_rate(), 5);
    }

    #[test]
    fn test_mining_coordinator_enable_disable() {
        use std::sync::Arc;
        let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
        let mut coordinator = MiningCoordinator::new(mempool, None);

        assert!(!coordinator.is_mining_enabled());
        coordinator.enable_mining();
        assert!(coordinator.is_mining_enabled());

        coordinator.disable_mining();
        assert!(!coordinator.is_mining_enabled());
    }

    #[test]
    fn test_mining_coordinator_info() {
        use std::sync::Arc;
        let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
        let coordinator = MiningCoordinator::new(mempool, None);

        let info = coordinator.get_mining_info();
        assert!(!info.enabled);
        assert_eq!(info.threads, 1);
        assert!(!info.has_template);
    }

    #[test]
    fn test_mining_coordinator_statistics() {
        use std::sync::Arc;
        let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
        let coordinator = MiningCoordinator::new(mempool, None);

        let stats = coordinator.get_mining_stats();
        assert_eq!(stats.blocks_mined, 0);
        assert_eq!(stats.total_hashrate, 0.0);
        assert_eq!(stats.average_block_time, 0.0);
        assert!(stats.last_block_time.is_none());
    }

    #[test]
    fn test_mining_coordinator_accessors() {
        use std::sync::Arc;
        let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
        let coordinator = MiningCoordinator::new(mempool, None);

        // Test immutable access
        let engine = coordinator.mining_engine();
        assert_eq!(engine.get_threads(), 1);

        let selector = coordinator.transaction_selector();
        assert_eq!(selector.max_block_size(), 1_000_000);

        // Test mutable access
        let mut coordinator = coordinator;
        let engine_mut = coordinator.mining_engine_mut();
        engine_mut.set_threads(4);
        assert_eq!(coordinator.mining_engine().get_threads(), 4);

        let selector_mut = coordinator.transaction_selector_mut();
        // Test that we can access the selector
        assert_eq!(selector_mut.max_block_size(), 1_000_000);
    }

    #[tokio::test]
    async fn test_mining_coordinator_mempool_operations() {
        use std::sync::Arc;
        // Create mempool and add transaction before wrapping in Arc
        let mut mempool_manager = crate::node::mempool::MempoolManager::new();
        let tx = create_test_transaction(1, 1000);
        let _ = mempool_manager.add_transaction(tx);
        let mempool = Arc::new(mempool_manager);
        let coordinator = MiningCoordinator::new(mempool, None);

        assert_eq!(coordinator.get_mempool_size(), 1);
    }

    #[tokio::test]
    async fn test_mining_coordinator_block_template_generation() {
        use std::sync::Arc;
        // Create mempool and add transaction before wrapping in Arc
        let mut mempool_manager = crate::node::mempool::MempoolManager::new();
        let tx = create_test_transaction(1, 1000);
        let _ = mempool_manager.add_transaction(tx);
        let mempool = Arc::new(mempool_manager);

        let mut coordinator = MiningCoordinator::new(mempool, None);

        let template = coordinator.generate_block_template().await;
        assert!(template.is_ok());

        let block = template.unwrap();
        assert_eq!(block.header.version, 1);
        assert!(!block.transactions.is_empty()); // Should have coinbase + mempool tx
    }

    #[tokio::test]
    async fn test_mining_coordinator_coinbase_creation() {
        use std::sync::Arc;
        let mempool = Arc::new(crate::node::mempool::MempoolManager::new());
        let coordinator = MiningCoordinator::new(mempool, None);

        // Test coinbase creation with no transactions (subsidy only)
        let empty_utxo_set = blvm_protocol::UtxoSet::default();
        let coinbase = coordinator
            .create_coinbase_transaction(0, &[], &empty_utxo_set)
            .await;
        assert!(coinbase.is_ok());

        let tx = coinbase.unwrap();
        assert_eq!(tx.version, 1);
        // Bitcoin coinbase: exactly one input, null prevout + 0xffffffff index (BIP30/BIP34).
        assert_eq!(tx.inputs.len(), 1);
        assert_eq!(tx.inputs[0].prevout.hash, [0u8; 32]);
        assert_eq!(tx.inputs[0].prevout.index, 0xffff_ffff);
        assert_eq!(tx.outputs.len(), 1);
        // Should be 50 BTC (subsidy) at height 0, with no fees
        assert_eq!(tx.outputs[0].value, 5000000000); // 50 BTC
        assert_eq!(tx.lock_time, 0);
    }

    // Helper functions for tests
    fn create_test_transaction(version: i32, output_value: u64) -> Transaction {
        use blvm_protocol::{OutPoint, TransactionInput};
        Transaction {
            version: version as u64,
            inputs: blvm_protocol::tx_inputs![TransactionInput {
                prevout: OutPoint {
                    hash: [0u8; 32],
                    index: 0,
                },
                script_sig: vec![
                    blvm_protocol::opcodes::OP_DUP,
                    blvm_protocol::opcodes::OP_HASH160,
                    blvm_protocol::opcodes::PUSH_20_BYTES,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    blvm_protocol::opcodes::OP_EQUALVERIFY,
                    blvm_protocol::opcodes::OP_CHECKSIG,
                ],
                sequence: 0xffffffff,
            }],
            outputs: blvm_protocol::tx_outputs![TransactionOutput {
                value: output_value as i64,
                script_pubkey: vec![
                    blvm_protocol::opcodes::OP_DUP,
                    blvm_protocol::opcodes::OP_HASH160,
                    blvm_protocol::opcodes::PUSH_20_BYTES,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    blvm_protocol::opcodes::OP_EQUALVERIFY,
                    blvm_protocol::opcodes::OP_CHECKSIG,
                ],
            }],
            lock_time: 0,
        }
    }

    fn create_test_block() -> Block {
        Block {
            header: BlockHeader {
                version: 1,
                prev_block_hash: [0u8; 32],
                merkle_root: [0u8; 32],
                timestamp: 1231006505,
                bits: 0x1d00ffff,
                nonce: 0,
            },
            transactions: vec![create_test_transaction(1, 1000)].into_boxed_slice(),
        }
    }
}
