//! Performance Tracking for E2E Tests
//!
//! Provides comprehensive performance monitoring and metrics collection
//! for E2E tests including latency tracking, throughput measurement,
//! and performance regression detection.

use anyhow::{Context, Result};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tracing::{debug, info, warn};

/// Performance metric data point
#[derive(Debug, Clone)]
pub struct MetricPoint {
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub value: f64,
    pub tags: HashMap<String, String>,
}

/// Performance statistics for a metric
#[derive(Debug, Clone)]
pub struct MetricStats {
    pub count: u64,
    pub sum: f64,
    pub min: f64,
    pub max: f64,
    pub mean: f64,
    pub std_dev: f64,
    pub p50: f64,
    pub p95: f64,
    pub p99: f64,
}

/// Latency measurement helper
#[derive(Debug)]
pub struct LatencyTracker {
    start_time: Instant,
    operation_name: String,
}

impl LatencyTracker {
    /// Start tracking latency for an operation
    pub fn start(operation_name: &str) -> Self {
        Self {
            start_time: Instant::now(),
            operation_name: operation_name.to_string(),
        }
    }

    /// Stop tracking and return the duration
    pub fn stop(self) -> (String, Duration) {
        let duration = self.start_time.elapsed();
        (self.operation_name, duration)
    }
}

/// Performance tracker for E2E tests
#[derive(Debug)]
pub struct PerformanceTracker {
    session_id: String,
    metrics: Arc<Mutex<HashMap<String, Vec<MetricPoint>>>>,
    thresholds: HashMap<String, f64>,
    start_time: Instant,
}

impl PerformanceTracker {
    /// Create a new performance tracker
    pub fn new(session_id: &str) -> Result<Self> {
        info!(
            "📊 Initializing performance tracker for session: {}",
            session_id
        );

        let thresholds = Self::create_default_thresholds();

        Ok(Self {
            session_id: session_id.to_string(),
            metrics: Arc::new(Mutex::new(HashMap::new())),
            thresholds,
            start_time: Instant::now(),
        })
    }

    /// Record a metric value
    pub fn record_metric(&self, name: &str, value: f64) -> Result<()> {
        self.record_metric_with_tags(name, value, HashMap::new())
    }

    /// Record a metric value with tags
    pub fn record_metric_with_tags(
        &self,
        name: &str,
        value: f64,
        tags: HashMap<String, String>,
    ) -> Result<()> {
        let mut metrics = self.metrics.lock().unwrap();

        let metric_point = MetricPoint {
            timestamp: chrono::Utc::now(),
            value,
            tags,
        };

        metrics
            .entry(name.to_string())
            .or_default()
            .push(metric_point);

        debug!("📈 Recorded metric '{}' = {}", name, value);

        // Check threshold if configured
        if let Some(&threshold) = self.thresholds.get(name) {
            if value > threshold {
                warn!(
                    "⚠️ Metric '{}' ({}) exceeds threshold ({})",
                    name, value, threshold
                );
            }
        }

        Ok(())
    }

    /// Record latency metric from duration
    pub fn record_latency(&self, operation: &str, duration: Duration) -> Result<()> {
        let latency_ms = duration.as_millis() as f64;
        let mut tags = HashMap::new();
        tags.insert("operation".to_string(), operation.to_string());

        self.record_metric_with_tags("latency_ms", latency_ms, tags)
    }

    /// Start latency tracking
    pub fn start_latency_tracking(&self, operation: &str) -> LatencyTracker {
        LatencyTracker::start(operation)
    }

    /// Record throughput metric (operations per second)
    pub fn record_throughput(
        &self,
        operation: &str,
        operations: u64,
        duration: Duration,
    ) -> Result<()> {
        let throughput = operations as f64 / duration.as_secs_f64();
        let mut tags = HashMap::new();
        tags.insert("operation".to_string(), operation.to_string());

        self.record_metric_with_tags("throughput_ops_per_sec", throughput, tags)
    }

    /// Record error rate metric
    pub fn record_error_rate(&self, operation: &str, errors: u64, total: u64) -> Result<()> {
        let error_rate = if total > 0 {
            errors as f64 / total as f64
        } else {
            0.0
        };
        let mut tags = HashMap::new();
        tags.insert("operation".to_string(), operation.to_string());

        self.record_metric_with_tags("error_rate", error_rate, tags)
    }

    /// Get statistics for a metric
    pub fn get_metric_stats(&self, name: &str) -> Result<Option<MetricStats>> {
        let metrics = self.metrics.lock().unwrap();

        if let Some(points) = metrics.get(name) {
            if points.is_empty() {
                return Ok(None);
            }

            let values: Vec<f64> = points.iter().map(|p| p.value).collect();
            let stats = Self::calculate_stats(&values);
            Ok(Some(stats))
        } else {
            Ok(None)
        }
    }

    /// Get all metric names
    pub fn get_metric_names(&self) -> Vec<String> {
        let metrics = self.metrics.lock().unwrap();
        metrics.keys().cloned().collect()
    }

    /// Get metric values for a specific metric
    pub fn get_metric_values(&self, name: &str) -> Result<Vec<f64>> {
        let metrics = self.metrics.lock().unwrap();

        Ok(metrics
            .get(name)
            .map(|points| points.iter().map(|p| p.value).collect())
            .unwrap_or_default())
    }

    /// Generate performance report
    pub fn generate_report(&self) -> Result<PerformanceReport> {
        let metrics = self.metrics.lock().unwrap();
        let session_duration = self.start_time.elapsed();

        let mut metric_summaries = HashMap::new();
        let mut violations = Vec::new();

        for (name, points) in metrics.iter() {
            if !points.is_empty() {
                let values: Vec<f64> = points.iter().map(|p| p.value).collect();
                let stats = Self::calculate_stats(&values);

                // Check for threshold violations
                if let Some(&threshold) = self.thresholds.get(name) {
                    if stats.max > threshold {
                        violations.push(ThresholdViolation {
                            metric_name: name.clone(),
                            threshold,
                            actual_value: stats.max,
                            violation_type: "max_exceeded".to_string(),
                        });
                    }
                }

                metric_summaries.insert(name.clone(), stats);
            }
        }

        Ok(PerformanceReport {
            session_id: self.session_id.clone(),
            session_duration,
            metric_summaries,
            threshold_violations: violations,
            total_metrics_collected: metrics.len(),
            report_generated_at: chrono::Utc::now(),
        })
    }

    /// Export metrics to JSON
    pub fn export_metrics_json(&self) -> Result<String> {
        let metrics = self.metrics.lock().unwrap();
        let export_data = ExportData {
            session_id: self.session_id.clone(),
            session_duration_ms: self.start_time.elapsed().as_millis() as u64,
            metrics: metrics.clone(),
            exported_at: chrono::Utc::now(),
        };

        serde_json::to_string_pretty(&export_data).context("Failed to serialize metrics to JSON")
    }

    /// Save metrics to file
    pub fn save_metrics_to_file(&self, file_path: &str) -> Result<()> {
        let json_data = self.export_metrics_json()?;
        std::fs::write(file_path, json_data)
            .with_context(|| format!("Failed to write metrics to file: {}", file_path))?;

        info!("📁 Saved performance metrics to: {}", file_path);
        Ok(())
    }

    /// Set threshold for a metric
    pub fn set_threshold(&mut self, metric_name: &str, threshold: f64) {
        self.thresholds.insert(metric_name.to_string(), threshold);
        debug!("🎯 Set threshold for '{}': {}", metric_name, threshold);
    }

    /// Calculate statistics for values
    fn calculate_stats(values: &[f64]) -> MetricStats {
        if values.is_empty() {
            return MetricStats {
                count: 0,
                sum: 0.0,
                min: 0.0,
                max: 0.0,
                mean: 0.0,
                std_dev: 0.0,
                p50: 0.0,
                p95: 0.0,
                p99: 0.0,
            };
        }

        let mut sorted_values = values.to_vec();
        sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap());

        let count = values.len() as u64;
        let sum: f64 = values.iter().sum();
        let mean = sum / values.len() as f64;
        let min = sorted_values[0];
        let max = sorted_values[sorted_values.len() - 1];

        // Calculate standard deviation
        let variance: f64 =
            values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / values.len() as f64;
        let std_dev = variance.sqrt();

        // Calculate percentiles
        let p50 = Self::percentile(&sorted_values, 0.5);
        let p95 = Self::percentile(&sorted_values, 0.95);
        let p99 = Self::percentile(&sorted_values, 0.99);

        MetricStats {
            count,
            sum,
            min,
            max,
            mean,
            std_dev,
            p50,
            p95,
            p99,
        }
    }

    /// Calculate percentile from sorted values
    fn percentile(sorted_values: &[f64], percentile: f64) -> f64 {
        if sorted_values.is_empty() {
            return 0.0;
        }

        let index = (percentile * (sorted_values.len() - 1) as f64).round() as usize;
        sorted_values[index.min(sorted_values.len() - 1)]
    }

    /// Create default thresholds for common metrics
    fn create_default_thresholds() -> HashMap<String, f64> {
        let mut thresholds = HashMap::new();

        // Latency thresholds (milliseconds)
        thresholds.insert("latency_ms".to_string(), 1000.0); // 1 second
        thresholds.insert("order_submission_latency_ms".to_string(), 100.0);
        thresholds.insert("ml_inference_latency_ms".to_string(), 200.0);
        thresholds.insert("config_update_latency_ms".to_string(), 500.0);

        // Error rate thresholds
        thresholds.insert("error_rate".to_string(), 0.05); // 5%

        // Throughput thresholds (minimum ops/sec)
        thresholds.insert("throughput_ops_per_sec".to_string(), 1.0);

        thresholds
    }
}

/// Performance report structure
#[derive(Debug)]
pub struct PerformanceReport {
    pub session_id: String,
    pub session_duration: Duration,
    pub metric_summaries: HashMap<String, MetricStats>,
    pub threshold_violations: Vec<ThresholdViolation>,
    pub total_metrics_collected: usize,
    pub report_generated_at: chrono::DateTime<chrono::Utc>,
}

impl PerformanceReport {
    /// Print a summary of the performance report
    pub fn print_summary(&self) {
        info!("📊 Performance Report Summary");
        info!("Session ID: {}", self.session_id);
        info!("Session Duration: {:?}", self.session_duration);
        info!("Total Metrics: {}", self.total_metrics_collected);
        info!("Threshold Violations: {}", self.threshold_violations.len());

        if !self.threshold_violations.is_empty() {
            warn!("⚠️ Threshold Violations:");
            for violation in &self.threshold_violations {
                warn!(
                    "  {} exceeded threshold {} with value {}",
                    violation.metric_name, violation.threshold, violation.actual_value
                );
            }
        }

        info!("📈 Key Metrics:");
        for (name, stats) in &self.metric_summaries {
            if name.contains("latency") {
                info!(
                    "  {}: mean={:.2}ms, p95={:.2}ms, p99={:.2}ms",
                    name, stats.mean, stats.p95, stats.p99
                );
            } else if name.contains("throughput") {
                info!(
                    "  {}: mean={:.2} ops/sec, max={:.2} ops/sec",
                    name, stats.mean, stats.max
                );
            } else {
                info!(
                    "  {}: mean={:.2}, min={:.2}, max={:.2}",
                    name, stats.mean, stats.min, stats.max
                );
            }
        }
    }
}

/// Threshold violation information
#[derive(Debug)]
pub struct ThresholdViolation {
    pub metric_name: String,
    pub threshold: f64,
    pub actual_value: f64,
    pub violation_type: String,
}

/// Export data structure for JSON serialization
#[derive(Debug, serde::Serialize)]
struct ExportData {
    session_id: String,
    session_duration_ms: u64,
    metrics: HashMap<String, Vec<MetricPoint>>,
    exported_at: chrono::DateTime<chrono::Utc>,
}

impl serde::Serialize for MetricPoint {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeStruct;
        let mut state = serializer.serialize_struct("MetricPoint", 3)?;
        state.serialize_field("timestamp", &self.timestamp.to_rfc3339())?;
        state.serialize_field("value", &self.value)?;
        state.serialize_field("tags", &self.tags)?;
        state.end()
    }
}

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

    #[test]
    fn test_performance_tracker_creation() {
        let tracker = PerformanceTracker::new("test_session");
        assert!(tracker.is_ok());

        let tracker = tracker.unwrap();
        assert_eq!(tracker.session_id, "test_session");
    }

    #[test]
    fn test_metric_recording() {
        let tracker = PerformanceTracker::new("test").unwrap();

        let result = tracker.record_metric("test_metric", 42.0);
        assert!(result.is_ok());

        let values = tracker.get_metric_values("test_metric").unwrap();
        assert_eq!(values.len(), 1);
        assert_eq!(values[0], 42.0);
    }

    #[test]
    fn test_stats_calculation() {
        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
        let stats = PerformanceTracker::calculate_stats(&values);

        assert_eq!(stats.count, 5);
        assert_eq!(stats.min, 1.0);
        assert_eq!(stats.max, 5.0);
        assert_eq!(stats.mean, 3.0);
        assert_eq!(stats.p50, 3.0);
    }

    #[test]
    fn test_latency_tracker() {
        let tracker = LatencyTracker::start("test_operation");
        std::thread::sleep(Duration::from_millis(1));
        let (operation, duration) = tracker.stop();

        assert_eq!(operation, "test_operation");
        assert!(duration.as_millis() >= 1);
    }

    #[test]
    fn test_percentile_calculation() {
        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
        assert_eq!(PerformanceTracker::percentile(&values, 0.0), 1.0);
        assert_eq!(PerformanceTracker::percentile(&values, 0.5), 3.0);
        assert_eq!(PerformanceTracker::percentile(&values, 1.0), 5.0);
    }
}
