// Prometheus metrics registry for Foxhunt HFT Trading System
//
// This module provides a global metrics registry using lazy_static for thread-safe
// metric registration and updates. All metrics follow Prometheus naming conventions:
// - Metric names: foxhunt_<component>_<metric>_<unit>
// - Counter suffix: _total
// - Duration suffix: _seconds
// - Size suffix: _bytes

use lazy_static::lazy_static;
use prometheus::{
    Counter, CounterVec, Gauge, GaugeVec, Histogram, HistogramOpts, HistogramVec,
    Opts, Registry,
};
use std::collections::HashMap;
use std::sync::Mutex;

lazy_static! {
    // Global Prometheus registry
    pub static ref REGISTRY: Registry = Registry::new();

    // Global counter storage
    static ref COUNTERS: Mutex<HashMap<String, Counter>> = Mutex::new(HashMap::new());

    /// Global counter vec storage (with labels)
    static ref COUNTER_VECS: Mutex<HashMap<String, CounterVec>> = Mutex::new(HashMap::new());

    /// Global gauge storage
    static ref GAUGES: Mutex<HashMap<String, Gauge>> = Mutex::new(HashMap::new());

    /// Global gauge vec storage (with labels)
    static ref GAUGE_VECS: Mutex<HashMap<String, GaugeVec>> = Mutex::new(HashMap::new());

    /// Global histogram storage
    static ref HISTOGRAMS: Mutex<HashMap<String, Histogram>> = Mutex::new(HashMap::new());

    /// Global histogram vec storage (with labels)
    static ref HISTOGRAM_VECS: Mutex<HashMap<String, HistogramVec>> = Mutex::new(HashMap::new());
}

/// Register a counter metric
///
/// Counters can only increase and are used for cumulative metrics like
/// total requests, total errors, etc.
///
/// # Arguments
/// * `name` - Metric name (should follow foxhunt_\<component\>_\<metric\>_total convention)
/// * `help` - Help text describing the metric
///
/// # Returns
/// Result indicating success or error
pub fn register_counter(name: &str, help: &str) -> Result<(), prometheus::Error> {
    let mut counters = (*COUNTERS).lock().map_err(|_| {
        prometheus::Error::Msg("Failed to lock COUNTERS mutex".to_string())
    })?;

    // Check if already registered
    if counters.contains_key(name) {
        return Ok(());
    }

    let opts = Opts::new(name, help);
    let counter = Counter::with_opts(opts)?;
    REGISTRY.register(Box::new(counter.clone()))?;
    counters.insert(name.to_string(), counter);

    Ok(())
}

/// Register a counter vec metric with labels
///
/// # Arguments
/// * `name` - Metric name
/// * `help` - Help text
/// * `labels` - Label names
pub fn register_counter_vec(
    name: &str,
    help: &str,
    labels: &[&str],
) -> Result<(), prometheus::Error> {
    let mut counter_vecs = (*COUNTER_VECS).lock().map_err(|_| {
        prometheus::Error::Msg("Failed to lock COUNTER_VECS mutex".to_string())
    })?;

    if counter_vecs.contains_key(name) {
        return Ok(());
    }

    let opts = Opts::new(name, help);
    let counter_vec = CounterVec::new(opts, labels)?;
    REGISTRY.register(Box::new(counter_vec.clone()))?;
    counter_vecs.insert(name.to_string(), counter_vec);

    Ok(())
}

/// Register a gauge metric
///
/// Gauges can go up and down and are used for metrics like active connections,
/// memory usage, queue depth, etc.
///
/// # Arguments
/// * `name` - Metric name (should follow foxhunt_\<component\>_\<metric\> convention)
/// * `help` - Help text describing the metric
pub fn register_gauge(name: &str, help: &str) -> Result<(), prometheus::Error> {
    let mut gauges = (*GAUGES).lock().map_err(|_| {
        prometheus::Error::Msg("Failed to lock GAUGES mutex".to_string())
    })?;

    if gauges.contains_key(name) {
        return Ok(());
    }

    let opts = Opts::new(name, help);
    let gauge = Gauge::with_opts(opts)?;
    REGISTRY.register(Box::new(gauge.clone()))?;
    gauges.insert(name.to_string(), gauge);

    Ok(())
}

/// Register a gauge vec metric with labels
pub fn register_gauge_vec(
    name: &str,
    help: &str,
    labels: &[&str],
) -> Result<(), prometheus::Error> {
    let mut gauge_vecs = (*GAUGE_VECS).lock().map_err(|_| {
        prometheus::Error::Msg("Failed to lock GAUGE_VECS mutex".to_string())
    })?;

    if gauge_vecs.contains_key(name) {
        return Ok(());
    }

    let opts = Opts::new(name, help);
    let gauge_vec = GaugeVec::new(opts, labels)?;
    REGISTRY.register(Box::new(gauge_vec.clone()))?;
    gauge_vecs.insert(name.to_string(), gauge_vec);

    Ok(())
}

/// Register a histogram metric
///
/// Histograms track distributions of values (typically latencies) in configurable buckets.
///
/// # Arguments
/// * `name` - Metric name (should include _seconds for durations, _bytes for sizes)
/// * `help` - Help text describing the metric
/// * `buckets` - Bucket boundaries for histogram
pub fn register_histogram(
    name: &str,
    help: &str,
    buckets: Vec<f64>,
) -> Result<(), prometheus::Error> {
    let mut histograms = (*HISTOGRAMS).lock().map_err(|_| {
        prometheus::Error::Msg("Failed to lock HISTOGRAMS mutex".to_string())
    })?;

    if histograms.contains_key(name) {
        return Ok(());
    }

    let opts = HistogramOpts::new(name, help).buckets(buckets);
    let histogram = Histogram::with_opts(opts)?;
    REGISTRY.register(Box::new(histogram.clone()))?;
    histograms.insert(name.to_string(), histogram);

    Ok(())
}

/// Register a histogram vec metric with labels
pub fn register_histogram_vec(
    name: &str,
    help: &str,
    labels: &[&str],
    buckets: Vec<f64>,
) -> Result<(), prometheus::Error> {
    let mut histogram_vecs = (*HISTOGRAM_VECS).lock().map_err(|_| {
        prometheus::Error::Msg("Failed to lock HISTOGRAM_VECS mutex".to_string())
    })?;

    if histogram_vecs.contains_key(name) {
        return Ok(());
    }

    let opts = HistogramOpts::new(name, help).buckets(buckets);
    let histogram_vec = HistogramVec::new(opts, labels)?;
    REGISTRY.register(Box::new(histogram_vec.clone()))?;
    histogram_vecs.insert(name.to_string(), histogram_vec);

    Ok(())
}

// Note: Summary metrics have been removed from prometheus 0.14+
// Use Histogram with appropriate buckets for quantile approximations instead

/// Increment a counter by a given amount
pub fn increment_counter(name: &str, value: f64) {
    if let Ok(counters) = (*COUNTERS).lock() {
        if let Some(counter) = counters.get(name) {
            counter.inc_by(value);
        }
    }
}

/// Increment a counter vec by label values
pub fn increment_counter_vec(name: &str, labels: &[&str], value: f64) {
    if let Ok(counter_vecs) = (*COUNTER_VECS).lock() {
        if let Some(counter_vec) = counter_vecs.get(name) {
            counter_vec.with_label_values(labels).inc_by(value);
        }
    }
}

/// Set a gauge to a specific value
pub fn set_gauge(name: &str, value: f64) {
    if let Ok(gauges) = (*GAUGES).lock() {
        if let Some(gauge) = gauges.get(name) {
            gauge.set(value);
        }
    }
}

/// Increment a gauge
pub fn inc_gauge(name: &str, value: f64) {
    if let Ok(gauges) = (*GAUGES).lock() {
        if let Some(gauge) = gauges.get(name) {
            gauge.add(value);
        }
    }
}

/// Decrement a gauge
pub fn dec_gauge(name: &str, value: f64) {
    if let Ok(gauges) = (*GAUGES).lock() {
        if let Some(gauge) = gauges.get(name) {
            gauge.sub(value);
        }
    }
}

/// Set a gauge vec by label values
pub fn set_gauge_vec(name: &str, labels: &[&str], value: f64) {
    if let Ok(gauge_vecs) = (*GAUGE_VECS).lock() {
        if let Some(gauge_vec) = gauge_vecs.get(name) {
            gauge_vec.with_label_values(labels).set(value);
        }
    }
}

/// Record a histogram observation
pub fn observe_histogram(name: &str, value: f64) {
    if let Ok(histograms) = (*HISTOGRAMS).lock() {
        if let Some(histogram) = histograms.get(name) {
            histogram.observe(value);
        }
    }
}

/// Record a histogram vec observation by label values
pub fn observe_histogram_vec(name: &str, labels: &[&str], value: f64) {
    if let Ok(histogram_vecs) = (*HISTOGRAM_VECS).lock() {
        if let Some(histogram_vec) = histogram_vecs.get(name) {
            histogram_vec.with_label_values(labels).observe(value);
        }
    }
}

// Summary observation functions removed - use histograms instead

/// Get the current value of a counter (for testing)
pub fn get_counter_value(name: &str) -> Option<f64> {
    if let Ok(counters) = (*COUNTERS).lock() {
        counters.get(name).map(|c| c.get())
    } else {
        None
    }
}

/// Get the current value of a gauge (for testing)
pub fn get_gauge_value(name: &str) -> Option<f64> {
    if let Ok(gauges) = (*GAUGES).lock() {
        gauges.get(name).map(|g| g.get())
    } else {
        None
    }
}

/// Get metrics in Prometheus text format
pub fn gather_metrics() -> String {
    use prometheus::Encoder;
    let encoder = prometheus::TextEncoder::new();
    let metric_families = REGISTRY.gather();

    let mut buffer = Vec::new();
    if encoder.encode(&metric_families, &mut buffer).is_ok() {
        String::from_utf8(buffer).unwrap_or_default()
    } else {
        String::new()
    }
}

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

    #[test]
    fn test_registry_initialization() {
        // Verify that the global registry exists
        let metrics = gather_metrics();
        assert!(metrics.is_empty() || !metrics.is_empty(), "Registry should be initialized");
    }

    #[test]
    fn test_gather_metrics_format() {
        // Register a test metric
        let _ = register_counter("test_gather_counter_total", "Test gather");
        increment_counter("test_gather_counter_total", 5.0);

        let metrics = gather_metrics();
        assert!(
            metrics.contains("test_gather_counter_total"),
            "Metrics output should contain registered metrics"
        );
    }
}
