//! Scaling benchmark infrastructure.
//!
//! Provides timing instrumentation for the scan pipeline:
//! files/sec indexed, per-priority breakdown, and query latency tracking.

use std::collections::HashMap;
use std::sync::RwLock;
use std::time::{Duration, Instant};

use tracing::debug;

/// Aggregated scan benchmark results.
#[derive(Debug, Clone)]
pub struct ScanBenchmark {
    /// Total wall-clock time for the scan.
    pub total_duration: Duration,
    /// Per-priority timing breakdown.
    pub priority_timings: HashMap<String, PriorityTiming>,
    /// Overall files/sec throughput.
    pub files_per_sec: f64,
    /// Total files processed.
    pub total_files: usize,
}

/// Timing data for a single priority level.
#[derive(Debug, Clone, Default)]
pub struct PriorityTiming {
    pub file_count: usize,
    pub total_duration: Duration,
    pub avg_duration_ms: f64,
}

/// Builder that accumulates timing data during a scan.
pub struct ScanBenchmarkBuilder {
    start: Instant,
    timings: HashMap<String, Vec<Duration>>,
}

impl ScanBenchmarkBuilder {
    pub fn new() -> Self {
        Self {
            start: Instant::now(),
            timings: HashMap::new(),
        }
    }

    /// Record the time taken to index a single file at a given priority.
    pub fn record_file(&mut self, priority: &str, duration: Duration) {
        self.timings
            .entry(priority.to_string())
            .or_default()
            .push(duration);
    }

    /// Finalize the benchmark with totals.
    pub fn finish(self) -> ScanBenchmark {
        let total_duration = self.start.elapsed();
        let total_files: usize = self.timings.values().map(|v| v.len()).sum();

        let files_per_sec = if total_duration.as_secs_f64() > 0.0 {
            total_files as f64 / total_duration.as_secs_f64()
        } else {
            0.0
        };

        let priority_timings: HashMap<String, PriorityTiming> = self
            .timings
            .into_iter()
            .map(|(priority, durations)| {
                let count = durations.len();
                let total: Duration = durations.iter().sum();
                let avg_ms = if count > 0 {
                    total.as_secs_f64() * 1000.0 / count as f64
                } else {
                    0.0
                };
                (
                    priority,
                    PriorityTiming {
                        file_count: count,
                        total_duration: total,
                        avg_duration_ms: avg_ms,
                    },
                )
            })
            .collect();

        debug!(
            total_files,
            files_per_sec = format!("{:.1}", files_per_sec),
            elapsed_ms = total_duration.as_millis() as u64,
            "Scan benchmark complete"
        );

        ScanBenchmark {
            total_duration,
            priority_timings,
            files_per_sec,
            total_files,
        }
    }
}

/// Tracks query latency percentiles over a rolling window.
pub struct LatencyTracker {
    samples: RwLock<Vec<Duration>>,
    max_samples: usize,
}

impl LatencyTracker {
    pub fn new(max_samples: usize) -> Self {
        Self {
            samples: RwLock::new(Vec::with_capacity(max_samples.min(10_000))),
            max_samples,
        }
    }

    /// Record a query latency sample.
    pub fn record(&self, latency: Duration) {
        if let Ok(mut samples) = self.samples.write() {
            if samples.len() >= self.max_samples {
                samples.remove(0); // Drop oldest
            }
            samples.push(latency);
        }
    }

    /// Compute latency percentiles (p50, p90, p95, p99).
    pub fn percentiles(&self) -> LatencyPercentiles {
        let samples = match self.samples.read() {
            Ok(s) => s.clone(),
            Err(_) => return LatencyPercentiles::default(),
        };

        if samples.is_empty() {
            return LatencyPercentiles::default();
        }

        let mut sorted: Vec<u128> = samples.iter().map(|d| d.as_micros()).collect();
        sorted.sort_unstable();

        let pct = |p: f64| -> Duration {
            let idx = ((p / 100.0) * (sorted.len() as f64 - 1.0)).round() as usize;
            Duration::from_micros(sorted[idx.min(sorted.len() - 1)] as u64)
        };

        LatencyPercentiles {
            count: sorted.len(),
            p50: pct(50.0),
            p90: pct(90.0),
            p95: pct(95.0),
            p99: pct(99.0),
            min: Duration::from_micros(sorted[0] as u64),
            max: Duration::from_micros(*sorted.last().unwrap() as u64),
        }
    }
}

/// Computed latency percentile values.
#[derive(Debug, Clone, Default)]
pub struct LatencyPercentiles {
    pub count: usize,
    pub p50: Duration,
    pub p90: Duration,
    pub p95: Duration,
    pub p99: Duration,
    pub min: Duration,
    pub max: Duration,
}

impl LatencyPercentiles {
    /// Format as a human-readable summary string.
    pub fn summary(&self) -> String {
        if self.count == 0 {
            return "no samples".to_string();
        }
        format!(
            "n={} p50={}ms p90={}ms p95={}ms p99={}ms min={}ms max={}ms",
            self.count,
            self.p50.as_millis(),
            self.p90.as_millis(),
            self.p95.as_millis(),
            self.p99.as_millis(),
            self.min.as_millis(),
            self.max.as_millis(),
        )
    }
}

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

    #[test]
    fn test_scan_benchmark_builder() {
        let mut builder = ScanBenchmarkBuilder::new();
        builder.record_file("critical", Duration::from_millis(10));
        builder.record_file("critical", Duration::from_millis(20));
        builder.record_file("important", Duration::from_millis(5));
        builder.record_file("reference", Duration::from_millis(2));

        let bench = builder.finish();
        assert_eq!(bench.total_files, 4);
        assert!(bench.files_per_sec > 0.0);
        assert_eq!(bench.priority_timings["critical"].file_count, 2);
        assert_eq!(bench.priority_timings["important"].file_count, 1);
        assert_eq!(bench.priority_timings["reference"].file_count, 1);
    }

    #[test]
    fn test_scan_benchmark_empty() {
        let builder = ScanBenchmarkBuilder::new();
        let bench = builder.finish();
        assert_eq!(bench.total_files, 0);
        assert!(bench.priority_timings.is_empty());
    }

    #[test]
    fn test_latency_tracker_basic() {
        let tracker = LatencyTracker::new(100);
        tracker.record(Duration::from_millis(10));
        tracker.record(Duration::from_millis(20));
        tracker.record(Duration::from_millis(30));

        let pcts = tracker.percentiles();
        assert_eq!(pcts.count, 3);
        assert!(pcts.min <= pcts.p50);
        assert!(pcts.p50 <= pcts.p90);
        assert!(pcts.p90 <= pcts.p99);
        assert!(pcts.p99 <= pcts.max);
    }

    #[test]
    fn test_latency_tracker_single_sample() {
        let tracker = LatencyTracker::new(100);
        tracker.record(Duration::from_millis(42));
        let pcts = tracker.percentiles();
        assert_eq!(pcts.count, 1);
        assert_eq!(pcts.p50.as_millis(), 42);
        assert_eq!(pcts.p99.as_millis(), 42);
    }

    #[test]
    fn test_latency_tracker_empty() {
        let tracker = LatencyTracker::new(100);
        let pcts = tracker.percentiles();
        assert_eq!(pcts.count, 0);
        assert_eq!(pcts.summary(), "no samples");
    }

    #[test]
    fn test_latency_tracker_overflow_eviction() {
        let tracker = LatencyTracker::new(5);
        for i in 0..10 {
            tracker.record(Duration::from_millis(i));
        }
        let pcts = tracker.percentiles();
        assert_eq!(pcts.count, 5); // max_samples enforced
    }

    #[test]
    fn test_latency_percentiles_summary() {
        let tracker = LatencyTracker::new(100);
        for i in 1..=100 {
            tracker.record(Duration::from_millis(i));
        }
        let pcts = tracker.percentiles();
        let summary = pcts.summary();
        assert!(summary.contains("n=100"));
        assert!(summary.contains("p50="));
        assert!(summary.contains("p99="));
    }

    #[test]
    fn test_priority_timing_default() {
        let pt = PriorityTiming::default();
        assert_eq!(pt.file_count, 0);
        assert_eq!(pt.avg_duration_ms, 0.0);
    }

    #[test]
    fn test_scan_benchmark_timing_averages() {
        let mut builder = ScanBenchmarkBuilder::new();
        builder.record_file("full", Duration::from_millis(100));
        builder.record_file("full", Duration::from_millis(200));
        let bench = builder.finish();
        let avg = bench.priority_timings["full"].avg_duration_ms;
        assert!((avg - 150.0).abs() < 1.0);
    }
}
