#![forbid(unsafe_code)]
#![allow(dead_code)]

//! Metrics collection and monitoring for the lifecycle API server.
//!
//! This module provides:
//! - Prometheus metrics collection
//! - Request duration tracking with configurable histogram buckets
//! - Request count tracking
//! - Metrics endpoint handler (/metrics)
//!
//! Metrics are collected using the prometheus crate and exposed in Prometheus format.

use axum::body::Body;
use axum::http::Request;
use axum::{middleware::Next, response::IntoResponse};
use prometheus::{Encoder, HistogramOpts, HistogramVec, IntCounter, IntCounterVec, Registry};
use std::sync::Arc;
use std::time::Instant;
use thiserror::Error;

/// Metrics errors
#[derive(Error, Debug)]
pub enum MetricsError {
    /// Error from Prometheus client
    #[error("Prometheus error: {0}")]
    PrometheusError(String),
}

/// Metrics collector for Prometheus
/// Manages all metrics collection and provides a handler to expose metrics
#[derive(Clone, Debug)]
pub struct MetricsCollector {
    /// Total HTTP requests by method and path
    #[allow(dead_code)]
    http_requests_total: IntCounterVec,

    /// HTTP request latency histogram (in seconds)
    #[allow(dead_code)]
    http_request_duration_seconds: HistogramVec,

    /// Total requests that succeeded
    #[allow(dead_code)]
    requests_success: IntCounter,

    /// Total requests that failed
    #[allow(dead_code)]
    requests_errors: IntCounter,

    /// Prometheus registry
    registry: Arc<Registry>,
}

impl MetricsCollector {
    /// Create a new metrics collector with configured histogram buckets
    ///
    /// # Arguments
    ///
    /// * `buckets` - Histogram buckets in milliseconds, converted to seconds for Prometheus
    ///
    /// # Returns
    ///
    /// A new MetricsCollector instance
    pub fn new(buckets_ms: &[f64]) -> Result<Self, MetricsError> {
        let registry = Registry::new();

        // Convert milliseconds to seconds for Prometheus (standard is seconds)
        let buckets_secs: Vec<f64> = buckets_ms.iter().map(|ms| ms / 1000.0).collect();

        // HTTP requests total (counter)
        let http_requests_total = IntCounterVec::new(
            prometheus::Opts::new("http_requests_total", "Total HTTP requests"),
            &["method", "path", "status"],
        )
        .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;
        registry
            .register(Box::new(http_requests_total.clone()))
            .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;

        // HTTP request duration histogram
        let http_request_duration_seconds = HistogramVec::new(
            HistogramOpts::new(
                "http_request_duration_seconds",
                "HTTP request latency in seconds",
            )
            .buckets(buckets_secs),
            &["method", "path"],
        )
        .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;
        registry
            .register(Box::new(http_request_duration_seconds.clone()))
            .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;

        // Success/error counters
        let requests_success =
            IntCounter::new("requests_success_total", "Total successful requests")
                .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;
        registry
            .register(Box::new(requests_success.clone()))
            .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;

        let requests_errors = IntCounter::new("requests_error_total", "Total failed requests")
            .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;
        registry
            .register(Box::new(requests_errors.clone()))
            .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;

        Ok(Self {
            http_requests_total,
            http_request_duration_seconds,
            requests_success,
            requests_errors,
            registry: Arc::new(registry),
        })
    }

    /// Record a successful request
    pub fn record_success(&self) {
        self.requests_success.inc();
    }

    /// Record a failed request
    pub fn record_error(&self) {
        self.requests_errors.inc();
    }

    /// Record request metrics (duration, method, path)
    pub fn record_request(&self, method: &str, path: &str, status: u16, duration_secs: f64) {
        let status_str = status.to_string();
        self.http_requests_total
            .with_label_values(&[method, path, &status_str])
            .inc();

        self.http_request_duration_seconds
            .with_label_values(&[method, path])
            .observe(duration_secs);
    }

    /// Get metrics in Prometheus format
    pub fn gather_metrics(&self) -> Result<String, MetricsError> {
        let metric_families = self.registry.gather();

        let encoder = prometheus::TextEncoder::new();
        let mut buf = Vec::new();
        encoder
            .encode(&metric_families, &mut buf)
            .map_err(|e| MetricsError::PrometheusError(e.to_string()))?;

        String::from_utf8(buf)
            .map_err(|e| MetricsError::PrometheusError(format!("UTF-8 error: {}", e)))
    }
}

/// Middleware to track request metrics
///
/// This middleware:
/// - Records request start time
/// - Tracks request duration
/// - Records HTTP method and path
/// - Records response status code
pub async fn metrics_middleware(req: Request<Body>, next: Next) -> impl IntoResponse {
    let method = req.method().to_string();
    let path = req.uri().path().to_string();
    let start = Instant::now();

    let response = next.run(req).await;

    let duration = start.elapsed();
    let status = response.status().as_u16();

    // Log metrics if requested (optional, based on config)
    tracing::debug!(
        method = %method,
        path = %path,
        status = status,
        duration_ms = duration.as_millis(),
        "HTTP request processed"
    );

    response
}

/// Handler to expose metrics in Prometheus format
///
/// # Arguments
///
/// * `State(state)` - Application state containing metrics collector
///
/// # Returns
///
/// Prometheus-format metrics as plain text
/// Metrics handler for API implementations
///
/// This should be implemented by each API using the shared library:
/// ```ignore
/// use shared_api_lib::state::AppState;
/// use shared_api_lib::metrics::MetricsCollector;
/// use axum::extract::State;
/// use axum::http::StatusCode;
/// use axum::response::IntoResponse;
///
/// pub async fn metrics_handler<DB: Send + Sync>(
///     State(state): State<AppState<DB>>,
/// ) -> impl IntoResponse {
///     match state.metrics.as_ref() {
///         Some(metrics) => match metrics.gather_metrics() {
///             Ok(m) => (StatusCode::OK, m).into_response(),
///             Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Failed to gather metrics").into_response(),
///         },
///         None => (StatusCode::NOT_FOUND, "Metrics not enabled").into_response(),
///     }
/// }
/// ```
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_metrics_collector_creation() {
        let buckets = vec![1.0, 5.0, 10.0, 50.0, 100.0];
        let collector = MetricsCollector::new(&buckets);
        assert!(collector.is_ok());
    }

    #[test]
    fn test_record_success() {
        let collector = MetricsCollector::new(&[1.0, 5.0, 10.0]).unwrap();
        collector.record_success();
        collector.record_success();
        // Metrics are recorded; we verify they were incremented by checking the registry
        let metrics = collector.gather_metrics().unwrap();
        assert!(metrics.contains("requests_success_total 2"));
    }

    #[test]
    fn test_record_error() {
        let collector = MetricsCollector::new(&[1.0, 5.0, 10.0]).unwrap();
        collector.record_error();
        let metrics = collector.gather_metrics().unwrap();
        assert!(metrics.contains("requests_error_total 1"));
    }

    #[test]
    fn test_record_request() {
        let collector = MetricsCollector::new(&[1.0, 5.0, 10.0]).unwrap();
        collector.record_request("GET", "/api/health", 200, 0.05);
        let metrics = collector.gather_metrics().unwrap();

        // Should contain histogram entry for the request
        assert!(metrics.contains("http_request_duration_seconds_bucket"));
        assert!(metrics.contains("method=\"GET\""));
        assert!(metrics.contains("path=\"/api/health\""));
    }

    #[test]
    fn test_gather_metrics_format() {
        let collector = MetricsCollector::new(&[1.0, 5.0, 10.0]).unwrap();
        collector.record_success();

        let metrics = collector.gather_metrics().unwrap();

        // Verify Prometheus format
        assert!(metrics.contains("# HELP"));
        assert!(metrics.contains("# TYPE"));
        assert!(metrics.contains("requests_success_total"));
    }

    #[test]
    fn test_bucket_conversion() {
        let buckets_ms = vec![1.0, 5.0, 10.0, 100.0];
        let collector = MetricsCollector::new(&buckets_ms).unwrap();

        // Record a request with 50ms duration
        collector.record_request("POST", "/api/projects", 201, 0.050);

        let metrics = collector.gather_metrics().unwrap();

        // Verify buckets are in seconds (0.001, 0.005, 0.010, 0.100)
        assert!(metrics.contains("0.001"));
        assert!(metrics.contains("0.01")); // 10ms = 0.01s
    }

    #[test]
    fn test_multiple_requests() {
        let collector = MetricsCollector::new(&[1.0, 10.0, 50.0]).unwrap();

        // Record multiple requests with different paths
        collector.record_request("GET", "/api/projects", 200, 0.020);
        collector.record_request("GET", "/api/projects", 200, 0.025);
        collector.record_request("POST", "/api/projects", 201, 0.050);

        let metrics = collector.gather_metrics().unwrap();

        // Verify all requests are tracked
        assert!(metrics.contains("method=\"GET\""));
        assert!(metrics.contains("method=\"POST\""));
        assert!(metrics.contains("path=\"/api/projects\""));
    }

    #[test]
    fn test_status_code_tracking() {
        let collector = MetricsCollector::new(&[1.0, 5.0]).unwrap();

        // Record requests with different status codes
        collector.record_request("GET", "/api/health", 200, 0.010);
        collector.record_request("GET", "/api/missing", 404, 0.005);
        collector.record_request("POST", "/api/error", 500, 0.100);

        let metrics = collector.gather_metrics().unwrap();

        // Verify status codes are tracked
        assert!(metrics.contains("status=\"200\""));
        assert!(metrics.contains("status=\"404\""));
        assert!(metrics.contains("status=\"500\""));
    }
}
