//! Block template creation for mining.
//!
//! Templates provide miners with all the information needed to construct
//! and mine a valid block.

use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use synor_types::{Address, Hash256};

use crate::{MiningError, Target};

/// A block template for mining.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BlockTemplate {
    /// Template identifier (for tracking).
    pub id: u64,
    /// Version of the template format.
    pub version: u32,
    /// Parent block hashes (DAG structure).
    pub parent_hashes: Vec<Hash256>,
    /// Selected parent (for GHOSTDAG).
    pub selected_parent: Hash256,
    /// GHOSTDAG blue score.
    pub blue_score: u64,
    /// Bits representation of target.
    pub bits: u32,
    /// Target for mining.
    pub target: [u8; 32],
    /// Block timestamp.
    pub timestamp: u64,
    /// Coinbase transaction data.
    pub coinbase_data: CoinbaseData,
    /// Transactions to include.
    pub transactions: Vec<TemplateTransaction>,
    /// Total fees from transactions.
    pub total_fees: u64,
    /// Block reward (subsidy).
    pub block_reward: u64,
    /// Merkle root of transactions.
    pub tx_merkle_root: Hash256,
    /// Accepted ID merkle root.
    pub accepted_id_merkle_root: Hash256,
    /// UTXO commitment.
    pub utxo_commitment: Hash256,
    /// Header bytes for mining (without nonce).
    pub header_data: Vec<u8>,
    /// Extra nonce space for pool mining.
    pub extra_nonce_range: (u64, u64),
}

impl BlockTemplate {
    /// Creates the header bytes for hashing.
    pub fn header_for_mining(&self) -> Vec<u8> {
        self.header_data.clone()
    }

    /// Creates header with a specific extra nonce.
    pub fn header_with_extra_nonce(&self, extra_nonce: u64) -> Vec<u8> {
        let mut header = self.header_data.clone();
        // Insert extra nonce at designated position (after merkle root)
        if header.len() >= 40 {
            let nonce_bytes = extra_nonce.to_le_bytes();
            header[32..40].copy_from_slice(&nonce_bytes);
        }
        header
    }

    /// Gets the target.
    pub fn get_target(&self) -> Target {
        Target::from_bytes(self.target)
    }

    /// Validates the template is well-formed.
    pub fn validate(&self) -> Result<(), MiningError> {
        if self.parent_hashes.is_empty() {
            return Err(MiningError::InvalidTemplate("No parent hashes".into()));
        }

        if !self.parent_hashes.contains(&self.selected_parent) {
            return Err(MiningError::InvalidTemplate(
                "Selected parent not in parent set".into(),
            ));
        }

        if self.header_data.is_empty() {
            return Err(MiningError::InvalidTemplate("Empty header data".into()));
        }

        Ok(())
    }

    /// Returns expected total payout (reward + fees).
    pub fn total_payout(&self) -> u64 {
        self.block_reward + self.total_fees
    }
}

/// Coinbase transaction data.
#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct CoinbaseData {
    /// Miner address to receive rewards.
    pub miner_address: Address,
    /// Block height for coinbase maturity.
    pub block_height: u64,
    /// Extra data (pool name, etc.).
    pub extra_data: Vec<u8>,
    /// Script for payout.
    pub script: Vec<u8>,
}

impl CoinbaseData {
    /// Creates new coinbase data.
    pub fn new(miner_address: Address, block_height: u64) -> Self {
        CoinbaseData {
            miner_address,
            block_height,
            extra_data: Vec::new(),
            script: Vec::new(),
        }
    }

    /// Adds extra data (e.g., pool name).
    pub fn with_extra_data(mut self, data: Vec<u8>) -> Self {
        self.extra_data = data;
        self
    }
}

/// Transaction in a block template.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TemplateTransaction {
    /// Transaction ID.
    pub txid: Hash256,
    /// Serialized transaction data.
    pub data: Vec<u8>,
    /// Fee paid by this transaction.
    pub fee: u64,
    /// Transaction mass (weight).
    pub mass: u64,
}

/// Builder for block templates.
pub struct BlockTemplateBuilder {
    /// Template version.
    version: u32,
    /// Parent block hashes.
    parents: Vec<Hash256>,
    /// Selected parent.
    selected_parent: Option<Hash256>,
    /// Blue score.
    blue_score: u64,
    /// Difficulty bits.
    bits: u32,
    /// Timestamp.
    timestamp: u64,
    /// Coinbase data.
    coinbase: Option<CoinbaseData>,
    /// Transactions.
    transactions: Vec<TemplateTransaction>,
    /// Block reward.
    reward: u64,
}

impl BlockTemplateBuilder {
    /// Creates a new template builder.
    pub fn new() -> Self {
        BlockTemplateBuilder {
            version: 1,
            parents: Vec::new(),
            selected_parent: None,
            blue_score: 0,
            bits: 0x1d00ffff,
            timestamp: 0,
            coinbase: None,
            transactions: Vec::new(),
            reward: 0,
        }
    }

    /// Sets the template version.
    pub fn version(mut self, version: u32) -> Self {
        self.version = version;
        self
    }

    /// Adds a parent hash.
    pub fn add_parent(mut self, hash: Hash256) -> Self {
        self.parents.push(hash);
        self
    }

    /// Sets parent hashes.
    pub fn parents(mut self, parents: Vec<Hash256>) -> Self {
        self.parents = parents;
        self
    }

    /// Sets the selected parent.
    pub fn selected_parent(mut self, parent: Hash256) -> Self {
        self.selected_parent = Some(parent);
        self
    }

    /// Sets the blue score.
    pub fn blue_score(mut self, score: u64) -> Self {
        self.blue_score = score;
        self
    }

    /// Sets difficulty bits.
    pub fn bits(mut self, bits: u32) -> Self {
        self.bits = bits;
        self
    }

    /// Sets timestamp.
    pub fn timestamp(mut self, ts: u64) -> Self {
        self.timestamp = ts;
        self
    }

    /// Sets coinbase data.
    pub fn coinbase(mut self, coinbase: CoinbaseData) -> Self {
        self.coinbase = Some(coinbase);
        self
    }

    /// Adds a transaction.
    pub fn add_transaction(mut self, tx: TemplateTransaction) -> Self {
        self.transactions.push(tx);
        self
    }

    /// Sets the block reward.
    pub fn reward(mut self, reward: u64) -> Self {
        self.reward = reward;
        self
    }

    /// Builds the block template.
    pub fn build(mut self, template_id: u64) -> Result<BlockTemplate, MiningError> {
        let selected = self
            .selected_parent
            .or_else(|| self.parents.first().copied())
            .ok_or_else(|| MiningError::InvalidTemplate("No parents".into()))?;

        // Build header data first before taking coinbase
        let header_data = self.build_header(&selected)?;

        let coinbase = self
            .coinbase
            .take()
            .ok_or_else(|| MiningError::InvalidTemplate("No coinbase".into()))?;

        // Calculate fees
        let total_fees: u64 = self.transactions.iter().map(|tx| tx.fee).sum();

        // Calculate merkle roots (simplified)
        let tx_merkle_root = self.calculate_tx_merkle_root();
        let accepted_id_merkle_root = Hash256::default();
        let utxo_commitment = Hash256::default();

        Ok(BlockTemplate {
            id: template_id,
            version: self.version,
            parent_hashes: self.parents,
            selected_parent: selected,
            blue_score: self.blue_score,
            bits: self.bits,
            target: Target::from_bits(self.bits).0,
            timestamp: self.timestamp,
            coinbase_data: coinbase,
            transactions: self.transactions,
            total_fees,
            block_reward: self.reward,
            tx_merkle_root,
            accepted_id_merkle_root,
            utxo_commitment,
            header_data,
            extra_nonce_range: (0, u64::MAX),
        })
    }

    fn build_header(&self, selected_parent: &Hash256) -> Result<Vec<u8>, MiningError> {
        // Block header format:
        // - Version: 4 bytes
        // - Parents hash (merkle of parents): 32 bytes
        // - Extra nonce space: 8 bytes
        // - TX merkle root: 32 bytes
        // - Timestamp: 8 bytes
        // - Bits: 4 bytes
        // - Blue score: 8 bytes
        // Total: 96 bytes (nonce added during mining)

        let mut header = Vec::with_capacity(96);

        // Version
        header.extend_from_slice(&self.version.to_le_bytes());

        // Parents hash (simplified: hash of selected parent)
        header.extend_from_slice(selected_parent.as_bytes());

        // Extra nonce space (8 bytes, filled during mining)
        header.extend_from_slice(&[0u8; 8]);

        // TX merkle root
        let merkle = self.calculate_tx_merkle_root();
        header.extend_from_slice(merkle.as_bytes());

        // Timestamp
        header.extend_from_slice(&self.timestamp.to_le_bytes());

        // Bits
        header.extend_from_slice(&self.bits.to_le_bytes());

        // Blue score
        header.extend_from_slice(&self.blue_score.to_le_bytes());

        Ok(header)
    }

    fn calculate_tx_merkle_root(&self) -> Hash256 {
        if self.transactions.is_empty() {
            return Hash256::default();
        }

        // Collect all txids
        let txids: Vec<&[u8; 32]> = self
            .transactions
            .iter()
            .map(|tx| tx.txid.as_bytes())
            .collect();

        // Simple merkle tree
        merkle_root(&txids)
    }
}

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

/// Builds a coinbase transaction.
pub struct CoinbaseBuilder {
    miner_address: Address,
    block_height: u64,
    extra_data: Vec<u8>,
    block_reward: u64,
    fees: u64,
}

impl CoinbaseBuilder {
    /// Creates a new coinbase builder.
    pub fn new(miner_address: Address, block_height: u64) -> Self {
        CoinbaseBuilder {
            miner_address,
            block_height,
            extra_data: Vec::new(),
            block_reward: 0,
            fees: 0,
        }
    }

    /// Sets extra data (pool name, etc.).
    pub fn extra_data(mut self, data: Vec<u8>) -> Self {
        self.extra_data = data;
        self
    }

    /// Sets the block reward.
    pub fn reward(mut self, reward: u64) -> Self {
        self.block_reward = reward;
        self
    }

    /// Sets the total fees.
    pub fn fees(mut self, fees: u64) -> Self {
        self.fees = fees;
        self
    }

    /// Builds the coinbase data.
    pub fn build(self) -> CoinbaseData {
        let script = self.build_script();
        CoinbaseData {
            miner_address: self.miner_address,
            block_height: self.block_height,
            extra_data: self.extra_data,
            script,
        }
    }

    fn build_script(&self) -> Vec<u8> {
        // Simplified coinbase script
        // In production, this would be a proper output script
        let mut script = Vec::new();

        // Height push (BIP34)
        let height_bytes = self.block_height.to_le_bytes();
        let len = height_bytes.iter().rposition(|&b| b != 0).unwrap_or(0) + 1;
        script.push(len as u8);
        script.extend_from_slice(&height_bytes[..len]);

        // Extra data
        if !self.extra_data.is_empty() {
            script.extend_from_slice(&self.extra_data);
        }

        script
    }

    /// Total payout (reward + fees).
    pub fn total_payout(&self) -> u64 {
        self.block_reward + self.fees
    }
}

/// Computes merkle root from a list of hashes.
fn merkle_root(hashes: &[&[u8; 32]]) -> Hash256 {
    if hashes.is_empty() {
        return Hash256::default();
    }

    if hashes.len() == 1 {
        return Hash256::from_bytes(*hashes[0]);
    }

    let mut current: Vec<[u8; 32]> = hashes.iter().map(|h| **h).collect();

    while current.len() > 1 {
        let mut next = Vec::with_capacity(current.len().div_ceil(2));

        for chunk in current.chunks(2) {
            let mut data = [0u8; 64];
            data[..32].copy_from_slice(&chunk[0]);
            if chunk.len() > 1 {
                data[32..].copy_from_slice(&chunk[1]);
            } else {
                data[32..].copy_from_slice(&chunk[0]); // Duplicate odd element
            }

            let hash: [u8; 32] = blake3::hash(&data).into();
            next.push(hash);
        }

        current = next;
    }

    Hash256::from_bytes(current[0])
}

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

    fn test_address() -> Address {
        Address::from_ed25519_pubkey(Network::Mainnet, &[0x42; 32])
    }

    fn test_hash(n: u8) -> Hash256 {
        let mut bytes = [0u8; 32];
        bytes[0] = n;
        Hash256::from_bytes(bytes)
    }

    #[test]
    fn test_coinbase_builder() {
        let coinbase = CoinbaseBuilder::new(test_address(), 1000)
            .extra_data(b"Synor Pool".to_vec())
            .reward(500_00000000)
            .fees(1_00000000)
            .build();

        assert_eq!(coinbase.block_height, 1000);
        assert!(!coinbase.script.is_empty());
    }

    #[test]
    fn test_block_template_builder() {
        let coinbase = CoinbaseBuilder::new(test_address(), 1)
            .reward(500_00000000)
            .build();

        let template = BlockTemplateBuilder::new()
            .version(1)
            .add_parent(test_hash(1))
            .timestamp(1234567890)
            .bits(0x1d00ffff)
            .blue_score(100)
            .coinbase(coinbase)
            .reward(500_00000000)
            .build(1)
            .unwrap();

        assert_eq!(template.id, 1);
        assert!(!template.header_data.is_empty());
        assert!(template.validate().is_ok());
    }

    #[test]
    fn test_merkle_root() {
        let h1 = [1u8; 32];
        let h2 = [2u8; 32];

        let root1 = merkle_root(&[&h1]);
        assert_eq!(*root1.as_bytes(), h1);

        let root2 = merkle_root(&[&h1, &h2]);
        assert_ne!(*root2.as_bytes(), h1);
        assert_ne!(*root2.as_bytes(), h2);
    }
}
