//! DAGKnight adaptive consensus protocol.
//!
//! DAGKnight is an evolution of GHOSTDAG that eliminates fixed network delay
//! assumptions. Instead of using a static k parameter, DAGKnight adapts based
//! on observed network conditions.
//!
//! # Key Improvements Over GHOSTDAG
//!
//! 1. **Adaptive K Parameter**: Adjusts based on measured network latency
//! 2. **Probabilistic Confirmation**: Provides confidence-based finality estimates
//! 3. **No Fixed Delay Assumption**: Learns actual network behavior
//! 4. **Faster Confirmation**: Converges faster under good network conditions
//!
//! # Algorithm Overview
//!
//! DAGKnight maintains the core GHOSTDAG blue set selection but adds:
//! - Network latency tracking via `LatencyTracker`
//! - Dynamic k calculation based on observed anticone growth
//! - Probabilistic confirmation time estimation
//!
//! # References
//!
//! - DAGKnight Paper (2022): "DAGKnight: A Parameterless GHOSTDAG"
//! - Kaspa 2025 Roadmap: Implementation plans for production use

use std::sync::Arc;
use std::time::Duration;

use parking_lot::RwLock;

use crate::{
    dag::BlockDag,
    ghostdag::{GhostdagData, GhostdagError, GhostdagManager},
    latency::{LatencyStats, LatencyTracker},
    reachability::ReachabilityStore,
    BlockId, BlueScore, GHOSTDAG_K,
};

/// Minimum adaptive k value (security lower bound).
const MIN_ADAPTIVE_K: u8 = 8;

/// Maximum adaptive k value (performance upper bound).
const MAX_ADAPTIVE_K: u8 = 64;

/// Default k when insufficient latency data is available.
const DEFAULT_K: u8 = GHOSTDAG_K;

/// Number of samples required before adapting k.
const MIN_SAMPLES_FOR_ADAPTATION: usize = 100;

/// Block rate (blocks per second) - used for k calculation.
/// At 10 BPS with 100ms block time, this is the baseline.
const BLOCK_RATE_BPS: f64 = 10.0;

/// Safety margin multiplier for k calculation.
/// Higher values = more conservative (safer but lower throughput).
const SAFETY_MARGIN: f64 = 1.5;

/// Confirmation confidence levels.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ConfirmationConfidence {
    /// ~68% confidence (1 sigma).
    Low,
    /// ~95% confidence (2 sigma).
    Medium,
    /// ~99.7% confidence (3 sigma).
    High,
    /// ~99.99% confidence (4 sigma).
    VeryHigh,
}

impl ConfirmationConfidence {
    /// Returns the sigma multiplier for this confidence level.
    fn sigma_multiplier(&self) -> f64 {
        match self {
            ConfirmationConfidence::Low => 1.0,
            ConfirmationConfidence::Medium => 2.0,
            ConfirmationConfidence::High => 3.0,
            ConfirmationConfidence::VeryHigh => 4.0,
        }
    }
}

/// Confirmation status for a block.
#[derive(Clone, Debug)]
pub struct ConfirmationStatus {
    /// Block being queried.
    pub block_id: BlockId,
    /// Current blue score depth from virtual tip.
    pub depth: u64,
    /// Estimated time to reach requested confidence.
    pub estimated_time: Duration,
    /// Current confidence level achieved.
    pub current_confidence: f64,
    /// Whether the block is considered final.
    pub is_final: bool,
}

/// DAGKnight manager extending GHOSTDAG with adaptive consensus.
pub struct DagKnightManager {
    /// Underlying GHOSTDAG manager.
    ghostdag: Arc<GhostdagManager>,
    /// The DAG structure.
    dag: Arc<BlockDag>,
    /// Reachability queries.
    reachability: Arc<ReachabilityStore>,
    /// Network latency tracker.
    latency_tracker: Arc<LatencyTracker>,
    /// Current adaptive k value.
    adaptive_k: RwLock<u8>,
    /// Block rate setting.
    block_rate_bps: f64,
}

impl DagKnightManager {
    /// Creates a new DAGKnight manager.
    pub fn new(
        dag: Arc<BlockDag>,
        reachability: Arc<ReachabilityStore>,
    ) -> Self {
        let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone()));
        let latency_tracker = Arc::new(LatencyTracker::new());

        Self {
            ghostdag,
            dag,
            reachability,
            latency_tracker,
            adaptive_k: RwLock::new(DEFAULT_K),
            block_rate_bps: BLOCK_RATE_BPS,
        }
    }

    /// Creates a DAGKnight manager with custom block rate.
    pub fn with_block_rate(
        dag: Arc<BlockDag>,
        reachability: Arc<ReachabilityStore>,
        block_rate_bps: f64,
    ) -> Self {
        let ghostdag = Arc::new(GhostdagManager::new(dag.clone(), reachability.clone()));
        let latency_tracker = Arc::new(LatencyTracker::new());

        Self {
            ghostdag,
            dag,
            reachability,
            latency_tracker,
            adaptive_k: RwLock::new(DEFAULT_K),
            block_rate_bps,
        }
    }

    /// Creates a DAGKnight manager wrapping an existing GHOSTDAG manager.
    pub fn from_ghostdag(
        ghostdag: Arc<GhostdagManager>,
        dag: Arc<BlockDag>,
        reachability: Arc<ReachabilityStore>,
    ) -> Self {
        Self {
            ghostdag,
            dag,
            reachability,
            latency_tracker: Arc::new(LatencyTracker::new()),
            adaptive_k: RwLock::new(DEFAULT_K),
            block_rate_bps: BLOCK_RATE_BPS,
        }
    }

    /// Processes a new block with latency tracking.
    ///
    /// This method:
    /// 1. Records the block observation in the latency tracker
    /// 2. Delegates to GHOSTDAG for blue set calculation
    /// 3. Updates the adaptive k parameter if needed
    pub fn add_block(
        &self,
        block_id: BlockId,
        parents: &[BlockId],
        block_time_ms: u64,
    ) -> Result<GhostdagData, GhostdagError> {
        // Calculate anticone size for this block
        let anticone_size = self.calculate_anticone_size(&block_id, parents);

        // Record observation in latency tracker
        self.latency_tracker.record_block(block_id, block_time_ms, anticone_size);

        // Process with underlying GHOSTDAG
        let data = self.ghostdag.add_block(block_id, parents)?;

        // Periodically update adaptive k
        if self.latency_tracker.sample_count() % 50 == 0 {
            self.update_adaptive_k();
        }

        Ok(data)
    }

    /// Calculates the anticone size for a new block.
    fn calculate_anticone_size(&self, block_id: &BlockId, parents: &[BlockId]) -> usize {
        // Anticone is the set of blocks that are neither ancestors nor descendants
        // For a new block, we estimate based on tips that aren't in parent set
        let tips = self.dag.tips();
        let mut anticone_count = 0;

        for tip in tips {
            if tip != *block_id && !parents.contains(&tip) {
                // Check if tip is in the past of any parent
                let in_past = parents.iter().any(|p| {
                    self.reachability
                        .is_ancestor(p, &tip)
                        .unwrap_or(false)
                });

                if !in_past {
                    anticone_count += 1;
                }
            }
        }

        anticone_count
    }

    /// Updates the adaptive k parameter based on observed latency.
    ///
    /// The adaptive k formula is:
    /// k = ceil(block_rate * network_delay * safety_margin)
    ///
    /// This ensures that even with network delays, honest miners
    /// can create blocks that fit within the k-cluster.
    fn update_adaptive_k(&self) {
        let stats = self.latency_tracker.get_stats();

        // Don't adapt until we have enough samples
        if stats.sample_count < MIN_SAMPLES_FOR_ADAPTATION {
            return;
        }

        // Calculate k based on P95 delay (conservative)
        let delay_secs = stats.p95_delay_ms / 1000.0;
        let calculated_k = (self.block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u8;

        // Clamp to valid range
        let new_k = calculated_k.clamp(MIN_ADAPTIVE_K, MAX_ADAPTIVE_K);

        // Update if significantly different (avoid jitter)
        let current_k = *self.adaptive_k.read();
        if (new_k as i16 - current_k as i16).abs() >= 2 {
            *self.adaptive_k.write() = new_k;
        }
    }

    /// Gets the current adaptive k parameter.
    pub fn adaptive_k(&self) -> u8 {
        *self.adaptive_k.read()
    }

    /// Gets the current latency statistics.
    pub fn latency_stats(&self) -> LatencyStats {
        self.latency_tracker.get_stats()
    }

    /// Estimates confirmation time for a block at a given confidence level.
    ///
    /// DAGKnight provides probabilistic confirmation based on:
    /// 1. Current depth (blue score difference from tip)
    /// 2. Observed network latency
    /// 3. Requested confidence level
    pub fn estimate_confirmation_time(
        &self,
        block_id: &BlockId,
        confidence: ConfirmationConfidence,
    ) -> Result<ConfirmationStatus, GhostdagError> {
        let block_data = self.ghostdag.get_data(block_id)?;
        let tip_data = self.get_virtual_tip_data()?;

        // Depth is the blue score difference
        let depth = tip_data.blue_score.saturating_sub(block_data.blue_score);

        // Get latency stats
        let stats = self.latency_tracker.get_stats();

        // Calculate required depth for requested confidence
        // Based on the paper, confirmation requires depth proportional to
        // network delay variance
        let sigma = stats.std_dev_ms / 1000.0; // Convert to seconds
        let mean_delay = stats.mean_delay_ms / 1000.0;
        let sigma_multiplier = confidence.sigma_multiplier();

        // Required depth scales with variance and confidence level
        let required_depth = (self.block_rate_bps * (mean_delay + sigma * sigma_multiplier)).ceil() as u64;

        // Current confidence based on actual depth
        let current_confidence = if depth >= required_depth {
            self.calculate_confidence(depth, mean_delay, sigma)
        } else {
            // Interpolate confidence based on depth progress
            (depth as f64 / required_depth as f64) * 0.95
        };

        // Time to reach required depth
        let blocks_needed = required_depth.saturating_sub(depth);
        let time_per_block_ms = 1000.0 / self.block_rate_bps;
        let estimated_time = Duration::from_millis((blocks_needed as f64 * time_per_block_ms) as u64);

        // Block is final if depth exceeds finality threshold
        let is_final = depth >= crate::FINALITY_DEPTH;

        Ok(ConfirmationStatus {
            block_id: *block_id,
            depth,
            estimated_time,
            current_confidence,
            is_final,
        })
    }

    /// Calculates confidence level based on depth and network conditions.
    fn calculate_confidence(&self, depth: u64, mean_delay: f64, sigma: f64) -> f64 {
        // Using simplified normal CDF approximation
        // Confidence increases with depth relative to expected delay variance
        let depth_secs = depth as f64 / self.block_rate_bps;
        let z_score = (depth_secs - mean_delay) / sigma.max(0.001);

        // Approximate CDF using logistic function
        1.0 / (1.0 + (-1.7 * z_score).exp())
    }

    /// Gets the GHOSTDAG data for the virtual tip (highest blue score block).
    fn get_virtual_tip_data(&self) -> Result<GhostdagData, GhostdagError> {
        let tips = self.dag.tips();

        // Find tip with highest blue score
        let mut best_tip = tips[0];
        let mut best_score = self.ghostdag.get_blue_score(&tips[0]).unwrap_or(0);

        for tip in tips.iter().skip(1) {
            let score = self.ghostdag.get_blue_score(tip).unwrap_or(0);
            if score > best_score {
                best_score = score;
                best_tip = *tip;
            }
        }

        self.ghostdag.get_data(&best_tip)
    }

    /// Gets the underlying GHOSTDAG manager.
    pub fn ghostdag(&self) -> &Arc<GhostdagManager> {
        &self.ghostdag
    }

    /// Gets the latency tracker.
    pub fn latency_tracker(&self) -> &Arc<LatencyTracker> {
        &self.latency_tracker
    }

    /// Gets the blue score for a block (delegates to GHOSTDAG).
    pub fn get_blue_score(&self, block_id: &BlockId) -> Result<BlueScore, GhostdagError> {
        self.ghostdag.get_blue_score(block_id)
    }

    /// Gets the GHOSTDAG data for a block.
    pub fn get_data(&self, block_id: &BlockId) -> Result<GhostdagData, GhostdagError> {
        self.ghostdag.get_data(block_id)
    }

    /// Checks if a block is in the blue set.
    pub fn is_blue(&self, block_id: &BlockId) -> bool {
        self.ghostdag.is_blue(block_id)
    }

    /// Returns the selected chain from a block to genesis.
    pub fn get_selected_chain(&self, from: &BlockId) -> Result<Vec<BlockId>, GhostdagError> {
        self.ghostdag.get_selected_chain(from)
    }

    /// Resets the latency tracker (e.g., after network reconfiguration).
    pub fn reset_latency_tracking(&self) {
        self.latency_tracker.reset();
        *self.adaptive_k.write() = DEFAULT_K;
    }
}

impl std::fmt::Debug for DagKnightManager {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let stats = self.latency_tracker.get_stats();
        f.debug_struct("DagKnightManager")
            .field("adaptive_k", &*self.adaptive_k.read())
            .field("block_rate_bps", &self.block_rate_bps)
            .field("mean_delay_ms", &stats.mean_delay_ms)
            .field("sample_count", &stats.sample_count)
            .finish()
    }
}

/// Calculates the optimal k for a given network delay and block rate.
///
/// This is a utility function for network analysis.
pub fn calculate_optimal_k(network_delay_ms: f64, block_rate_bps: f64) -> u8 {
    let delay_secs = network_delay_ms / 1000.0;
    let k = (block_rate_bps * delay_secs * SAFETY_MARGIN).ceil() as u8;
    k.clamp(MIN_ADAPTIVE_K, MAX_ADAPTIVE_K)
}

/// Estimates throughput (TPS) for given network conditions.
///
/// Throughput depends on block rate and transaction capacity per block.
pub fn estimate_throughput(
    block_rate_bps: f64,
    avg_tx_per_block: u64,
    network_delay_ms: f64,
) -> f64 {
    // Effective block rate accounting for orphan rate
    let orphan_rate = (network_delay_ms / 1000.0 * block_rate_bps).min(0.5);
    let effective_bps = block_rate_bps * (1.0 - orphan_rate);

    effective_bps * avg_tx_per_block as f64
}

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

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

    fn setup_test_dag() -> (Arc<BlockDag>, Arc<ReachabilityStore>, DagKnightManager) {
        let genesis = make_block_id(0);
        let dag = Arc::new(BlockDag::new(genesis, 0));
        let reachability = Arc::new(ReachabilityStore::new(genesis));
        let dagknight = DagKnightManager::new(dag.clone(), reachability.clone());
        (dag, reachability, dagknight)
    }

    #[test]
    fn test_initial_k() {
        let (_, _, dagknight) = setup_test_dag();
        assert_eq!(dagknight.adaptive_k(), DEFAULT_K);
    }

    #[test]
    fn test_calculate_optimal_k() {
        // 100ms delay at 10 BPS: k = ceil(10 * 0.1 * 1.5) = 2, clamped to MIN_ADAPTIVE_K (8)
        let k_low = calculate_optimal_k(100.0, 10.0);
        assert!(k_low >= MIN_ADAPTIVE_K);
        assert!(k_low <= MAX_ADAPTIVE_K);

        // 1000ms delay at 10 BPS: k = ceil(10 * 1.0 * 1.5) = 15, above MIN
        let k_medium = calculate_optimal_k(1000.0, 10.0);
        assert!(k_medium >= MIN_ADAPTIVE_K);

        // 3000ms delay at 10 BPS: k = ceil(10 * 3.0 * 1.5) = 45
        let k_high = calculate_optimal_k(3000.0, 10.0);
        assert!(k_high > k_medium);
        assert!(k_high > k_low);
    }

    #[test]
    fn test_estimate_throughput() {
        // Good network: 10ms delay - orphan_rate = 0.01 * 10 = 0.1
        let tps_good = estimate_throughput(10.0, 100, 10.0);

        // Poor network: 40ms delay - orphan_rate = 0.04 * 10 = 0.4
        let tps_poor = estimate_throughput(10.0, 100, 40.0);

        // Good network should have higher throughput
        assert!(tps_good > tps_poor, "tps_good={} should be > tps_poor={}", tps_good, tps_poor);
    }

    #[test]
    fn test_confidence_levels() {
        assert!(ConfirmationConfidence::VeryHigh.sigma_multiplier()
            > ConfirmationConfidence::High.sigma_multiplier());
        assert!(ConfirmationConfidence::High.sigma_multiplier()
            > ConfirmationConfidence::Medium.sigma_multiplier());
        assert!(ConfirmationConfidence::Medium.sigma_multiplier()
            > ConfirmationConfidence::Low.sigma_multiplier());
    }
}
