//! Network latency tracking for DAGKnight adaptive consensus.
//!
//! This module tracks observed network propagation delays to enable
//! DAGKnight's adaptive k parameter calculation. Unlike GHOSTDAG's
//! fixed k assumption, DAGKnight adjusts based on real-world conditions.
//!
//! # Key Metrics
//!
//! - **Block propagation delay**: Time from block creation to network-wide visibility
//! - **Anticone growth rate**: How quickly anticones grow (indicates network latency)
//! - **Confirmation velocity**: Rate at which blocks achieve probabilistic finality

use parking_lot::RwLock;
use std::collections::VecDeque;
use std::time::{Duration, Instant};

use crate::BlockId;

/// Maximum number of latency samples to keep for moving average.
const MAX_LATENCY_SAMPLES: usize = 1000;

/// Default network delay assumption in milliseconds.
const DEFAULT_DELAY_MS: u64 = 100;

/// Minimum delay to prevent unrealistic values.
const MIN_DELAY_MS: u64 = 10;

/// Maximum delay to cap at reasonable network conditions.
const MAX_DELAY_MS: u64 = 5000;

/// Latency sample from observed block propagation.
#[derive(Clone, Debug)]
pub struct LatencySample {
    /// Block that was observed.
    pub block_id: BlockId,
    /// Timestamp when block was first seen locally.
    pub local_time: Instant,
    /// Timestamp from block header (creation time).
    pub block_time_ms: u64,
    /// Observed propagation delay in milliseconds.
    pub delay_ms: u64,
    /// Anticone size at time of observation.
    pub anticone_size: usize,
}

/// Rolling statistics for latency measurements.
#[derive(Clone, Debug, Default)]
pub struct LatencyStats {
    /// Mean propagation delay (ms).
    pub mean_delay_ms: f64,
    /// Standard deviation of delay (ms).
    pub std_dev_ms: f64,
    /// 95th percentile delay (ms).
    pub p95_delay_ms: f64,
    /// 99th percentile delay (ms).
    pub p99_delay_ms: f64,
    /// Average anticone growth rate (blocks per second).
    pub anticone_growth_rate: f64,
    /// Number of samples in current window.
    pub sample_count: usize,
}

/// Network latency tracker for DAGKnight.
///
/// Collects block propagation samples and computes statistics
/// used for adaptive k calculation and confirmation time estimation.
pub struct LatencyTracker {
    /// Recent latency samples.
    samples: RwLock<VecDeque<LatencySample>>,
    /// Cached statistics (recomputed on demand).
    stats_cache: RwLock<Option<(Instant, LatencyStats)>>,
    /// Cache validity duration.
    cache_ttl: Duration,
}

impl LatencyTracker {
    /// Creates a new latency tracker.
    pub fn new() -> Self {
        Self {
            samples: RwLock::new(VecDeque::with_capacity(MAX_LATENCY_SAMPLES)),
            stats_cache: RwLock::new(None),
            cache_ttl: Duration::from_secs(5),
        }
    }

    /// Creates a latency tracker with custom cache TTL.
    pub fn with_cache_ttl(cache_ttl: Duration) -> Self {
        Self {
            samples: RwLock::new(VecDeque::with_capacity(MAX_LATENCY_SAMPLES)),
            stats_cache: RwLock::new(None),
            cache_ttl,
        }
    }

    /// Records a new block observation.
    ///
    /// # Arguments
    /// * `block_id` - Hash of the observed block
    /// * `block_time_ms` - Timestamp from block header (Unix ms)
    /// * `anticone_size` - Number of blocks in the anticone at observation time
    pub fn record_block(
        &self,
        block_id: BlockId,
        block_time_ms: u64,
        anticone_size: usize,
    ) {
        let local_time = Instant::now();
        let now_ms = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_millis() as u64)
            .unwrap_or(0);

        // Calculate observed delay (clamp to valid range)
        let delay_ms = if now_ms > block_time_ms {
            (now_ms - block_time_ms).clamp(MIN_DELAY_MS, MAX_DELAY_MS)
        } else {
            // Clock skew - use default
            DEFAULT_DELAY_MS
        };

        let sample = LatencySample {
            block_id,
            local_time,
            block_time_ms,
            delay_ms,
            anticone_size,
        };

        let mut samples = self.samples.write();
        if samples.len() >= MAX_LATENCY_SAMPLES {
            samples.pop_front();
        }
        samples.push_back(sample);

        // Invalidate stats cache
        *self.stats_cache.write() = None;
    }

    /// Records a latency sample directly (for testing or external measurements).
    pub fn record_sample(&self, sample: LatencySample) {
        let mut samples = self.samples.write();
        if samples.len() >= MAX_LATENCY_SAMPLES {
            samples.pop_front();
        }
        samples.push_back(sample);

        // Invalidate stats cache
        *self.stats_cache.write() = None;
    }

    /// Gets current latency statistics.
    ///
    /// Uses cached value if available and fresh, otherwise recomputes.
    pub fn get_stats(&self) -> LatencyStats {
        // Check cache
        {
            let cache = self.stats_cache.read();
            if let Some((cached_at, stats)) = cache.as_ref() {
                if cached_at.elapsed() < self.cache_ttl {
                    return stats.clone();
                }
            }
        }

        // Recompute statistics
        let stats = self.compute_stats();

        // Update cache
        *self.stats_cache.write() = Some((Instant::now(), stats.clone()));

        stats
    }

    /// Computes latency statistics from current samples.
    fn compute_stats(&self) -> LatencyStats {
        let samples = self.samples.read();

        if samples.is_empty() {
            return LatencyStats {
                mean_delay_ms: DEFAULT_DELAY_MS as f64,
                std_dev_ms: 0.0,
                p95_delay_ms: DEFAULT_DELAY_MS as f64,
                p99_delay_ms: DEFAULT_DELAY_MS as f64,
                anticone_growth_rate: 0.0,
                sample_count: 0,
            };
        }

        let n = samples.len();

        // Collect delay values
        let mut delays: Vec<f64> = samples.iter().map(|s| s.delay_ms as f64).collect();
        delays.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));

        // Mean
        let sum: f64 = delays.iter().sum();
        let mean = sum / n as f64;

        // Standard deviation
        let variance: f64 = delays.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / n as f64;
        let std_dev = variance.sqrt();

        // Percentiles
        let p95_idx = ((n as f64 * 0.95) as usize).min(n - 1);
        let p99_idx = ((n as f64 * 0.99) as usize).min(n - 1);

        // Anticone growth rate (blocks per second)
        let anticone_growth_rate = if n > 1 {
            let first = samples.front().unwrap();
            let last = samples.back().unwrap();
            let time_span_secs = last.local_time.duration_since(first.local_time).as_secs_f64();

            if time_span_secs > 0.0 {
                let total_anticone_growth: usize = samples.iter().map(|s| s.anticone_size).sum();
                total_anticone_growth as f64 / time_span_secs / n as f64
            } else {
                0.0
            }
        } else {
            0.0
        };

        LatencyStats {
            mean_delay_ms: mean,
            std_dev_ms: std_dev,
            p95_delay_ms: delays[p95_idx],
            p99_delay_ms: delays[p99_idx],
            anticone_growth_rate,
            sample_count: n,
        }
    }

    /// Estimates the network delay for adaptive k calculation.
    ///
    /// Uses P95 delay as a conservative estimate to ensure security.
    pub fn estimated_network_delay(&self) -> Duration {
        let stats = self.get_stats();
        Duration::from_millis(stats.p95_delay_ms as u64)
    }

    /// Estimates the expected anticone size for a given delay.
    ///
    /// Used by DAGKnight to predict confirmation times.
    pub fn expected_anticone_size(&self, delay: Duration) -> usize {
        let stats = self.get_stats();
        let delay_secs = delay.as_secs_f64();

        // Anticone grows at approximately anticone_growth_rate blocks/second
        (stats.anticone_growth_rate * delay_secs).ceil() as usize
    }

    /// Gets the number of samples currently tracked.
    pub fn sample_count(&self) -> usize {
        self.samples.read().len()
    }

    /// Clears all samples and resets the tracker.
    pub fn reset(&self) {
        self.samples.write().clear();
        *self.stats_cache.write() = None;
    }
}

impl Default for LatencyTracker {
    fn default() -> Self {
        Self::new()
    }
}

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

#[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)
    }

    #[test]
    fn test_empty_tracker() {
        let tracker = LatencyTracker::new();
        let stats = tracker.get_stats();

        assert_eq!(stats.sample_count, 0);
        assert_eq!(stats.mean_delay_ms, DEFAULT_DELAY_MS as f64);
    }

    #[test]
    fn test_record_samples() {
        let tracker = LatencyTracker::new();
        let now_ms = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        // Record some samples with varying delays
        for i in 0..10 {
            tracker.record_block(
                make_block_id(i),
                now_ms - (50 + i as u64 * 10), // 50-140ms delays
                i as usize,
            );
        }

        let stats = tracker.get_stats();
        assert_eq!(stats.sample_count, 10);
        assert!(stats.mean_delay_ms >= 50.0);
        assert!(stats.mean_delay_ms <= 150.0);
    }

    #[test]
    fn test_sample_limit() {
        let tracker = LatencyTracker::new();
        let now_ms = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        // Record more than MAX_LATENCY_SAMPLES
        for i in 0..MAX_LATENCY_SAMPLES + 100 {
            tracker.record_block(make_block_id(i as u8), now_ms - 100, 0);
        }

        assert_eq!(tracker.sample_count(), MAX_LATENCY_SAMPLES);
    }

    #[test]
    fn test_estimated_delay() {
        let tracker = LatencyTracker::new();
        let now_ms = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        // Record samples with ~100ms delay
        for i in 0..50 {
            tracker.record_block(make_block_id(i), now_ms - 100, 0);
        }

        let delay = tracker.estimated_network_delay();
        assert!(delay.as_millis() >= 90);
        assert!(delay.as_millis() <= 200);
    }

    #[test]
    fn test_reset() {
        let tracker = LatencyTracker::new();
        let now_ms = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        tracker.record_block(make_block_id(0), now_ms - 100, 0);
        assert_eq!(tracker.sample_count(), 1);

        tracker.reset();
        assert_eq!(tracker.sample_count(), 0);
    }
}
