//! Metrics and telemetry for storage operations

use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;

use tracing::{debug, info, warn};

/// Storage operation metrics
#[derive(Debug, Clone)]
pub struct StorageMetrics {
    /// Operation counters
    pub operations: Arc<OperationMetrics>,
    /// Performance metrics
    pub performance: Arc<PerformanceMetrics>,
    /// Error metrics
    pub errors: Arc<ErrorMetrics>,
}

impl StorageMetrics {
    /// Create new metrics instance
    ///
    /// Create a new operation metrics instance
    pub fn new() -> Self {
        Self {
            operations: Arc::new(OperationMetrics::new()),
            performance: Arc::new(PerformanceMetrics::new()),
            errors: Arc::new(ErrorMetrics::new()),
        }
    }

    /// Record a storage operation
    pub fn record_operation(
        &self,
        operation: &str,
        provider: &str,
        duration: Duration,
        success: bool,
    ) {
        // Record operation count
        self.operations.increment(operation, provider);

        // Record performance metrics
        self.performance
            .record_duration(operation, provider, duration);

        // Record errors if failed
        if !success {
            self.errors
                .increment(operation, provider, "operation_failed");
        }

        debug!(
            "Storage operation recorded: {} on {} took {:?} (success: {})",
            operation, provider, duration, success
        );
    }

    /// Record data transfer metrics
    ///
    /// Record data transfer metrics for throughput calculation
    pub fn record_transfer(&self, operation: &str, provider: &str, bytes: u64, duration: Duration) {
        self.performance
            .record_transfer(operation, provider, bytes, duration);

        let throughput_mbps = (f64::from(u32::try_from(bytes).unwrap_or(0)) / (1024.0 * 1024.0))
            / duration.as_secs_f64();
        debug!(
            "Data transfer recorded: {} on {} - {} bytes in {:?} ({:.2} MB/s)",
            operation, provider, bytes, duration, throughput_mbps
        );
    }

    /// Record authentication metrics
    pub fn record_auth_event(&self, provider: &str, event: &str, success: bool) {
        self.operations.increment("auth", provider);

        if !success {
            self.errors.increment("auth", provider, event);
        }

        debug!(
            "Auth event recorded: {} on {} (success: {})",
            event, provider, success
        );
    }

    /// Get operation summary
    pub fn get_summary(&self) -> MetricsSummary {
        MetricsSummary {
            total_operations: self.operations.get_total(),
            total_errors: self.errors.get_total(),
            average_latency_ms: self.performance.get_average_latency_ms(),
            total_bytes_transferred: self.performance.get_total_bytes_transferred(),
            operations_by_type: self.operations.get_by_type(),
            errors_by_type: self.errors.get_by_type(),
            performance_percentiles: self.performance.get_percentiles(),
        }
    }

    /// Reset all metrics (useful for testing)
    ///
    /// Reset all operation counters
    pub fn reset(&self) {
        self.operations.reset();
        self.performance.reset();
        self.errors.reset();
        info!("Storage metrics reset");
    }
}

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

/// Operation counter metrics
#[derive(Debug)]
pub struct OperationMetrics {
    /// Total operations by type and provider
    counters: Arc<parking_lot::RwLock<HashMap<String, AtomicU64>>>,
}

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

impl OperationMetrics {
    /// Create a new OperationMetrics instance
    pub fn new() -> Self {
        Self {
            counters: Arc::new(parking_lot::RwLock::new(HashMap::new())),
        }
    }

    /// Increment the counter for a specific operation and provider
    pub fn increment(&self, operation: &str, provider: &str) {
        let key = format!("{}:{}", operation, provider);
        let counters = self.counters.read();

        if let Some(counter) = counters.get(&key) {
            counter.fetch_add(1, Ordering::Relaxed);
        } else {
            drop(counters);
            let mut counters = self.counters.write();
            counters
                .entry(key)
                .or_insert_with(|| AtomicU64::new(0))
                .fetch_add(1, Ordering::Relaxed);
        }
    }

    /// Get the count for a specific operation and provider
    pub fn get(&self, operation: &str, provider: &str) -> u64 {
        let key = format!("{}:{}", operation, provider);
        self.counters
            .read()
            .get(&key)
            .map(|c| c.load(Ordering::Relaxed))
            .unwrap_or(0)
    }

    /// Get the total count across all operations
    ///
    /// Get the total number of errors across all operations
    pub fn get_total(&self) -> u64 {
        self.counters
            .read()
            .values()
            .map(|c| c.load(Ordering::Relaxed))
            .sum()
    }

    /// Get all operation counts grouped by type
    ///
    /// Get all error counts grouped by type
    pub fn get_by_type(&self) -> HashMap<String, u64> {
        self.counters
            .read()
            .iter()
            .map(|(key, counter)| (key.clone(), counter.load(Ordering::Relaxed)))
            .collect()
    }

    /// Reset all operation counters to zero
    pub fn reset(&self) {
        let mut counters = self.counters.write();
        counters.clear();
    }
}

/// Performance metrics (latency, throughput)
#[derive(Debug)]
pub struct PerformanceMetrics {
    /// Latency measurements
    latencies: Arc<parking_lot::RwLock<HashMap<String, Vec<Duration>>>>,
    /// Bytes transferred
    bytes_transferred: Arc<parking_lot::RwLock<HashMap<String, AtomicU64>>>,
    /// Transfer durations for throughput calculation
    transfer_durations: Arc<parking_lot::RwLock<HashMap<String, Vec<Duration>>>>,
}

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

impl PerformanceMetrics {
    /// Create a new performance metrics instance
    pub fn new() -> Self {
        Self {
            latencies: Arc::new(parking_lot::RwLock::new(HashMap::new())),
            bytes_transferred: Arc::new(parking_lot::RwLock::new(HashMap::new())),
            transfer_durations: Arc::new(parking_lot::RwLock::new(HashMap::new())),
        }
    }

    /// Record the duration of an operation
    pub fn record_duration(&self, operation: &str, provider: &str, duration: Duration) {
        let key = format!("{}:{}", operation, provider);
        let mut latencies = self.latencies.write();
        latencies.entry(key.clone()).or_default().push(duration);

        // Keep only recent measurements (sliding window)
        if let Some(durations) = latencies.get_mut(&key) {
            if durations.len() > 1000 {
                durations.drain(..500); // Keep latest 500
            }
        }
    }

    /// Record a data transfer operation with bytes transferred and duration
    pub fn record_transfer(&self, operation: &str, provider: &str, bytes: u64, duration: Duration) {
        let key = format!("{}:{}", operation, provider);

        // Record bytes
        let bytes_map = self.bytes_transferred.read();
        if let Some(counter) = bytes_map.get(&key) {
            counter.fetch_add(bytes, Ordering::Relaxed);
        } else {
            drop(bytes_map);
            let mut bytes_map = self.bytes_transferred.write();
            bytes_map
                .entry(key.clone())
                .or_insert_with(|| AtomicU64::new(0))
                .fetch_add(bytes, Ordering::Relaxed);
        }

        // Record duration for throughput calculation
        let mut durations = self.transfer_durations.write();
        durations.entry(key).or_default().push(duration);
    }

    /// Get the average latency across all operations in milliseconds
    #[allow(clippy::integer_division)]
    pub fn get_average_latency_ms(&self) -> f64 {
        let latencies = self.latencies.read();
        let all_durations: Vec<Duration> = latencies.values().flatten().cloned().collect();

        if all_durations.is_empty() {
            0.0
        } else {
            let total_ms: f64 = all_durations
                .iter()
                .map(|d| f64::from(u32::try_from(d.as_millis()).unwrap_or(0)))
                .sum();
            total_ms / f64::from(u32::try_from(all_durations.len()).unwrap_or(1))
        }
    }

    /// Get the total number of bytes transferred across all operations
    pub fn get_total_bytes_transferred(&self) -> u64 {
        self.bytes_transferred
            .read()
            .values()
            .map(|c| c.load(Ordering::Relaxed))
            .sum()
    }

    /// Get latency percentiles for performance analysis
    ///
    /// # Panics
    /// The integer division `len * pct / 100` is safe because:
    /// - `len` is guaranteed to be > 0 (checked by early return if empty)
    /// - Division by 100 is a constant divisor, never zero
    pub fn get_percentiles(&self) -> PerformancePercentiles {
        let latencies = self.latencies.read();
        let mut all_durations: Vec<Duration> = latencies.values().flatten().cloned().collect();

        if all_durations.is_empty() {
            return PerformancePercentiles::default();
        }

        all_durations.sort();
        let len = all_durations.len();

        // Safe percentile calculation with bounds checking
        let get_percentile = |pct: usize| -> f64 {
            #[allow(clippy::integer_division)]
            let idx = ((len * pct) / 100).min(len.saturating_sub(1));
            all_durations
                .get(idx)
                .map(|d| f64::from(u32::try_from(d.as_millis()).unwrap_or(0)))
                .unwrap_or(0.0)
        };

        PerformancePercentiles {
            p50_ms: get_percentile(50),
            p90_ms: get_percentile(90),
            p95_ms: get_percentile(95),
            p99_ms: get_percentile(99),
            min_ms: all_durations
                .first()
                .map(|d| f64::from(u32::try_from(d.as_millis()).unwrap_or(0)))
                .unwrap_or(0.0),
            max_ms: all_durations
                .last()
                .map(|d| f64::from(u32::try_from(d.as_millis()).unwrap_or(0)))
                .unwrap_or(0.0),
        }
    }

    /// Reset all performance metrics
    pub fn reset(&self) {
        let mut latencies = self.latencies.write();
        latencies.clear();

        let mut bytes_transferred = self.bytes_transferred.write();
        bytes_transferred.clear();

        let mut transfer_durations = self.transfer_durations.write();
        transfer_durations.clear();
    }
}

/// Error tracking metrics
#[derive(Debug)]
pub struct ErrorMetrics {
    /// Error counters by type and provider
    error_counters: Arc<parking_lot::RwLock<HashMap<String, AtomicU64>>>,
}

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

impl ErrorMetrics {
    /// Create a new error metrics instance
    pub fn new() -> Self {
        Self {
            error_counters: Arc::new(parking_lot::RwLock::new(HashMap::new())),
        }
    }

    /// Increment the error counter for a specific operation, provider, and error type
    pub fn increment(&self, operation: &str, provider: &str, error_type: &str) {
        let key = format!("{}:{}:{}", operation, provider, error_type);
        let counters = self.error_counters.read();

        if let Some(counter) = counters.get(&key) {
            counter.fetch_add(1, Ordering::Relaxed);
        } else {
            drop(counters);
            let mut counters = self.error_counters.write();
            counters
                .entry(key)
                .or_insert_with(|| AtomicU64::new(0))
                .fetch_add(1, Ordering::Relaxed);
        }

        warn!(
            "Storage error recorded: {} on {} (type: {})",
            operation, provider, error_type
        );
    }

    /// Get the total number of errors across all types
    pub fn get_total(&self) -> u64 {
        self.error_counters
            .read()
            .values()
            .map(|c| c.load(Ordering::Relaxed))
            .sum()
    }

    /// Get error counts grouped by error type
    pub fn get_by_type(&self) -> HashMap<String, u64> {
        self.error_counters
            .read()
            .iter()
            .map(|(key, counter)| (key.clone(), counter.load(Ordering::Relaxed)))
            .collect()
    }

    /// Reset all error counters
    pub fn reset(&self) {
        let mut counters = self.error_counters.write();
        counters.clear();
    }
}

/// Performance percentiles for latency analysis
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct PerformancePercentiles {
    /// 50th percentile (median) latency in milliseconds
    pub p50_ms: f64,
    /// 90th percentile latency in milliseconds
    pub p90_ms: f64,
    /// 95th percentile latency in milliseconds
    pub p95_ms: f64,
    /// 99th percentile latency in milliseconds
    pub p99_ms: f64,
    /// Minimum latency in milliseconds
    pub min_ms: f64,
    /// Maximum latency in milliseconds
    pub max_ms: f64,
}

/// Comprehensive metrics summary
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[allow(clippy::module_name_repetitions)]
pub struct MetricsSummary {
    /// Total number of operations performed
    pub total_operations: u64,
    /// Total number of errors encountered
    pub total_errors: u64,
    /// Average latency across all operations in milliseconds
    pub average_latency_ms: f64,
    /// Total bytes transferred across all operations
    pub total_bytes_transferred: u64,
    /// Operation counts grouped by operation type
    pub operations_by_type: HashMap<String, u64>,
    /// Error counts grouped by error type
    pub errors_by_type: HashMap<String, u64>,
    /// Latency percentile statistics
    pub performance_percentiles: PerformancePercentiles,
}

/// Global storage metrics instance
static GLOBAL_METRICS: std::sync::OnceLock<StorageMetrics> = std::sync::OnceLock::new();

/// Get the global metrics instance
pub fn get_metrics() -> &'static StorageMetrics {
    GLOBAL_METRICS.get_or_init(StorageMetrics::new)
}

/// Macro for timing storage operations
#[macro_export]
macro_rules! time_storage_operation {
    ($operation:expr, $provider:expr, $code:block) => {{
        let start = std::time::Instant::now();
        let result = $code;
        let duration = start.elapsed();
        let success = result.is_ok();

        $crate::metrics::get_metrics().record_operation($operation, $provider, duration, success);

        result
    }};
}

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

    #[test]
    fn test_operation_metrics() {
        let metrics = OperationMetrics::new();

        metrics.increment("read", "s3");
        metrics.increment("read", "s3");
        metrics.increment("write", "local");

        assert_eq!(metrics.get("read", "s3"), 2);
        assert_eq!(metrics.get("write", "local"), 1);
        assert_eq!(metrics.get_total(), 3);
    }

    #[test]
    fn test_performance_metrics() {
        let metrics = PerformanceMetrics::new();

        metrics.record_duration("read", "s3", Duration::from_millis(100));
        metrics.record_duration("read", "s3", Duration::from_millis(200));
        metrics.record_transfer("upload", "s3", 1024, Duration::from_millis(50));

        assert_eq!(metrics.get_total_bytes_transferred(), 1024);
        let avg = metrics.get_average_latency_ms();
        assert!(avg > 0.0);
    }

    #[test]
    fn test_error_metrics() {
        let metrics = ErrorMetrics::new();

        metrics.increment("read", "s3", "timeout");
        metrics.increment("write", "local", "permission_denied");

        assert_eq!(metrics.get_total(), 2);
        let by_type = metrics.get_by_type();
        assert!(by_type.contains_key("read:s3:timeout"));
    }

    #[test]
    fn test_storage_metrics_integration() {
        let metrics = StorageMetrics::new();

        metrics.record_operation("read", "s3", Duration::from_millis(100), true);
        metrics.record_operation("write", "local", Duration::from_millis(50), false);
        metrics.record_transfer("upload", "s3", 2048, Duration::from_millis(100));

        let summary = metrics.get_summary();
        assert_eq!(summary.total_operations, 2);
        assert_eq!(summary.total_errors, 1);
        assert_eq!(summary.total_bytes_transferred, 2048);
    }
}
