//! Consensus service.

use std::sync::Arc;

use borsh::BorshDeserialize;
use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info};

use synor_consensus::{
    BlockValidator, DaaParams, DifficultyManager, RewardCalculator, TransactionValidator, UtxoSet,
    ValidationError,
};
use synor_types::{
    block::{Block, BlockHeader},
    transaction::Transaction,
    Amount, BlockId, Hash256, Network,
};

use crate::config::NodeConfig;
use crate::services::StorageService;

/// Block validation result.
#[derive(Clone, Debug)]
pub enum BlockValidation {
    /// Block is valid.
    Valid,
    /// Block is valid but orphan (missing parents).
    Orphan { missing: Vec<[u8; 32]> },
    /// Block is invalid.
    Invalid { reason: String },
    /// Block already exists.
    Duplicate,
}

/// Transaction validation result.
#[derive(Clone, Debug)]
pub enum TxValidation {
    /// Transaction is valid.
    Valid,
    /// Transaction is invalid.
    Invalid { reason: String },
    /// Transaction already in mempool.
    Duplicate,
    /// Transaction conflicts with another.
    Conflict,
}

/// Consensus service manages block validation and chain state.
pub struct ConsensusService {
    /// Storage reference.
    storage: Arc<StorageService>,

    /// Transaction validator from synor-consensus.
    tx_validator: TransactionValidator,

    /// Block validator from synor-consensus.
    block_validator: BlockValidator,

    /// Reward calculator.
    reward_calculator: RewardCalculator,

    /// Difficulty manager.
    difficulty_manager: DifficultyManager,

    /// UTXO set (virtual state).
    utxo_set: UtxoSet,

    /// Network type.
    network: Network,

    /// GHOSTDAG K parameter.
    ghostdag_k: u8,

    /// Current DAA score.
    daa_score: RwLock<u64>,

    /// Current blue score.
    blue_score: RwLock<u64>,

    /// Current tips.
    tips: RwLock<Vec<[u8; 32]>>,

    /// Is running.
    running: RwLock<bool>,

    /// Shutdown receiver.
    #[allow(dead_code)]
    shutdown_rx: broadcast::Receiver<()>,

    /// Block accepted channel.
    block_accepted_tx: broadcast::Sender<[u8; 32]>,
}

impl ConsensusService {
    /// Creates a new consensus service.
    pub fn new(
        storage: Arc<StorageService>,
        config: &NodeConfig,
        shutdown_rx: broadcast::Receiver<()>,
    ) -> anyhow::Result<Self> {
        let (block_accepted_tx, _) = broadcast::channel(1000);

        // Determine network type from config
        let network = match config.network.as_str() {
            "mainnet" => Network::Mainnet,
            "testnet" => Network::Testnet,
            _ => Network::Devnet,
        };

        // Create DAA params based on config
        let daa_params = DaaParams {
            target_time_ms: config.consensus.target_time_ms,
            window_size: config.consensus.difficulty_window,
            max_adjustment_factor: 4.0,
            min_difficulty: 1,
        };

        Ok(ConsensusService {
            storage,
            tx_validator: TransactionValidator::new(),
            block_validator: BlockValidator::new(),
            reward_calculator: RewardCalculator::new(),
            difficulty_manager: DifficultyManager::new(daa_params),
            utxo_set: UtxoSet::new(),
            network,
            ghostdag_k: config.consensus.ghostdag_k,
            daa_score: RwLock::new(0),
            blue_score: RwLock::new(0),
            tips: RwLock::new(vec![]),
            running: RwLock::new(false),
            shutdown_rx,
            block_accepted_tx,
        })
    }

    /// Starts the consensus service.
    pub async fn start(&self) -> anyhow::Result<()> {
        info!("Starting consensus service");

        // Load chain state from storage
        if let Ok(Some(state)) = self.storage.get_chain_state().await {
            *self.daa_score.write().await = state.daa_score;
            *self.blue_score.write().await = state.max_blue_score;
            info!(
                daa_score = state.daa_score,
                blue_score = state.max_blue_score,
                total_blocks = state.total_blocks,
                "Loaded chain state"
            );
        }

        // Load tips from storage
        if let Ok(tips) = self.storage.get_tips().await {
            let tip_bytes: Vec<[u8; 32]> = tips.iter().map(|t| *t.as_bytes()).collect();
            *self.tips.write().await = tip_bytes.clone();
            info!(tip_count = tips.len(), "Loaded DAG tips");
        }

        *self.running.write().await = true;
        info!("Consensus service started");
        Ok(())
    }

    /// Stops the consensus service.
    pub async fn stop(&self) -> anyhow::Result<()> {
        info!("Stopping consensus service");
        *self.running.write().await = false;
        info!("Consensus service stopped");
        Ok(())
    }

    /// Returns current DAA score.
    pub async fn current_daa_score(&self) -> u64 {
        *self.daa_score.read().await
    }

    /// Returns current block height (alias for DAA score).
    pub async fn current_height(&self) -> u64 {
        *self.daa_score.read().await
    }

    /// Returns current blue score.
    pub async fn current_blue_score(&self) -> u64 {
        *self.blue_score.read().await
    }

    /// Returns current blue score (alias for miner).
    pub async fn blue_score(&self) -> u64 {
        *self.blue_score.read().await
    }

    /// Returns current difficulty bits.
    pub async fn current_difficulty(&self) -> u32 {
        // Get difficulty from latest block or use default
        let tips = self.tips.read().await;
        if tips.is_empty() {
            return 0x1e0fffff; // Default easy difficulty
        }

        // Get difficulty from first tip (would use GHOSTDAG selected parent in production)
        let tip_id = synor_types::BlockId::from_bytes(tips[0]);
        match self.storage.get_header(&tip_id).await {
            Ok(Some(header)) => header.bits,
            _ => 0x1e0fffff, // Default
        }
    }

    /// Returns current tips.
    pub async fn tips(&self) -> Vec<[u8; 32]> {
        self.tips.read().await.clone()
    }

    /// Returns the UTXO set.
    pub fn utxo_set(&self) -> &UtxoSet {
        &self.utxo_set
    }

    /// Returns the network type.
    pub fn network(&self) -> Network {
        self.network
    }

    // ==================== Block Validation ====================

    /// Validates a block header.
    pub async fn validate_header(&self, header: &BlockHeader) -> anyhow::Result<()> {
        debug!(hash = %header.block_id(), "Validating header");

        // Check parents exist
        for parent in &header.parents {
            if !self.storage.has_header(parent).await {
                anyhow::bail!("Missing parent: {}", parent);
            }
        }

        // Validate header structure
        self.block_validator
            .validate_header(header)
            .map_err(|e| anyhow::anyhow!("Invalid header: {}", e))
    }

    /// Validates a block from raw bytes.
    pub async fn validate_block(&self, block_bytes: &[u8]) -> BlockValidation {
        debug!("Validating block from bytes");

        // Try to parse the block
        let block = match Block::try_from_slice(block_bytes) {
            Ok(b) => b,
            Err(e) => {
                return BlockValidation::Invalid {
                    reason: format!("Failed to parse block: {}", e),
                };
            }
        };

        self.validate_block_parsed(&block).await
    }

    /// Validates a parsed block.
    pub async fn validate_block_parsed(&self, block: &Block) -> BlockValidation {
        let block_hash = block.header.block_id();
        debug!(hash = %block_hash, "Validating parsed block");

        // Check if block already exists
        if self.storage.has_header(&block_hash).await {
            return BlockValidation::Duplicate;
        }

        // Check parents exist
        let mut missing_parents = Vec::new();
        for parent in &block.header.parents {
            if !self.storage.has_header(parent).await {
                missing_parents.push(*parent.as_bytes());
            }
        }

        if !missing_parents.is_empty() {
            return BlockValidation::Orphan {
                missing: missing_parents,
            };
        }

        // Validate header
        if let Err(e) = self.block_validator.validate_header(&block.header) {
            return BlockValidation::Invalid {
                reason: format!("Invalid header: {}", e),
            };
        }

        // Calculate expected reward
        let expected_reward = self
            .reward_calculator
            .calculate_subsidy(block.header.daa_score);

        // Validate the full block (including transactions)
        if let Err(e) = self
            .block_validator
            .validate_block(block, &self.utxo_set, expected_reward)
        {
            return BlockValidation::Invalid {
                reason: format!("Invalid block: {}", e),
            };
        }

        BlockValidation::Valid
    }

    /// Processes a validated block (adds to DAG and updates state).
    pub async fn process_block(&self, block: &Block) -> anyhow::Result<()> {
        self.process_block_impl(block).await
    }

    /// Processes a block from raw bytes.
    pub async fn process_block_bytes(&self, block_bytes: &[u8]) -> anyhow::Result<()> {
        // Parse block
        let block = Block::try_from_slice(block_bytes)
            .map_err(|e| anyhow::anyhow!("Failed to parse block: {}", e))?;

        self.process_block_impl(&block).await
    }

    /// Internal implementation for processing a block.
    async fn process_block_impl(&self, block: &Block) -> anyhow::Result<()> {
        let block_hash = block.header.block_id();
        debug!(hash = %block_hash, "Processing block");

        // Store the header
        self.storage.put_header(&block.header).await?;

        // Create block body for storage
        let body = synor_storage::BlockBody {
            transaction_ids: block.body.transactions.iter().map(|tx| tx.txid()).collect(),
        };
        self.storage.put_block_body(&block_hash, &body).await?;

        // Store transactions
        for tx in &block.body.transactions {
            self.storage.put_transaction(tx).await?;
        }

        // Update UTXO set
        for tx in &block.body.transactions {
            // Create UTXO diff
            let diff = self
                .utxo_set
                .create_transaction_diff(tx, block.header.daa_score)
                .map_err(|e| anyhow::anyhow!("UTXO diff error: {}", e))?;

            // Apply diff
            self.utxo_set
                .apply_diff(&diff)
                .map_err(|e| anyhow::anyhow!("UTXO apply error: {}", e))?;

            // Store UTXOs in persistent storage
            for (outpoint, entry) in &diff.to_add {
                let stored_utxo = synor_storage::StoredUtxo {
                    amount: entry.amount().as_sompi(),
                    script_pubkey: entry.script_pubkey().data.clone(),
                    block_daa_score: entry.block_daa_score,
                    is_coinbase: entry.is_coinbase,
                };
                self.storage
                    .put_utxo(&outpoint.txid, outpoint.index, &stored_utxo)
                    .await?;
            }

            // Remove spent UTXOs from storage
            for outpoint in &diff.to_remove {
                self.storage
                    .delete_utxo(&outpoint.txid, outpoint.index)
                    .await?;
            }
        }

        // Update tips
        let mut tips = self.tips.write().await;
        // Remove parents that are no longer tips
        for parent in &block.header.parents {
            tips.retain(|t| t != parent.as_bytes());
        }
        // Add this block as a new tip
        tips.push(*block_hash.as_bytes());

        // Persist tips
        let tip_ids: Vec<BlockId> = tips.iter().map(|t| BlockId::from_bytes(*t)).collect();
        self.storage.set_tips(&tip_ids).await?;

        // Update DAA score
        let mut daa = self.daa_score.write().await;
        if block.header.daa_score > *daa {
            *daa = block.header.daa_score;
        }

        // Update chain state in storage
        let current_state = synor_storage::ChainState {
            max_blue_score: *self.blue_score.read().await,
            total_blocks: *daa,
            daa_score: *daa,
            difficulty_bits: block.header.bits,
            total_work: vec![], // Would compute actual work
        };
        self.storage.set_chain_state(&current_state).await?;

        // Notify subscribers
        let _ = self.block_accepted_tx.send(*block_hash.as_bytes());

        info!(hash = %block_hash, daa_score = block.header.daa_score, "Block processed");
        Ok(())
    }

    // ==================== Transaction Validation ====================

    /// Validates a transaction from raw bytes.
    pub async fn validate_tx(&self, tx_bytes: &[u8]) -> TxValidation {
        debug!("Validating transaction from bytes");

        // Try to parse the transaction
        let tx = match Transaction::try_from_slice(tx_bytes) {
            Ok(t) => t,
            Err(e) => {
                return TxValidation::Invalid {
                    reason: format!("Failed to parse transaction: {}", e),
                };
            }
        };

        self.validate_tx_parsed(&tx).await
    }

    /// Validates a parsed transaction.
    pub async fn validate_tx_parsed(&self, tx: &Transaction) -> TxValidation {
        let txid = tx.txid();
        debug!(txid = %txid, "Validating parsed transaction");

        // Check if already in chain
        if self.storage.has_transaction(&txid).await {
            return TxValidation::Duplicate;
        }

        // Validate structure
        if let Err(e) = self.tx_validator.validate_structure(tx) {
            return TxValidation::Invalid {
                reason: format!("Invalid structure: {}", e),
            };
        }

        // For non-coinbase transactions, validate against UTXO set
        if !tx.is_coinbase() {
            let current_daa = *self.daa_score.read().await;
            if let Err(e) =
                self.tx_validator
                    .validate_against_utxos(tx, &self.utxo_set, current_daa)
            {
                // Check if this is a double-spend conflict
                if matches!(e, ValidationError::UtxoNotFound(_)) {
                    return TxValidation::Conflict;
                }
                return TxValidation::Invalid {
                    reason: format!("UTXO validation failed: {}", e),
                };
            }
        }

        TxValidation::Valid
    }

    /// Calculates the fee for a transaction.
    pub fn calculate_tx_fee(&self, tx: &Transaction) -> Option<Amount> {
        if tx.is_coinbase() {
            return Some(Amount::ZERO);
        }

        let current_daa = 0; // Would need async access
        self.tx_validator
            .validate_against_utxos(tx, &self.utxo_set, current_daa)
            .ok()
    }

    // ==================== Chain Info ====================

    /// Subscribes to accepted blocks.
    pub fn subscribe_blocks(&self) -> broadcast::Receiver<[u8; 32]> {
        self.block_accepted_tx.subscribe()
    }

    /// Gets the selected parent chain (up to limit blocks).
    pub async fn get_selected_chain(&self, limit: usize) -> Vec<[u8; 32]> {
        let mut chain = Vec::new();
        let tips = self.tips.read().await;

        if tips.is_empty() {
            return chain;
        }

        // Start from first tip (virtual selected parent in simple case)
        let mut current = BlockId::from_bytes(tips[0]);

        for _ in 0..limit {
            // Get selected parent from GHOSTDAG data
            match self.storage.get_selected_parent(&current).await {
                Ok(Some(parent)) => {
                    chain.push(*current.as_bytes());
                    current = parent;
                }
                _ => {
                    chain.push(*current.as_bytes());
                    break;
                }
            }
        }

        chain
    }

    /// Gets block info.
    pub async fn get_block_info(&self, hash: &[u8; 32]) -> Option<BlockInfo> {
        let block_id = BlockId::from_bytes(*hash);

        // Get GHOSTDAG data
        let ghostdag = self.storage.get_ghostdag(&block_id).await.ok()??;

        // Get relations
        let relations = self.storage.get_relations(&block_id).await.ok()?;

        Some(BlockInfo {
            hash: *hash,
            height: ghostdag.blue_score,
            blue_score: ghostdag.blue_score,
            is_chain_block: true, // Would need to check selected chain
            selected_parent: Some(*ghostdag.selected_parent.as_bytes()),
            parents: relations
                .as_ref()
                .map(|r| r.parents.iter().map(|p| *p.as_bytes()).collect())
                .unwrap_or_default(),
            children: relations
                .as_ref()
                .map(|r| r.children.iter().map(|c| *c.as_bytes()).collect())
                .unwrap_or_default(),
            blues: ghostdag
                .merge_set_blues
                .iter()
                .map(|b| *b.as_bytes())
                .collect(),
            reds: ghostdag
                .merge_set_reds
                .iter()
                .map(|r| *r.as_bytes())
                .collect(),
        })
    }

    /// Checks if block is in the selected chain.
    pub async fn is_in_selected_chain(&self, hash: &[u8; 32]) -> bool {
        let chain = self.get_selected_chain(1000).await;
        chain.contains(hash)
    }

    /// Gets the accepting block for a transaction.
    pub async fn get_accepting_block(&self, _tx_hash: &[u8; 32]) -> Option<[u8; 32]> {
        // Would need to track this in storage
        // For now, return None
        None
    }

    /// Gets confirmations for a block.
    pub async fn get_confirmations(&self, hash: &[u8; 32]) -> u64 {
        let block_id = BlockId::from_bytes(*hash);

        // Get block's blue score
        let block_score = match self.storage.get_blue_score(&block_id).await {
            Ok(Some(score)) => score,
            _ => return 0,
        };

        // Current blue score
        let current_score = *self.blue_score.read().await;

        // Confirmations = current_score - block_score
        current_score.saturating_sub(block_score)
    }

    /// Gets the virtual selected parent.
    pub async fn virtual_selected_parent(&self) -> Option<[u8; 32]> {
        let tips = self.tips.read().await;
        tips.first().copied()
    }

    /// Gets the expected block reward for the next block.
    pub async fn get_next_reward(&self) -> Amount {
        let daa_score = *self.daa_score.read().await;
        self.reward_calculator.calculate_subsidy(daa_score + 1)
    }

    /// Gets the current difficulty target.
    pub async fn get_current_target(&self) -> Hash256 {
        // Would need to compute from difficulty bits
        // For now, return a maximum target (all 1s)
        Hash256::from_bytes([0xff; 32])
    }

    /// Gets blocks at or near a specific blue score.
    ///
    /// In a DAG, multiple blocks can exist at similar blue scores.
    /// This method walks the selected chain to find blocks closest to the target.
    pub async fn get_blocks_by_blue_score(&self, target_score: u64) -> Vec<[u8; 32]> {
        let mut result = Vec::new();

        // Get a reasonable window of the selected chain
        let chain = self.get_selected_chain(10_000).await;

        // Find blocks at the target blue score (or within tolerance)
        for hash in &chain {
            if let Some(info) = self.get_block_info(hash).await {
                if info.blue_score == target_score {
                    result.push(*hash);
                }
            }
        }

        // If exact match not found, return the closest block
        if result.is_empty() {
            let mut closest_hash: Option<[u8; 32]> = None;
            let mut closest_diff = u64::MAX;

            for hash in &chain {
                if let Some(info) = self.get_block_info(hash).await {
                    let diff = info.blue_score.abs_diff(target_score);
                    if diff < closest_diff {
                        closest_diff = diff;
                        closest_hash = Some(*hash);
                    }
                }
            }

            if let Some(h) = closest_hash {
                result.push(h);
            }
        }

        result
    }
}

/// Block information.
#[derive(Clone, Debug)]
pub struct BlockInfo {
    pub hash: [u8; 32],
    pub height: u64,
    pub blue_score: u64,
    pub is_chain_block: bool,
    pub selected_parent: Option<[u8; 32]>,
    pub parents: Vec<[u8; 32]>,
    pub children: Vec<[u8; 32]>,
    pub blues: Vec<[u8; 32]>,
    pub reds: Vec<[u8; 32]>,
}
