//! Network configuration with BPS (Blocks Per Second) presets.
//!
//! Synor supports multiple block rate configurations:
//! - **Standard (10 BPS)**: Default configuration, 100ms block time
//! - **Fast (32 BPS)**: High-throughput mode, ~31ms block time
//! - **Ultra (100 BPS)**: Maximum throughput, 10ms block time
//!
//! # Selecting a Configuration
//!
//! Higher BPS provides faster transaction confirmation but requires:
//! - Better network connectivity (lower latency)
//! - More powerful nodes (higher processing requirements)
//! - Adjusted DAA window sizes
//!
//! ```rust
//! use synor_consensus::network::{NetworkConfig, BpsMode};
//!
//! // Create standard 10 BPS configuration
//! let config = NetworkConfig::standard();
//!
//! // Or use a preset
//! let fast_config = NetworkConfig::from_bps_mode(BpsMode::Fast32);
//! ```

use std::time::Duration;

/// Blocks per second mode.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[derive(Default)]
pub enum BpsMode {
    /// Standard mode: 10 blocks per second (100ms block time)
    /// - Suitable for most network conditions
    /// - Requires ~100ms P95 network latency
    #[default]
    Standard10,

    /// Fast mode: 32 blocks per second (~31ms block time)
    /// - High-throughput for well-connected networks
    /// - Requires ~50ms P95 network latency
    Fast32,

    /// Ultra mode: 100 blocks per second (10ms block time)
    /// - Maximum throughput for data center deployments
    /// - Requires ~20ms P95 network latency
    Ultra100,

    /// Custom BPS configuration
    Custom(u64),
}

impl BpsMode {
    /// Returns the blocks per second for this mode.
    pub fn bps(&self) -> u64 {
        match self {
            BpsMode::Standard10 => 10,
            BpsMode::Fast32 => 32,
            BpsMode::Ultra100 => 100,
            BpsMode::Custom(bps) => *bps,
        }
    }

    /// Returns the target block time in milliseconds.
    pub fn block_time_ms(&self) -> u64 {
        1000 / self.bps()
    }

    /// Returns the recommended minimum network latency (P95) in ms.
    pub fn recommended_latency_ms(&self) -> u64 {
        match self {
            BpsMode::Standard10 => 100,
            BpsMode::Fast32 => 50,
            BpsMode::Ultra100 => 20,
            BpsMode::Custom(bps) => 1000 / bps / 2, // Half of block time
        }
    }
}


impl std::fmt::Display for BpsMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BpsMode::Standard10 => write!(f, "Standard (10 BPS)"),
            BpsMode::Fast32 => write!(f, "Fast (32 BPS)"),
            BpsMode::Ultra100 => write!(f, "Ultra (100 BPS)"),
            BpsMode::Custom(bps) => write!(f, "Custom ({} BPS)", bps),
        }
    }
}

/// Network configuration parameters.
#[derive(Clone, Debug)]
pub struct NetworkConfig {
    /// BPS mode
    pub bps_mode: BpsMode,

    /// Blocks per second
    pub blocks_per_second: u64,

    /// Target block time in milliseconds
    pub target_block_time_ms: u64,

    /// DAA window size (number of blocks)
    /// Adjusted to cover approximately the same time period across BPS modes
    pub daa_window_size: u64,

    /// GHOSTDAG k parameter (for standard GHOSTDAG compatibility)
    pub ghostdag_k: u8,

    /// DAGKnight adaptive k minimum
    pub dagknight_k_min: u8,

    /// DAGKnight adaptive k maximum
    pub dagknight_k_max: u8,

    /// Finality depth (blocks before considered final)
    pub finality_depth: u64,

    /// Pruning depth (blocks to keep before pruning)
    pub pruning_depth: u64,

    /// Merge set size limit (max parents per block)
    pub merge_set_size_limit: u64,

    /// Expected network delay (for initial k calculation)
    pub expected_delay_ms: u64,
}

impl NetworkConfig {
    /// Creates a standard 10 BPS configuration.
    pub fn standard() -> Self {
        Self::from_bps_mode(BpsMode::Standard10)
    }

    /// Creates a fast 32 BPS configuration.
    pub fn fast() -> Self {
        Self::from_bps_mode(BpsMode::Fast32)
    }

    /// Creates an ultra 100 BPS configuration.
    pub fn ultra() -> Self {
        Self::from_bps_mode(BpsMode::Ultra100)
    }

    /// Creates a configuration from a BPS mode.
    pub fn from_bps_mode(mode: BpsMode) -> Self {
        match mode {
            BpsMode::Standard10 => Self {
                bps_mode: mode,
                blocks_per_second: 10,
                target_block_time_ms: 100,
                daa_window_size: 2641,       // ~264s window
                ghostdag_k: 18,              // For 10 BPS
                dagknight_k_min: 8,
                dagknight_k_max: 64,
                finality_depth: 864,         // ~86 seconds
                pruning_depth: 864_000,      // ~24 hours
                merge_set_size_limit: 180,
                expected_delay_ms: 100,
            },
            BpsMode::Fast32 => Self {
                bps_mode: mode,
                blocks_per_second: 32,
                target_block_time_ms: 31,    // ~31.25ms
                daa_window_size: 8461,       // ~264s window at 32 BPS
                ghostdag_k: 58,              // Scaled for 32 BPS
                dagknight_k_min: 16,         // Higher min for faster blocks
                dagknight_k_max: 128,        // Higher max for adaptation
                finality_depth: 2765,        // ~86 seconds at 32 BPS
                pruning_depth: 2_764_800,    // ~24 hours at 32 BPS
                merge_set_size_limit: 576,   // 32/10 * 180
                expected_delay_ms: 50,
            },
            BpsMode::Ultra100 => Self {
                bps_mode: mode,
                blocks_per_second: 100,
                target_block_time_ms: 10,
                daa_window_size: 26410,      // ~264s window at 100 BPS
                ghostdag_k: 180,             // Scaled for 100 BPS
                dagknight_k_min: 50,         // Higher min for very fast blocks
                dagknight_k_max: 255,        // u8 max - very high for adaptation
                finality_depth: 8640,        // ~86 seconds at 100 BPS
                pruning_depth: 8_640_000,    // ~24 hours at 100 BPS
                merge_set_size_limit: 1800,  // 100/10 * 180
                expected_delay_ms: 20,
            },
            BpsMode::Custom(bps) => {
                let scale = bps as f64 / 10.0;
                Self {
                    bps_mode: mode,
                    blocks_per_second: bps,
                    target_block_time_ms: 1000 / bps,
                    daa_window_size: (2641.0 * scale) as u64,
                    ghostdag_k: (18.0 * scale).min(255.0) as u8,
                    dagknight_k_min: (8.0 * scale.sqrt()) as u8,
                    dagknight_k_max: (64.0 * scale).min(255.0) as u8,
                    finality_depth: (864.0 * scale) as u64,
                    pruning_depth: (864_000.0 * scale) as u64,
                    merge_set_size_limit: (180.0 * scale) as u64,
                    expected_delay_ms: (100.0 / scale).max(10.0) as u64,
                }
            }
        }
    }

    /// Creates a configuration with custom BPS.
    pub fn with_bps(bps: u64) -> Self {
        Self::from_bps_mode(BpsMode::Custom(bps))
    }

    /// Returns the block time as a Duration.
    pub fn block_time(&self) -> Duration {
        Duration::from_millis(self.target_block_time_ms)
    }

    /// Calculates the recommended k for given network latency.
    ///
    /// Formula: k = ceil(bps * delay_seconds * safety_margin)
    pub fn calculate_k_for_latency(&self, latency_ms: f64) -> u8 {
        const SAFETY_MARGIN: f64 = 1.5;
        let delay_seconds = latency_ms / 1000.0;
        let k = (self.blocks_per_second as f64 * delay_seconds * SAFETY_MARGIN).ceil() as u8;
        k.clamp(self.dagknight_k_min, self.dagknight_k_max)
    }

    /// Estimates transaction throughput (TPS) for given average tx/block.
    pub fn estimate_tps(&self, avg_tx_per_block: u64) -> f64 {
        self.blocks_per_second as f64 * avg_tx_per_block as f64
    }

    /// Estimates confirmation time at a given confidence level.
    ///
    /// Based on finality depth and block rate.
    pub fn estimate_confirmation_time(&self, confidence_sigma: f64) -> Duration {
        // Confirmation requires depth proportional to sigma
        let required_depth = (self.finality_depth as f64 * confidence_sigma / 3.0) as u64;
        let blocks_time_ms = required_depth * self.target_block_time_ms;
        Duration::from_millis(blocks_time_ms)
    }

    /// Validates network conditions are suitable for this configuration.
    ///
    /// Returns true if the observed latency is acceptable.
    pub fn is_latency_acceptable(&self, observed_p95_latency_ms: u64) -> bool {
        observed_p95_latency_ms <= self.expected_delay_ms * 2
    }

    /// Creates DAA parameters for this configuration.
    pub fn to_daa_params(&self) -> crate::DaaParams {
        crate::DaaParams {
            target_time_ms: self.target_block_time_ms,
            window_size: self.daa_window_size,
            max_adjustment_factor: 4.0,
            min_difficulty: 1,
        }
    }
}

impl Default for NetworkConfig {
    fn default() -> Self {
        Self::standard()
    }
}

/// Comparison table for BPS modes.
pub fn bps_comparison_table() -> String {
    let standard = NetworkConfig::standard();
    let fast = NetworkConfig::fast();
    let ultra = NetworkConfig::ultra();

    let mut table = String::from(
        "| Property | Standard (10 BPS) | Fast (32 BPS) | Ultra (100 BPS) |\n\
         |----------|-------------------|---------------|------------------|\n"
    );

    // Block Time
    table.push_str(&format!(
        "| Block Time | {}ms | {}ms | {}ms |\n",
        standard.target_block_time_ms, fast.target_block_time_ms, ultra.target_block_time_ms
    ));

    // DAA Window
    table.push_str(&format!(
        "| DAA Window | {} blocks | {} blocks | {} blocks |\n",
        standard.daa_window_size, fast.daa_window_size, ultra.daa_window_size
    ));

    // GHOSTDAG K
    table.push_str(&format!(
        "| GHOSTDAG K | {} | {} | {} |\n",
        standard.ghostdag_k, fast.ghostdag_k, ultra.ghostdag_k
    ));

    // Finality Depth
    table.push_str(&format!(
        "| Finality Depth | {} blocks | {} blocks | {} blocks |\n",
        standard.finality_depth, fast.finality_depth, ultra.finality_depth
    ));

    // ~Finality Time
    let std_time = standard.finality_depth * standard.target_block_time_ms / 1000;
    let fast_time = fast.finality_depth * fast.target_block_time_ms / 1000;
    let ultra_time = ultra.finality_depth * ultra.target_block_time_ms / 1000;
    table.push_str(&format!(
        "| ~Finality Time | ~{}s | ~{}s | ~{}s |\n",
        std_time, fast_time, ultra_time
    ));

    // Recommended Latency
    table.push_str(&format!(
        "| Rec. Latency | ≤{}ms | ≤{}ms | ≤{}ms |\n",
        standard.expected_delay_ms, fast.expected_delay_ms, ultra.expected_delay_ms
    ));

    // Estimated TPS
    table.push_str(&format!(
        "| Est. TPS @1000tx/block | {:.0} | {:.0} | {:.0} |\n",
        standard.estimate_tps(1000), fast.estimate_tps(1000), ultra.estimate_tps(1000)
    ));

    table
}

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

    #[test]
    fn test_bps_modes() {
        assert_eq!(BpsMode::Standard10.bps(), 10);
        assert_eq!(BpsMode::Fast32.bps(), 32);
        assert_eq!(BpsMode::Ultra100.bps(), 100);
        assert_eq!(BpsMode::Custom(50).bps(), 50);
    }

    #[test]
    fn test_block_times() {
        assert_eq!(BpsMode::Standard10.block_time_ms(), 100);
        assert_eq!(BpsMode::Fast32.block_time_ms(), 31); // 1000/32 = 31.25
        assert_eq!(BpsMode::Ultra100.block_time_ms(), 10);
    }

    #[test]
    fn test_standard_config() {
        let config = NetworkConfig::standard();
        assert_eq!(config.blocks_per_second, 10);
        assert_eq!(config.target_block_time_ms, 100);
        assert_eq!(config.ghostdag_k, 18);
    }

    #[test]
    fn test_fast_config() {
        let config = NetworkConfig::fast();
        assert_eq!(config.blocks_per_second, 32);
        assert!(config.target_block_time_ms <= 32); // ~31.25ms
        assert!(config.ghostdag_k > 18); // Should be higher than standard
    }

    #[test]
    fn test_ultra_config() {
        let config = NetworkConfig::ultra();
        assert_eq!(config.blocks_per_second, 100);
        assert_eq!(config.target_block_time_ms, 10);
        assert!(config.ghostdag_k > 100); // Much higher for ultra
    }

    #[test]
    fn test_calculate_k_for_latency() {
        let config = NetworkConfig::standard();

        // Low latency = lower k (but clamped to min)
        // 10ms: k = ceil(10 * 0.01 * 1.5) = 1 -> clamped to min (8)
        let k_low = config.calculate_k_for_latency(10.0);
        assert!(k_low >= config.dagknight_k_min);
        assert_eq!(k_low, config.dagknight_k_min); // Should hit min clamp

        // Medium latency = above min
        // 1000ms: k = ceil(10 * 1.0 * 1.5) = 15 -> above min
        let k_medium = config.calculate_k_for_latency(1000.0);
        assert!(k_medium > config.dagknight_k_min);

        // High latency = higher k
        // 3000ms: k = ceil(10 * 3.0 * 1.5) = 45 -> much higher
        let k_high = config.calculate_k_for_latency(3000.0);
        assert!(k_high > k_medium);
        assert!(k_high <= config.dagknight_k_max);
    }

    #[test]
    fn test_estimate_tps() {
        let standard = NetworkConfig::standard();
        let ultra = NetworkConfig::ultra();

        // At 100 tx/block:
        // Standard: 10 * 100 = 1000 TPS
        // Ultra: 100 * 100 = 10000 TPS
        assert_eq!(standard.estimate_tps(100), 1000.0);
        assert_eq!(ultra.estimate_tps(100), 10000.0);
    }

    #[test]
    fn test_latency_acceptable() {
        let config = NetworkConfig::standard(); // expects 100ms

        assert!(config.is_latency_acceptable(50));   // Good
        assert!(config.is_latency_acceptable(100));  // OK
        assert!(config.is_latency_acceptable(200));  // Still OK (2x limit)
        assert!(!config.is_latency_acceptable(300)); // Too high
    }

    #[test]
    fn test_custom_bps() {
        let config = NetworkConfig::with_bps(50);
        assert_eq!(config.blocks_per_second, 50);
        assert_eq!(config.target_block_time_ms, 20);
    }

    #[test]
    fn test_daa_params_conversion() {
        let config = NetworkConfig::fast();
        let daa_params = config.to_daa_params();

        assert_eq!(daa_params.target_time_ms, config.target_block_time_ms);
        assert_eq!(daa_params.window_size, config.daa_window_size);
    }

    #[test]
    fn test_comparison_table() {
        let table = bps_comparison_table();
        assert!(table.contains("Standard"));
        assert!(table.contains("Fast"));
        assert!(table.contains("Ultra"));
    }
}
