//! Genesis block and chain configuration for Synor.
//!
//! This module defines the genesis block for different networks
//! and provides chain configuration parameters.

use synor_types::{
    block::BlockBody,
    transaction::{Outpoint, ScriptPubKey, ScriptType, SubnetworkId},
    Amount, Block, BlockHeader, BlueScore, Hash256, Network, Timestamp, Transaction, TxInput,
    TxOutput,
};

/// Chain configuration parameters.
#[derive(Clone, Debug)]
pub struct ChainConfig {
    /// Network identifier.
    pub network: Network,
    /// Genesis block.
    pub genesis: Block,
    /// Genesis hash.
    pub genesis_hash: Hash256,
    /// Target block time in milliseconds.
    pub target_block_time_ms: u64,
    /// GHOSTDAG K parameter.
    pub ghostdag_k: u64,
    /// Maximum block parents.
    pub max_block_parents: u64,
    /// Maximum block mass.
    pub max_block_mass: u64,
    /// Coinbase maturity.
    pub coinbase_maturity: u64,
    /// Finality depth.
    pub finality_depth: u64,
    /// Pruning depth.
    pub pruning_depth: u64,
    /// Initial difficulty bits.
    pub initial_difficulty: u32,
    /// Halving interval in blocks.
    pub halving_interval: u64,
    /// Initial block reward in sompi.
    pub initial_reward: u64,
    /// DNS seeds.
    pub dns_seeds: Vec<String>,
    /// Bootstrap nodes.
    pub bootstrap_nodes: Vec<String>,
    /// BIP32 coin type.
    pub bip32_coin_type: u32,
    /// Address prefix (for bech32).
    pub address_prefix: String,
}

impl ChainConfig {
    /// Creates mainnet configuration.
    pub fn mainnet() -> Self {
        let genesis = create_mainnet_genesis();
        let genesis_hash = genesis.header.block_id();

        ChainConfig {
            network: Network::Mainnet,
            genesis,
            genesis_hash,
            target_block_time_ms: 100, // 10 bps
            ghostdag_k: 18,
            max_block_parents: 64,
            max_block_mass: 500_000,
            coinbase_maturity: 100,
            finality_depth: 86_400,            // ~24 hours
            pruning_depth: 288_000,            // ~8 hours of data
            initial_difficulty: 0x1d00ffff,    // Bitcoin-style initial difficulty
            halving_interval: 315_360_000,     // ~1 year at 10 bps
            initial_reward: 500 * 100_000_000, // 500 SYNOR
            dns_seeds: vec![
                "seed1.synor.cc".to_string(),
                "seed2.synor.cc".to_string(),
                "seed3.synor.cc".to_string(),
            ],
            bootstrap_nodes: vec![],
            bip32_coin_type: 0x5359, // "SY" in hex
            address_prefix: "synor".to_string(),
        }
    }

    /// Creates testnet configuration.
    pub fn testnet() -> Self {
        let genesis = create_testnet_genesis();
        let genesis_hash = genesis.header.block_id();

        ChainConfig {
            network: Network::Testnet,
            genesis,
            genesis_hash,
            target_block_time_ms: 100,
            ghostdag_k: 18,
            max_block_parents: 64,
            max_block_mass: 500_000,
            coinbase_maturity: 10, // Lower for testing
            finality_depth: 1000,  // Lower for testing
            pruning_depth: 5000,
            initial_difficulty: 0x1f00ffff, // Lower difficulty
            halving_interval: 10_000,       // Quick halvings for testing
            initial_reward: 500 * 100_000_000,
            dns_seeds: vec![
                "testnet-seed1.synor.cc".to_string(),
                "testnet-seed2.synor.cc".to_string(),
            ],
            bootstrap_nodes: vec![],
            bip32_coin_type: 0x5359,
            address_prefix: "tsynor".to_string(),
        }
    }

    /// Creates devnet configuration.
    pub fn devnet() -> Self {
        let genesis = create_devnet_genesis();
        let genesis_hash = genesis.header.block_id();

        ChainConfig {
            network: Network::Devnet,
            genesis,
            genesis_hash,
            target_block_time_ms: 1000, // 1 second blocks for dev
            ghostdag_k: 3,              // Smaller K for dev
            max_block_parents: 10,
            max_block_mass: 100_000,
            coinbase_maturity: 1,
            finality_depth: 10,
            pruning_depth: 100,
            initial_difficulty: 0x207fffff, // Minimum difficulty
            halving_interval: 100,
            initial_reward: 1000 * 100_000_000, // Higher reward for dev
            dns_seeds: vec![],
            bootstrap_nodes: vec!["/ip4/127.0.0.1/tcp/16511".to_string()],
            bip32_coin_type: 0x5359,
            address_prefix: "dsynor".to_string(),
        }
    }

    /// Returns the configuration for a network.
    pub fn for_network(network: Network) -> Self {
        match network {
            Network::Mainnet => Self::mainnet(),
            Network::Testnet => Self::testnet(),
            Network::Devnet => Self::devnet(),
        }
    }

    /// Validates a block against chain rules.
    pub fn validate_block_header(
        &self,
        header: &BlockHeader,
        _parent_blue_score: u64,
    ) -> Result<(), GenesisError> {
        // Check timestamp is reasonable
        let now_ms = Timestamp::now().as_millis();
        let max_future = 2 * 60 * 60 * 1000; // 2 hours

        if header.timestamp.as_millis() > now_ms + max_future {
            return Err(GenesisError::TimestampTooFar);
        }

        // Check version
        if header.version == 0 {
            return Err(GenesisError::InvalidVersion);
        }

        // Check number of parents
        if header.parents.len() > self.max_block_parents as usize {
            return Err(GenesisError::TooManyParents);
        }

        Ok(())
    }
}

/// Genesis-related errors.
#[derive(Debug, thiserror::Error)]
pub enum GenesisError {
    #[error("Invalid genesis block")]
    InvalidGenesis,

    #[error("Genesis hash mismatch")]
    HashMismatch,

    #[error("Invalid block version")]
    InvalidVersion,

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

    #[error("Too many parent blocks")]
    TooManyParents,
}

/// Creates the mainnet genesis block.
fn create_mainnet_genesis() -> Block {
    // Genesis timestamp: 2025-01-01 00:00:00 UTC
    let genesis_timestamp = Timestamp::from_millis(1735689600000);

    // Genesis message embedded in coinbase
    let genesis_message =
        b"Synor Genesis - The Dawn of Quantum-Secure Decentralized Computing - 2025";

    // Create coinbase transaction
    let coinbase_tx = create_coinbase_transaction(
        genesis_message,
        Amount::from_synor(500),
        &genesis_allocation_mainnet(),
    );

    let txid = coinbase_tx.txid();

    // Create genesis header
    let header = BlockHeader {
        version: 1,
        parents: vec![], // Genesis has no parents
        merkle_root: txid,
        accepted_id_merkle_root: Hash256::ZERO,
        utxo_commitment: Hash256::ZERO,
        timestamp: genesis_timestamp,
        bits: 0x1d00ffff,
        nonce: 0,
        daa_score: 0,
        blue_work: Hash256::ZERO,
        blue_score: BlueScore::new(0),
        pruning_point: Hash256::ZERO,
    };

    let body = BlockBody::new(vec![coinbase_tx]);
    Block::new(header, body)
}

/// Creates the testnet genesis block.
fn create_testnet_genesis() -> Block {
    let genesis_timestamp = Timestamp::from_millis(1735689600000);

    let genesis_message = b"Synor Testnet Genesis - Testing the Future";

    let coinbase_tx = create_coinbase_transaction(
        genesis_message,
        Amount::from_synor(500),
        &genesis_allocation_testnet(),
    );

    let txid = coinbase_tx.txid();

    let header = BlockHeader {
        version: 1,
        parents: vec![],
        merkle_root: txid,
        accepted_id_merkle_root: Hash256::ZERO,
        utxo_commitment: Hash256::ZERO,
        timestamp: genesis_timestamp,
        bits: 0x1f00ffff,
        nonce: 0,
        daa_score: 0,
        blue_work: Hash256::ZERO,
        blue_score: BlueScore::new(0),
        pruning_point: Hash256::ZERO,
    };

    let body = BlockBody::new(vec![coinbase_tx]);
    Block::new(header, body)
}

/// Creates the devnet genesis block.
fn create_devnet_genesis() -> Block {
    let genesis_timestamp = Timestamp::now();

    let genesis_message = b"Synor Devnet - Local Development";

    let coinbase_tx = create_coinbase_transaction(
        genesis_message,
        Amount::from_synor(1000),
        &genesis_allocation_devnet(),
    );

    let txid = coinbase_tx.txid();

    let header = BlockHeader {
        version: 1,
        parents: vec![],
        merkle_root: txid,
        accepted_id_merkle_root: Hash256::ZERO,
        utxo_commitment: Hash256::ZERO,
        timestamp: genesis_timestamp,
        bits: 0x207fffff,
        nonce: 0,
        daa_score: 0,
        blue_work: Hash256::ZERO,
        blue_score: BlueScore::new(0),
        pruning_point: Hash256::ZERO,
    };

    let body = BlockBody::new(vec![coinbase_tx]);
    Block::new(header, body)
}

/// Genesis allocation entry.
#[derive(Clone, Debug)]
pub struct GenesisAllocation {
    /// Recipient address (as string for genesis, actual address derived later).
    pub address_hash: Hash256,
    /// Amount to allocate.
    pub amount: Amount,
    /// Description/purpose.
    pub description: String,
}

/// Returns mainnet genesis allocations.
fn genesis_allocation_mainnet() -> Vec<GenesisAllocation> {
    vec![
        // Development fund (15%)
        GenesisAllocation {
            address_hash: Hash256::from([0x01; 32]),
            amount: Amount::from_synor(10_500_000),
            description: "Development Fund".to_string(),
        },
        // Ecosystem grants (10%)
        GenesisAllocation {
            address_hash: Hash256::from([0x02; 32]),
            amount: Amount::from_synor(7_000_000),
            description: "Ecosystem Grants".to_string(),
        },
        // Team (10%, vested)
        GenesisAllocation {
            address_hash: Hash256::from([0x03; 32]),
            amount: Amount::from_synor(7_000_000),
            description: "Team (4yr vest, 1yr cliff)".to_string(),
        },
        // Reserve (5%)
        GenesisAllocation {
            address_hash: Hash256::from([0x04; 32]),
            amount: Amount::from_synor(3_500_000),
            description: "Reserve".to_string(),
        },
        // Initial staking rewards pool (10%)
        GenesisAllocation {
            address_hash: Hash256::from([0x05; 32]),
            amount: Amount::from_synor(7_000_000),
            description: "Staking Rewards Pool".to_string(),
        },
        // Note: Remaining 50% (35M) is for community/public distribution via mining
    ]
}

/// Returns testnet genesis allocations.
fn genesis_allocation_testnet() -> Vec<GenesisAllocation> {
    vec![GenesisAllocation {
        address_hash: Hash256::from([0x10; 32]),
        amount: Amount::from_synor(1_000_000),
        description: "Testnet Faucet".to_string(),
    }]
}

/// Returns devnet genesis allocations.
fn genesis_allocation_devnet() -> Vec<GenesisAllocation> {
    vec![GenesisAllocation {
        address_hash: Hash256::from([0x20; 32]),
        amount: Amount::from_synor(100_000_000),
        description: "Dev Account".to_string(),
    }]
}

/// Creates a coinbase transaction for genesis.
fn create_coinbase_transaction(
    message: &[u8],
    block_reward: Amount,
    allocations: &[GenesisAllocation],
) -> Transaction {
    // Coinbase input with genesis message
    let input = TxInput {
        previous_output: Outpoint::null(),
        signature_script: message.to_vec(),
        sequence: u64::MAX,
    };

    // Outputs for allocations
    let mut outputs: Vec<TxOutput> = allocations
        .iter()
        .map(|alloc| TxOutput {
            amount: alloc.amount,
            script_pubkey: ScriptPubKey {
                script_type: ScriptType::P2PKH,
                data: alloc.address_hash.as_bytes().to_vec(),
            },
        })
        .collect();

    // Add block reward output (to miner - for genesis, burned or to fund)
    outputs.push(TxOutput {
        amount: block_reward,
        script_pubkey: ScriptPubKey::op_return(&[0x00; 32]), // OP_RETURN style burn for genesis
    });

    Transaction {
        version: 1,
        inputs: vec![input],
        outputs,
        lock_time: 0,
        subnetwork_id: SubnetworkId::COINBASE,
        gas: 0,
        payload: vec![],
    }
}

/// Checkpoint for chain validation.
#[derive(Clone, Debug)]
pub struct Checkpoint {
    /// Blue score at checkpoint.
    pub blue_score: u64,
    /// Block hash at checkpoint.
    pub hash: Hash256,
    /// Timestamp for reference.
    pub timestamp: u64,
}

/// Returns hardcoded checkpoints for mainnet.
pub fn mainnet_checkpoints() -> Vec<Checkpoint> {
    vec![
        // Genesis
        Checkpoint {
            blue_score: 0,
            hash: ChainConfig::mainnet().genesis_hash,
            timestamp: 1735689600000,
        },
        // Add more checkpoints as network grows
    ]
}

/// Returns hardcoded checkpoints for testnet.
pub fn testnet_checkpoints() -> Vec<Checkpoint> {
    vec![Checkpoint {
        blue_score: 0,
        hash: ChainConfig::testnet().genesis_hash,
        timestamp: 1735689600000,
    }]
}

/// Network magic bytes for message framing.
pub fn network_magic(network: Network) -> [u8; 4] {
    match network {
        Network::Mainnet => [0x53, 0x59, 0x4E, 0x4F], // "SYNO"
        Network::Testnet => [0x54, 0x53, 0x59, 0x4E], // "TSYN"
        Network::Devnet => [0x44, 0x53, 0x59, 0x4E],  // "DSYN"
    }
}

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

    #[test]
    fn test_mainnet_genesis() {
        let config = ChainConfig::mainnet();

        assert_eq!(config.network, Network::Mainnet);
        assert_eq!(config.genesis.body.transactions.len(), 1);
        assert_eq!(config.genesis.header.parents.len(), 0);
        assert_eq!(config.genesis.header.blue_score.value(), 0);
    }

    #[test]
    fn test_testnet_genesis() {
        let config = ChainConfig::testnet();

        assert_eq!(config.network, Network::Testnet);
        // Higher bits value = lower difficulty (easier), so testnet < devnet means testnet is harder
        assert!(config.initial_difficulty < ChainConfig::devnet().initial_difficulty);
    }

    #[test]
    fn test_devnet_genesis() {
        let config = ChainConfig::devnet();

        assert_eq!(config.network, Network::Devnet);
        assert_eq!(config.coinbase_maturity, 1);
    }

    #[test]
    fn test_genesis_hash_consistency() {
        let config1 = ChainConfig::mainnet();
        let config2 = ChainConfig::mainnet();

        // Genesis hash should be deterministic
        assert_eq!(config1.genesis_hash, config2.genesis_hash);
    }

    #[test]
    fn test_network_magic() {
        assert_eq!(network_magic(Network::Mainnet), [0x53, 0x59, 0x4E, 0x4F]);
        assert_eq!(network_magic(Network::Testnet), [0x54, 0x53, 0x59, 0x4E]);
    }

    #[test]
    fn test_genesis_allocations() {
        let allocations = genesis_allocation_mainnet();

        // Should have 5 allocations
        assert_eq!(allocations.len(), 5);

        // Total should be 35M (50% of 70M supply, rest is mined)
        let total: u64 = allocations.iter().map(|a| a.amount.as_sompi()).sum();
        assert_eq!(total, 35_000_000 * 100_000_000);
    }

    #[test]
    fn test_config_for_network() {
        let mainnet = ChainConfig::for_network(Network::Mainnet);
        assert_eq!(mainnet.network, Network::Mainnet);

        let testnet = ChainConfig::for_network(Network::Testnet);
        assert_eq!(testnet.network, Network::Testnet);
    }
}
