//! High-precision latency recording with HDR histogram for sub-50μs validation
//!
//! This module provides comprehensive latency tracking for critical trading paths
//! with precise P50/P95/P99 percentile measurements using HDR histograms.

use hdrhistogram::Histogram;
use once_cell::sync::Lazy;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tracing::{debug, error, info, warn};

/// Global latency recorder instance
pub static LATENCY_RECORDER: Lazy<LatencyRecorder> = Lazy::new(LatencyRecorder::new);

/// Latency measurement categories for different trading operations
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LatencyCategory {
    /// Order submission from request to validation
    OrderSubmission,
    /// Risk validation and checks
    RiskValidation,
    /// Order processing and routing
    OrderProcessing,
    /// Market data ingestion
    MarketDataIngestion,
    /// Position updates
    PositionUpdate,
    /// End-to-end order latency
    EndToEndOrder,
    /// ML inference latency
    MLInference,
    /// Database operations
    DatabaseOperation,
    /// gRPC request processing
    GrpcProcessing,
}

impl LatencyCategory {
    /// Get the category name for logging and metrics
    pub fn name(&self) -> &'static str {
        match self {
            Self::OrderSubmission => "order_submission",
            Self::RiskValidation => "risk_validation",
            Self::OrderProcessing => "order_processing",
            Self::MarketDataIngestion => "market_data_ingestion",
            Self::PositionUpdate => "position_update",
            Self::EndToEndOrder => "end_to_end_order",
            Self::MLInference => "ml_inference",
            Self::DatabaseOperation => "database_operation",
            Self::GrpcProcessing => "grpc_processing",
        }
    }
}

/// High-precision latency recorder with HDR histogram
#[derive(Debug)]
pub struct LatencyRecorder {
    histograms: Arc<Mutex<std::collections::HashMap<LatencyCategory, Histogram<u64>>>>,
}

impl Default for LatencyRecorder {
    fn default() -> Self {
        Self {
            histograms: Arc::new(Mutex::new(std::collections::HashMap::new())),
        }
    }
}

impl LatencyRecorder {
    /// Create new latency recorder with optimized histogram settings
    pub fn new() -> Self {
        Self::default()
    }

    /// Record a latency measurement for the specified category
    #[allow(clippy::expect_used)] // Expect in critical error path is justified - system failure
    pub fn record(&self, category: LatencyCategory, latency_ns: u64) {
        let mut histograms = match self.histograms.lock() {
            Ok(h) => h,
            Err(e) => {
                warn!("Failed to acquire latency histogram lock (poisoned): {} - skipping measurement", e);
                return;
            },
        };

        let histogram = histograms.entry(category).or_insert_with(|| {
            // Create histogram optimized for sub-microsecond measurements
            // Range: 1ns to 10ms with 3 significant digits of precision
            match Histogram::new_with_bounds(1, 10_000_000, 3) {
                Ok(h) => h,
                Err(e) => {
                    warn!("Failed to create histogram for {:?}: {} - using default", category, e);
                    // Fallback: create a minimal histogram
                    // If this fails, we have a serious system issue and should log it
                    Histogram::new(3).unwrap_or_else(|err| {
                        error!("FATAL: Cannot create basic histogram for latency recording: {} - measurements will be dropped", err);
                        // Return a minimal histogram with default parameters (this should never fail)
                        Histogram::new(1).expect("Critical failure: basic histogram creation impossible")
                    })
                }
            }
        });

        if let Err(e) = histogram.record(latency_ns) {
            warn!("Failed to record latency for {:?}: {}", category, e);
        } else {
            debug!(
                "Recorded {}ns latency for category: {}",
                latency_ns,
                category.name()
            );
        }
    }

    /// Get latency statistics for a category
    pub fn get_stats(&self, category: LatencyCategory) -> Option<LatencyStats> {
        let histograms = match self.histograms.lock() {
            Ok(h) => h,
            Err(e) => {
                warn!(
                    "Failed to acquire latency histogram lock for stats: {} - returning None",
                    e
                );
                return None;
            },
        };

        histograms.get(&category).map(|histogram| LatencyStats {
            count: histogram.len(),
            min_ns: histogram.min(),
            max_ns: histogram.max(),
            mean_ns: u64::try_from(histogram.mean() as u128).unwrap_or(u64::MAX),
            p50_ns: histogram.value_at_quantile(0.50),
            p95_ns: histogram.value_at_quantile(0.95),
            p99_ns: histogram.value_at_quantile(0.99),
            p99_9_ns: histogram.value_at_quantile(0.999),
            stddev_ns: u64::try_from(histogram.stdev() as u128).unwrap_or(u64::MAX),
        })
    }

    /// Get comprehensive report of all latency categories
    pub fn generate_report(&self) -> LatencyReport {
        let histograms = match self.histograms.lock() {
            Ok(h) => h,
            Err(e) => {
                warn!("Failed to acquire latency histogram lock for report: {} - returning empty report", e);
                return LatencyReport {
                    timestamp: chrono::Utc::now(),
                    categories: Vec::new(),
                };
            },
        };

        let mut categories = Vec::new();

        for (&category, histogram) in histograms.iter() {
            if !histogram.is_empty() {
                let stats = LatencyStats {
                    count: histogram.len(),
                    min_ns: histogram.min(),
                    max_ns: histogram.max(),
                    mean_ns: histogram.mean() as u64,
                    p50_ns: histogram.value_at_quantile(0.50),
                    p95_ns: histogram.value_at_quantile(0.95),
                    p99_ns: histogram.value_at_quantile(0.99),
                    p99_9_ns: histogram.value_at_quantile(0.999),
                    stddev_ns: histogram.stdev() as u64,
                };

                let target_met_50us = stats.p99_ns <= 50_000; // Sub-50μs target
                categories.push(CategoryReport {
                    category,
                    stats,
                    target_met_50us,
                });
            }
        }

        LatencyReport {
            timestamp: chrono::Utc::now(),
            categories,
        }
    }

    /// Reset all histograms (useful for testing)
    pub fn reset(&self) {
        let mut histograms = match self.histograms.lock() {
            Ok(h) => h,
            Err(e) => {
                warn!(
                    "Failed to acquire latency histogram lock for reset: {} - skipping reset",
                    e
                );
                return;
            },
        };

        for histogram in histograms.values_mut() {
            histogram.reset();
        }
        info!("Latency recorder reset - all histograms cleared");
    }

    /// Log current statistics for all categories
    pub fn log_current_stats(&self) {
        let report = self.generate_report();
        info!(
            "=== LATENCY REPORT ({}) ===",
            report.timestamp.format("%Y-%m-%d %H:%M:%S")
        );

        for category_report in &report.categories {
            let stats = &category_report.stats;
            let target_status = if category_report.target_met_50us {
                "✅ PASS"
            } else {
                "❌ FAIL"
            };

            info!(
                "{}: {} | Count: {} | P50: {}μs | P95: {}μs | P99: {}μs | Target: {}μs",
                category_report.category.name(),
                target_status,
                stats.count,
                stats.p50_ns / 1_000,
                stats.p95_ns / 1_000,
                stats.p99_ns / 1_000,
                if category_report.target_met_50us {
                    1.0
                } else {
                    0.0
                }
            );
        }
    }
}

/// Latency statistics for a category
#[derive(Debug, Clone)]
pub struct LatencyStats {
    pub count: u64,
    pub min_ns: u64,
    pub max_ns: u64,
    pub mean_ns: u64,
    pub p50_ns: u64,
    pub p95_ns: u64,
    pub p99_ns: u64,
    pub p99_9_ns: u64,
    pub stddev_ns: u64,
}

impl LatencyStats {
    /// Check if latency meets sub-50μs target (P99 < 50μs)
    pub fn meets_sub_50us_target(&self) -> bool {
        self.p99_ns <= 50_000
    }

    /// Get P99 latency in microseconds
    pub fn p99_us(&self) -> f64 {
        self.p99_ns as f64 / 1_000.0
    }

    /// Get P95 latency in microseconds  
    pub fn p95_us(&self) -> f64 {
        self.p95_ns as f64 / 1_000.0
    }

    /// Get P50 latency in microseconds
    pub fn p50_us(&self) -> f64 {
        self.p50_ns as f64 / 1_000.0
    }
}

/// Individual category report
#[derive(Debug, Clone)]
pub struct CategoryReport {
    pub category: LatencyCategory,
    pub stats: LatencyStats,
    pub target_met_50us: bool,
}

/// Comprehensive latency report
#[derive(Debug, Clone)]
pub struct LatencyReport {
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub categories: Vec<CategoryReport>,
}

impl LatencyReport {
    /// Check if all categories meet the sub-50μs target
    pub fn all_targets_met(&self) -> bool {
        self.categories.iter().all(|c| c.target_met_50us)
    }

    /// Get count of categories that meet the target
    pub fn targets_met_count(&self) -> usize {
        self.categories.iter().filter(|c| c.target_met_50us).count()
    }

    /// Generate summary string
    pub fn summary(&self) -> String {
        format!(
            "Latency Report: {}/{} categories meet sub-50μs target",
            self.targets_met_count(),
            self.categories.len()
        )
    }
}

/// High-precision timing guard for automatic latency recording
pub struct TimingGuard {
    category: LatencyCategory,
    start_time: Instant,
}

impl TimingGuard {
    /// Start timing for the specified category
    pub fn start(category: LatencyCategory) -> Self {
        Self {
            category,
            start_time: Instant::now(),
        }
    }
}

impl Drop for TimingGuard {
    fn drop(&mut self) {
        let elapsed = self.start_time.elapsed();
        let latency_ns = u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX);
        LATENCY_RECORDER.record(self.category, latency_ns);
    }
}

/// Convenience macro for timing code blocks
#[macro_export]
macro_rules! time_operation {
    ($category:expr, $block:block) => {{
        let _guard = $crate::latency_recorder::TimingGuard::start($category);
        $block
    }};
}

/// Convenience function to record a duration
pub fn record_duration(category: LatencyCategory, duration: Duration) {
    let latency_ns = u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX);
    LATENCY_RECORDER.record(category, latency_ns);
}

/// Convenience function to time an async operation
pub async fn time_async<F, R>(category: LatencyCategory, operation: F) -> R
where
    F: std::future::Future<Output = R>,
{
    let start = Instant::now();
    let result = operation.await;
    let latency_ns = u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX);
    LATENCY_RECORDER.record(category, latency_ns);
    result
}

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

    #[test]
    fn test_latency_recording() {
        let recorder = LatencyRecorder::new();

        // Record some test latencies
        recorder.record(LatencyCategory::OrderSubmission, 25_000); // 25μs
        recorder.record(LatencyCategory::OrderSubmission, 35_000); // 35μs
        recorder.record(LatencyCategory::OrderSubmission, 45_000); // 45μs

        let stats = recorder
            .get_stats(LatencyCategory::OrderSubmission)
            .unwrap();
        assert_eq!(stats.count, 3);
        assert!(stats.meets_sub_50us_target());
        assert!(stats.p99_us() < 50.0);
    }

    #[test]
    fn test_timing_guard() {
        // TimingGuard uses the global LATENCY_RECORDER, so we query it directly
        // Clear any previous stats first by creating a new scope
        {
            let _guard = TimingGuard::start(LatencyCategory::RiskValidation);
            std::thread::sleep(Duration::from_micros(10)); // 10μs
        }

        // Query the global recorder that TimingGuard actually uses
        let stats = LATENCY_RECORDER.get_stats(LatencyCategory::RiskValidation);
        if let Some(stats) = stats {
            assert!(stats.count >= 1, "Expected at least 1 recorded latency");
        } else {
            panic!("Expected latency stats to be recorded by TimingGuard");
        }
    }

    #[tokio::test]
    async fn test_async_timing() {
        let result = time_async(LatencyCategory::DatabaseOperation, async {
            tokio::time::sleep(Duration::from_micros(5)).await;
            42
        })
        .await;

        assert_eq!(result, 42);
        let stats = LATENCY_RECORDER.get_stats(LatencyCategory::DatabaseOperation);
        assert!(stats.is_some());
    }
}
