//! Metrics collection and reporting for load tests

use hdrhistogram::Histogram;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};

#[derive(Debug)]
pub struct LoadTestMetrics {
    pub total_requests: AtomicU64,
    pub successful_requests: AtomicU64,
    pub failed_requests: AtomicU64,
    pub latency_us: parking_lot::Mutex<Histogram<u64>>,
    pub memory_samples: parking_lot::Mutex<Vec<u64>>,
    pub start_time: Instant,
}

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

impl LoadTestMetrics {
    pub fn new() -> Self {
        Self {
            total_requests: AtomicU64::new(0),
            successful_requests: AtomicU64::new(0),
            failed_requests: AtomicU64::new(0),
            latency_us: parking_lot::Mutex::new(
                Histogram::<u64>::new_with_bounds(1_u64, 60_000_000, 3).unwrap(),
            ),
            memory_samples: parking_lot::Mutex::new(Vec::new()),
            start_time: Instant::now(),
        }
    }

    pub fn record_request(&self, duration: Duration, success: bool) {
        self.total_requests.fetch_add(1_u64, Ordering::Relaxed);
        if success {
            self.successful_requests.fetch_add(1_u64, Ordering::Relaxed);
        } else {
            self.failed_requests.fetch_add(1_u64, Ordering::Relaxed);
        }

        let mut hist = self.latency_us.lock();
        let _ = hist.record(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX));
    }

    pub fn record_memory(&self, bytes: u64) {
        self.memory_samples.lock().push(bytes);
    }

    pub fn to_report(&self, test_name: &str) -> LoadTestReport {
        let elapsed = self.start_time.elapsed();
        let total = self.total_requests.load(Ordering::Relaxed);
        let successful = self.successful_requests.load(Ordering::Relaxed);
        let failed = self.failed_requests.load(Ordering::Relaxed);

        let hist = self.latency_us.lock();
        let p50 = hist.value_at_quantile(0.50);
        let p95 = hist.value_at_quantile(0.95);
        let p99 = hist.value_at_quantile(0.99);
        let max = hist.max();

        let throughput = total as f64 / elapsed.as_secs_f64();
        let error_rate = if total > 0 {
            (failed as f64 / total as f64) * 100.0
        } else {
            0.0
        };

        let memory_samples = self.memory_samples.lock();
        let avg_memory_mb = if !memory_samples.is_empty() {
            memory_samples.iter().sum::<u64>() as f64 / memory_samples.len() as f64 / 1_048_576.0
        } else {
            0.0
        };

        LoadTestReport {
            test_name: test_name.to_string(),
            test_duration: elapsed,
            total_requests: total,
            successful_requests: successful,
            failed_requests: failed,
            throughput_per_sec: throughput,
            error_rate_percent: error_rate,
            latency_p50_us: p50,
            latency_p95_us: p95,
            latency_p99_us: p99,
            latency_max_us: max,
            avg_memory_mb,
            custom_metrics: HashMap::new(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct LoadTestReport {
    pub test_name: String,
    pub test_duration: Duration,
    pub total_requests: u64,
    pub successful_requests: u64,
    pub failed_requests: u64,
    pub throughput_per_sec: f64,
    pub error_rate_percent: f64,
    pub latency_p50_us: u64,
    pub latency_p95_us: u64,
    pub latency_p99_us: u64,
    pub latency_max_us: u64,
    pub avg_memory_mb: f64,
    pub custom_metrics: HashMap<String, f64>,
}

impl LoadTestReport {
    pub fn new(test_name: &str) -> Self {
        Self {
            test_name: test_name.to_string(),
            test_duration: Duration::from_secs(0),
            total_requests: 0_u64,
            successful_requests: 0_u64,
            failed_requests: 0_u64,
            throughput_per_sec: 0.0,
            error_rate_percent: 0.0,
            latency_p50_us: 0_u64,
            latency_p95_us: 0_u64,
            latency_p99_us: 0_u64,
            latency_max_us: 0_u64,
            avg_memory_mb: 0.0,
            custom_metrics: HashMap::new(),
        }
    }

    pub fn add_custom_metric(&mut self, name: &str, value: f64) {
        self.custom_metrics.insert(name.to_string(), value);
    }

    pub fn merge(&mut self, other: &LoadTestReport) {
        self.total_requests += other.total_requests;
        self.successful_requests += other.successful_requests;
        self.failed_requests += other.failed_requests;
        self.test_duration = self.test_duration.max(other.test_duration);

        // Average metrics
        self.throughput_per_sec = (self.throughput_per_sec + other.throughput_per_sec) / 2.0;
        self.error_rate_percent = (self.error_rate_percent + other.error_rate_percent) / 2.0;
        self.avg_memory_mb = (self.avg_memory_mb + other.avg_memory_mb) / 2.0;

        // Max latency metrics
        self.latency_p50_us = self.latency_p50_us.max(other.latency_p50_us);
        self.latency_p95_us = self.latency_p95_us.max(other.latency_p95_us);
        self.latency_p99_us = self.latency_p99_us.max(other.latency_p99_us);
        self.latency_max_us = self.latency_max_us.max(other.latency_max_us);

        // Merge custom metrics
        for (key, value) in &other.custom_metrics {
            self.custom_metrics.insert(key.clone(), *value);
        }
    }

    pub fn to_markdown(&self) -> String {
        let mut output = String::new();

        output.push_str("# Load Test Report: Wave 120 Agent 5\n\n");
        let test_name = &self.test_name;
        output.push_str(&format!("## Test: {test_name}\n\n"));
        let test_duration = self.test_duration;
        output.push_str(&format!("**Duration**: {test_duration:?}\n\n"));

        output.push_str("### Summary\n\n");
        let total_requests = self.total_requests;
        let successful_requests = self.successful_requests;
        let failed_requests = self.failed_requests;
        output.push_str(&format!("- **Total Requests**: {total_requests} (Success: {successful_requests}, Failed: {failed_requests})\n"));
        let throughput_per_sec = self.throughput_per_sec;
        output.push_str(&format!(
            "- **Throughput**: {throughput_per_sec:.2} req/sec\n"
        ));
        let error_rate_percent = self.error_rate_percent;
        output.push_str(&format!("- **Error Rate**: {error_rate_percent:.2}%\n\n"));

        output.push_str("### Latency Distribution (microseconds)\n\n");
        let latency_p50_us = self.latency_p50_us;
        output.push_str(&format!("- **P50**: {latency_p50_us} μs\n"));
        let latency_p95_us = self.latency_p95_us;
        output.push_str(&format!("- **P95**: {latency_p95_us} μs\n"));
        let latency_p99_us = self.latency_p99_us;
        output.push_str(&format!("- **P99**: {latency_p99_us} μs\n"));
        let latency_max_us = self.latency_max_us;
        output.push_str(&format!("- **Max**: {latency_max_us} μs\n\n"));

        output.push_str("### Resource Usage\n\n");
        let avg_memory_mb = self.avg_memory_mb;
        output.push_str(&format!("- **Memory**: {avg_memory_mb:.2} MB (avg)\n\n"));

        if !self.custom_metrics.is_empty() {
            output.push_str("### Custom Metrics\n\n");
            for (key, value) in &self.custom_metrics {
                output.push_str(&format!("- **{key}**: {value:.2}\n"));
            }
            output.push('\n');
        }

        output.push_str("---\n\n");
        let generated = chrono::Utc::now().to_rfc3339();
        output.push_str(&format!("*Generated: {generated}*\n"));

        output
    }

}
