//! Ultra-low latency Prometheus metrics server for trading service
//!
//! This module provides a dedicated HTTP server for exposing Prometheus metrics
//! with minimal overhead on the critical trading path. The metrics are collected
//! asynchronously and served via a separate thread to avoid blocking trading operations.

use anyhow::{Context, Result};
use axum::{
    extract::State,
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::get,
    Router,
};
use std::time::Duration;
use tokio::net::TcpListener;
use tracing::{error, info, warn};
use trading_engine::metrics::{
    global_metrics_tracker, EnhancedHftLatencyTracker, PrometheusMetric,
};

/// Configuration for the metrics server
#[derive(Debug, Clone)]
pub struct MetricsServerConfig {
    pub bind_address: String,
    pub bind_port: u16,
    pub scrape_timeout_ms: u64,
    pub max_metrics_per_scrape: usize,
}

impl Default for MetricsServerConfig {
    fn default() -> Self {
        Self {
            bind_address: "0.0.0.0".to_string(),
            bind_port: 9001,
            scrape_timeout_ms: 500,
            max_metrics_per_scrape: 10000,
        }
    }
}

/// Metrics server state shared between handlers
#[derive(Clone)]
pub struct MetricsServerState {
    pub config: MetricsServerConfig,
    pub tracker: &'static EnhancedHftLatencyTracker,
    pub start_time: std::time::Instant,
}

/// Prometheus metrics server for trading service
pub struct TradingMetricsServer {
    config: MetricsServerConfig,
    state: MetricsServerState,
}

impl TradingMetricsServer {
    /// Create new metrics server with configuration
    pub fn new(config: MetricsServerConfig) -> Self {
        let tracker = global_metrics_tracker();
        let state = MetricsServerState {
            config: config.clone(),
            tracker,
            start_time: std::time::Instant::now(),
        };

        Self { config, state }
    }

    /// Start the metrics server (non-blocking)
    pub async fn start(self) -> Result<()> {
        let addr = format!("{}:{}", self.config.bind_address, self.config.bind_port);
        info!("Starting trading metrics server on {}", addr);

        // Create router with metrics endpoints
        let app = Router::new()
            .route("/metrics", get(metrics_handler))
            .route("/health", get(health_handler))
            .route("/ready", get(readiness_handler))
            .with_state(self.state);

        // Bind to address
        let listener = TcpListener::bind(&addr)
            .await
            .with_context(|| format!("Failed to bind to {}", addr))?;

        info!("Trading metrics server listening on {}", addr);

        // Start server
        axum::serve(listener, app)
            .await
            .with_context(|| "Trading metrics server error")?;

        Ok(())
    }

    /// Start server in background task
    pub fn spawn(self) -> tokio::task::JoinHandle<Result<()>> {
        tokio::spawn(async move { self.start().await })
    }
}

/// Main metrics endpoint handler - optimized for Prometheus scraping
async fn metrics_handler(State(state): State<MetricsServerState>) -> Response {
    let start = std::time::Instant::now();

    // Set timeout to prevent blocking
    let timeout_duration = Duration::from_millis(state.config.scrape_timeout_ms);

    let result = tokio::time::timeout(timeout_duration, async {
        // Export metrics from global tracker
        let metrics = state.tracker.export_prometheus_metrics();

        // Convert to Prometheus exposition format
        let mut output = String::new();

        // Add service metadata
        output.push_str("# Trading Service Metrics\n");
        output.push_str("# HELP trading_service_info Trading service information\n");
        output.push_str("# TYPE trading_service_info gauge\n");
        output.push_str(&format!(
            "trading_service_info{{version=\"{}\",service=\"trading\"}} 1\n",
            env!("CARGO_PKG_VERSION")
        ));

        // Add uptime metric
        let uptime_seconds = state.start_time.elapsed().as_secs_f64();
        output.push_str("# HELP trading_service_uptime_seconds Service uptime in seconds\n");
        output.push_str("# TYPE trading_service_uptime_seconds counter\n");
        output.push_str(&format!(
            "trading_service_uptime_seconds {}\n",
            uptime_seconds
        ));

        // Add scrape timing
        output.push_str(
            "# HELP trading_metrics_scrape_duration_seconds Time spent generating metrics\n",
        );
        output.push_str("# TYPE trading_metrics_scrape_duration_seconds gauge\n");

        // Add all trading metrics
        for metric in metrics
            .into_iter()
            .take(state.config.max_metrics_per_scrape)
        {
            output.push_str(&metric.format_prometheus());
        }

        // Add scrape duration at the end
        let scrape_duration = start.elapsed().as_secs_f64();
        output.push_str(&format!(
            "trading_metrics_scrape_duration_seconds {}\n",
            scrape_duration
        ));

        output
    })
    .await;

    match result {
        Ok(output) => {
            // Return metrics with appropriate content type
            (
                StatusCode::OK,
                [("Content-Type", "text/plain; version=0.0.4; charset=utf-8")],
                output,
            )
                .into_response()
        },
        Err(_) => {
            warn!(
                "Metrics scrape timed out after {}ms",
                state.config.scrape_timeout_ms
            );
            (StatusCode::REQUEST_TIMEOUT, "# Metrics scrape timed out\n").into_response()
        },
    }
}

/// Health check endpoint
async fn health_handler() -> Response {
    // Simple health check - always returns OK if service is running
    (StatusCode::OK, "OK").into_response()
}

/// Readiness check endpoint  
async fn readiness_handler(State(state): State<MetricsServerState>) -> Response {
    // Check if metrics system is ready
    let stats = state.tracker.get_enhanced_stats();

    // Consider ready if buffer utilization is reasonable
    if stats.buffer_stats.utilization_pct < 95.0 {
        (StatusCode::OK, "READY").into_response()
    } else {
        warn!(
            "Metrics buffer utilization high: {:.1}%",
            stats.buffer_stats.utilization_pct
        );
        (StatusCode::SERVICE_UNAVAILABLE, "NOT READY - buffer full").into_response()
    }
}

/// Extended metrics for trading-specific monitoring
pub fn get_trading_specific_metrics() -> Vec<PrometheusMetric> {
    let tracker = global_metrics_tracker();
    let stats = tracker.get_enhanced_stats();

    // Additional trading metrics not covered by the base tracker
    vec![
        PrometheusMetric {
            name: "trading_orders_submitted_total".to_string(),
            value: stats.latency_stats.measurements_count as f64,
            metric_type: trading_engine::metrics::MetricType::Counter,
            help: "Total number of orders submitted".to_string(),
            labels: vec![("service".to_string(), "trading".to_string())],
        },
        PrometheusMetric {
            name: "trading_buffer_capacity".to_string(),
            value: stats.buffer_stats.capacity as f64,
            metric_type: trading_engine::metrics::MetricType::Gauge,
            help: "Metrics buffer capacity".to_string(),
            labels: vec![("buffer".to_string(), "ring".to_string())],
        },
        PrometheusMetric {
            name: "trading_buffer_used".to_string(),
            value: stats.buffer_stats.used as f64,
            metric_type: trading_engine::metrics::MetricType::Gauge,
            help: "Metrics buffer current usage".to_string(),
            labels: vec![("buffer".to_string(), "ring".to_string())],
        },
    ]
}

/// Metrics collection task that runs in background
pub async fn start_metrics_collection_task() {
    let mut interval = tokio::time::interval(Duration::from_secs(1));

    loop {
        interval.tick().await;

        // This task can perform additional metrics collection if needed
        // For now, the metrics are collected on-demand during scraping

        // Check buffer health
        let tracker = global_metrics_tracker();
        let stats = tracker.get_enhanced_stats();

        if stats.buffer_stats.utilization_pct > 90.0 {
            warn!(
                "Metrics buffer utilization high: {:.1}%",
                stats.buffer_stats.utilization_pct
            );
        }

        if stats.buffer_stats.dropped_count > 0 {
            error!(
                "Metrics dropped: {} total",
                stats.buffer_stats.dropped_count
            );
        }
    }
}

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

    #[tokio::test]
    async fn test_metrics_server_creation() {
        let config = MetricsServerConfig::default();
        let server = TradingMetricsServer::new(config);

        // Verify server was created successfully
        assert_eq!(server.config.bind_port, 9001);
        assert_eq!(server.config.bind_address, "0.0.0.0");
    }

    #[tokio::test]
    async fn test_metrics_handler_timeout() {
        let config = MetricsServerConfig {
            scrape_timeout_ms: 1, // Very short timeout
            ..MetricsServerConfig::default()
        };

        let tracker = global_metrics_tracker();
        let state = MetricsServerState {
            config,
            tracker,
            start_time: std::time::Instant::now(),
        };

        // Test that handler respects timeout
        let response = timeout(Duration::from_millis(100), metrics_handler(State(state))).await;

        assert!(response.is_ok());
    }

    #[tokio::test]
    async fn test_trading_specific_metrics() {
        let metrics = get_trading_specific_metrics();
        assert!(!metrics.is_empty());

        // Verify we have expected metrics
        let metric_names: Vec<&str> = metrics.iter().map(|m| m.name.as_str()).collect();

        assert!(metric_names.contains(&"trading_orders_submitted_total"));
        assert!(metric_names.contains(&"trading_buffer_capacity"));
    }
}
