//! Block types for the Synor BlockDAG.
//!
//! Unlike traditional blockchains with a single parent, Synor blocks
//! can reference multiple parent blocks, forming a DAG structure.

use crate::{Amount, BlueScore, Hash256, Timestamp, Transaction};
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use std::fmt;

/// Block identifier (hash of the block header).
pub type BlockId = Hash256;

/// Maximum number of parent blocks.
pub const MAX_PARENTS: usize = 64;

/// Maximum number of transactions per block.
pub const MAX_TRANSACTIONS: usize = 10_000;

/// Maximum block size in bytes.
pub const MAX_BLOCK_SIZE: usize = 1_000_000; // 1 MB

/// Block header - contains metadata about the block.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct BlockHeader {
    /// Block version.
    pub version: u32,

    /// Parent block hashes (DAG structure - multiple parents allowed).
    /// In GHOSTDAG, a block references all tips it knows about.
    pub parents: Vec<BlockId>,

    /// Merkle root of transactions.
    pub merkle_root: Hash256,

    /// Merkle root of accepted transaction IDs (from merged blocks).
    pub accepted_id_merkle_root: Hash256,

    /// UTXO commitment (hash of UTXO set state).
    pub utxo_commitment: Hash256,

    /// Block timestamp (milliseconds since Unix epoch).
    pub timestamp: Timestamp,

    /// Difficulty target (compact representation).
    pub bits: u32,

    /// Nonce for proof of work.
    pub nonce: u64,

    /// DAA (Difficulty Adjustment Algorithm) score.
    pub daa_score: u64,

    /// Blue score (GHOSTDAG ordering metric).
    pub blue_score: BlueScore,

    /// Blue work (cumulative proof-of-work from blue blocks).
    pub blue_work: Hash256,

    /// Pruning point (for DAG pruning).
    pub pruning_point: BlockId,
}

impl BlockHeader {
    /// Computes the block ID (hash of the header).
    pub fn block_id(&self) -> BlockId {
        let serialized = borsh::to_vec(self).expect("Serialization should not fail");
        Hash256::double_blake3(&serialized)
    }

    /// Computes the hash for proof-of-work verification.
    pub fn pow_hash(&self) -> Hash256 {
        // For kHeavyHash, we would use a different computation
        // For now, use double BLAKE3
        let pre_pow = self.pre_pow_hash();
        Hash256::double_blake3(pre_pow.as_bytes())
    }

    /// Computes the pre-PoW hash (excludes nonce).
    pub fn pre_pow_hash(&self) -> Hash256 {
        let mut header_copy = self.clone();
        header_copy.nonce = 0;
        let serialized = borsh::to_vec(&header_copy).expect("Serialization should not fail");
        Hash256::blake3(&serialized)
    }

    /// Returns the difficulty target as a 256-bit value.
    pub fn target(&self) -> Hash256 {
        bits_to_target(self.bits)
    }

    /// Checks if the PoW is valid (hash < target).
    pub fn check_pow(&self) -> bool {
        let hash = self.pow_hash();
        let target = self.target();
        hash.as_bytes() <= target.as_bytes()
    }

    /// Returns true if this is the genesis block.
    pub fn is_genesis(&self) -> bool {
        self.parents.is_empty() || self.parents.iter().all(|p| p.is_zero())
    }

    /// Returns the number of parent blocks.
    pub fn parent_count(&self) -> usize {
        self.parents.len()
    }
}

impl Default for BlockHeader {
    fn default() -> Self {
        BlockHeader {
            version: 1,
            parents: Vec::new(),
            merkle_root: Hash256::ZERO,
            accepted_id_merkle_root: Hash256::ZERO,
            utxo_commitment: Hash256::ZERO,
            timestamp: Timestamp::now(),
            bits: 0x1d00ffff, // Bitcoin-style initial difficulty
            nonce: 0,
            daa_score: 0,
            blue_score: BlueScore::new(0),
            blue_work: Hash256::ZERO,
            pruning_point: Hash256::ZERO,
        }
    }
}

impl fmt::Display for BlockHeader {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "BlockHeader {{ id: {}, parents: {}, blue_score: {}, daa: {} }}",
            self.block_id(),
            self.parents.len(),
            self.blue_score.value(),
            self.daa_score
        )
    }
}

/// Block body - contains the transactions.
#[derive(
    Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub struct BlockBody {
    /// Transactions in this block.
    pub transactions: Vec<Transaction>,
}

impl BlockBody {
    /// Creates a new block body with the given transactions.
    pub fn new(transactions: Vec<Transaction>) -> Self {
        BlockBody { transactions }
    }

    /// Returns the number of transactions.
    pub fn transaction_count(&self) -> usize {
        self.transactions.len()
    }

    /// Computes the Merkle root of transactions.
    pub fn merkle_root(&self) -> Hash256 {
        let tx_hashes: Vec<Hash256> = self.transactions.iter().map(|tx| tx.txid()).collect();
        Hash256::merkle_root(&tx_hashes)
    }

    /// Returns the total size of transactions in bytes.
    pub fn size(&self) -> usize {
        self.transactions
            .iter()
            .map(|tx| borsh::to_vec(tx).map(|v| v.len()).unwrap_or(0))
            .sum()
    }
}

/// A complete block (header + body).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct Block {
    /// Block header.
    pub header: BlockHeader,
    /// Block body.
    pub body: BlockBody,
}

impl Block {
    /// Creates a new block.
    pub fn new(header: BlockHeader, body: BlockBody) -> Self {
        Block { header, body }
    }

    /// Creates a new block with the given parent and transactions.
    pub fn with_transactions(
        parents: Vec<BlockId>,
        transactions: Vec<Transaction>,
        timestamp: Timestamp,
        bits: u32,
        daa_score: u64,
        blue_score: BlueScore,
    ) -> Self {
        let body = BlockBody::new(transactions);
        let merkle_root = body.merkle_root();

        let header = BlockHeader {
            version: 1,
            parents,
            merkle_root,
            accepted_id_merkle_root: Hash256::ZERO,
            utxo_commitment: Hash256::ZERO,
            timestamp,
            bits,
            nonce: 0,
            daa_score,
            blue_score,
            blue_work: Hash256::ZERO,
            pruning_point: Hash256::ZERO,
        };

        Block { header, body }
    }

    /// Returns the block ID.
    pub fn id(&self) -> BlockId {
        self.header.block_id()
    }

    /// Returns the parent block IDs.
    pub fn parents(&self) -> &[BlockId] {
        &self.header.parents
    }

    /// Returns the transactions.
    pub fn transactions(&self) -> &[Transaction] {
        &self.body.transactions
    }

    /// Returns true if this is the genesis block.
    pub fn is_genesis(&self) -> bool {
        self.header.is_genesis()
    }

    /// Returns the coinbase transaction if present.
    pub fn coinbase(&self) -> Option<&Transaction> {
        self.body.transactions.first().filter(|tx| tx.is_coinbase())
    }

    /// Returns the total block size in bytes.
    pub fn size(&self) -> usize {
        let header_size = borsh::to_vec(&self.header).map(|v| v.len()).unwrap_or(0);
        let body_size = self.body.size();
        header_size + body_size
    }

    /// Validates basic block structure (not consensus rules).
    pub fn validate_structure(&self) -> Result<(), BlockError> {
        // Check parent count
        if self.header.parents.len() > MAX_PARENTS && !self.is_genesis() {
            return Err(BlockError::TooManyParents {
                count: self.header.parents.len(),
                max: MAX_PARENTS,
            });
        }

        // Check transaction count
        if self.body.transactions.len() > MAX_TRANSACTIONS {
            return Err(BlockError::TooManyTransactions {
                count: self.body.transactions.len(),
                max: MAX_TRANSACTIONS,
            });
        }

        // Check block size
        let size = self.size();
        if size > MAX_BLOCK_SIZE {
            return Err(BlockError::BlockTooLarge {
                size,
                max: MAX_BLOCK_SIZE,
            });
        }

        // Verify merkle root
        let computed_root = self.body.merkle_root();
        if self.header.merkle_root != computed_root {
            return Err(BlockError::InvalidMerkleRoot);
        }

        // First transaction must be coinbase (except for special blocks)
        if !self.body.transactions.is_empty() {
            if !self.body.transactions[0].is_coinbase() {
                return Err(BlockError::MissingCoinbase);
            }

            // Only first transaction can be coinbase
            for (i, tx) in self.body.transactions.iter().enumerate().skip(1) {
                if tx.is_coinbase() {
                    return Err(BlockError::MultipleCoinbase { index: i });
                }
            }
        }

        Ok(())
    }
}

impl fmt::Display for Block {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Block {{ id: {}, parents: {}, txs: {}, blue_score: {} }}",
            self.id(),
            self.header.parents.len(),
            self.body.transactions.len(),
            self.header.blue_score.value()
        )
    }
}

/// Block template for mining.
#[derive(Debug, Clone)]
pub struct BlockTemplate {
    /// Block being mined.
    pub block: Block,
    /// Transaction fees (for coinbase reward calculation).
    pub fees: Amount,
    /// Selected parent (the one determining blue score).
    pub selected_parent: BlockId,
}

impl BlockTemplate {
    /// Updates the nonce and checks PoW.
    pub fn try_nonce(&mut self, nonce: u64) -> bool {
        self.block.header.nonce = nonce;
        self.block.header.check_pow()
    }

    /// Increments the nonce.
    pub fn increment_nonce(&mut self) {
        self.block.header.nonce = self.block.header.nonce.wrapping_add(1);
    }
}

/// Errors that can occur with blocks.
#[derive(Debug, thiserror::Error)]
pub enum BlockError {
    #[error("Block has too many parents: {count} > {max}")]
    TooManyParents { count: usize, max: usize },

    #[error("Block has too many transactions: {count} > {max}")]
    TooManyTransactions { count: usize, max: usize },

    #[error("Block too large: {size} > {max} bytes")]
    BlockTooLarge { size: usize, max: usize },

    #[error("Invalid merkle root")]
    InvalidMerkleRoot,

    #[error("Missing coinbase transaction")]
    MissingCoinbase,

    #[error("Multiple coinbase transactions (index {index})")]
    MultipleCoinbase { index: usize },

    #[error("Invalid proof of work")]
    InvalidPow,

    #[error("Block timestamp too far in future")]
    TimestampTooFarInFuture,

    #[error("Parent block not found: {0}")]
    ParentNotFound(BlockId),
}

/// Converts compact difficulty bits to full 256-bit target.
fn bits_to_target(bits: u32) -> Hash256 {
    let exponent = (bits >> 24) as usize;
    let mantissa = bits & 0x00ffffff;

    let mut target = [0u8; 32];

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

    Hash256::from_bytes(target)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::transaction::{ScriptPubKey, TxOutput};

    fn create_coinbase() -> Transaction {
        Transaction::coinbase(
            vec![TxOutput::new(
                Amount::from_synor(500),
                ScriptPubKey::p2pkh(&[0u8; 32]),
            )],
            b"Test block".to_vec(),
        )
    }

    #[test]
    fn test_genesis_block() {
        let header = BlockHeader::default();
        let body = BlockBody::new(vec![create_coinbase()]);
        let block = Block::new(header, body);

        assert!(block.is_genesis());
    }

    #[test]
    fn test_block_with_parents() {
        let parent1 = Hash256::blake3(b"parent1");
        let parent2 = Hash256::blake3(b"parent2");

        let block = Block::with_transactions(
            vec![parent1, parent2],
            vec![create_coinbase()],
            Timestamp::now(),
            0x1d00ffff,
            1,
            BlueScore::new(1),
        );

        assert!(!block.is_genesis());
        assert_eq!(block.parents().len(), 2);
    }

    #[test]
    fn test_merkle_root() {
        let tx1 = create_coinbase();
        let tx2 = Transaction::new(vec![], vec![]);

        let body = BlockBody::new(vec![tx1.clone(), tx2.clone()]);
        let root1 = body.merkle_root();

        // Same transactions should produce same root
        let body2 = BlockBody::new(vec![tx1, tx2]);
        let root2 = body2.merkle_root();

        assert_eq!(root1, root2);
    }

    #[test]
    fn test_block_validation() {
        let block = Block::with_transactions(
            vec![Hash256::blake3(b"parent")],
            vec![create_coinbase()],
            Timestamp::now(),
            0x1d00ffff,
            1,
            BlueScore::new(1),
        );

        assert!(block.validate_structure().is_ok());
    }

    #[test]
    fn test_block_id_deterministic() {
        let block = Block::with_transactions(
            vec![Hash256::blake3(b"parent")],
            vec![create_coinbase()],
            Timestamp::from_millis(1000),
            0x1d00ffff,
            1,
            BlueScore::new(1),
        );

        let id1 = block.id();
        let id2 = block.id();
        assert_eq!(id1, id2);
    }
}
