//! Module de métriques Prometheus pour le Stream Server

use prometheus::{Counter, Encoder, Gauge, Histogram, Registry, TextEncoder};
use std::sync::Arc;
use tokio::sync::RwLock;

/// Métriques Prometheus pour le streaming
#[derive(Clone)]
pub struct StreamMetrics {
    /// Total des requêtes HTTP
    pub requests_total: Counter,

    /// Durée des streams en secondes
    pub stream_duration: Histogram,

    /// Nombre de streams actifs
    pub active_streams: Gauge,

    /// Total des streams créés
    pub streams_total: Counter,

    /// Total des streams terminés
    pub streams_ended: Counter,

    /// Bytes streamés total
    pub bytes_streamed_total: Counter,

    /// Erreurs de streaming
    pub stream_errors_total: Counter,

    /// Durée des requêtes HTTP
    pub http_request_duration: Histogram,

    /// Registry Prometheus pour exporter les métriques
    pub registry: Arc<Registry>,
}

impl StreamMetrics {
    /// Crée une nouvelle instance de métriques
    pub fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let registry = Registry::new();

        // Création des métriques
        let requests_total = Counter::with_opts(
            prometheus::Opts::new("requests_total", "Total des requêtes HTTP")
                .namespace("veza_stream"),
        )?;

        let stream_duration = Histogram::with_opts(
            prometheus::HistogramOpts::new(
                "stream_duration_seconds",
                "Durée des streams en secondes",
            )
            .namespace("veza_stream")
            .buckets(vec![0.1, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0, 300.0, 600.0]),
        )?;

        let active_streams = Gauge::with_opts(
            prometheus::Opts::new("active_streams", "Nombre de streams actifs")
                .namespace("veza_stream"),
        )?;

        let streams_total = Counter::with_opts(
            prometheus::Opts::new("streams_total", "Total des streams créés")
                .namespace("veza_stream"),
        )?;

        let streams_ended = Counter::with_opts(
            prometheus::Opts::new("streams_ended_total", "Total des streams terminés")
                .namespace("veza_stream"),
        )?;

        let bytes_streamed_total = Counter::with_opts(
            prometheus::Opts::new("bytes_streamed_total", "Total des bytes streamés")
                .namespace("veza_stream"),
        )?;

        let stream_errors_total = Counter::with_opts(
            prometheus::Opts::new("stream_errors_total", "Total des erreurs de streaming")
                .namespace("veza_stream"),
        )?;

        let http_request_duration = Histogram::with_opts(
            prometheus::HistogramOpts::new(
                "http_request_duration_seconds",
                "Durée des requêtes HTTP en secondes",
            )
            .namespace("veza_stream")
            .buckets(vec![
                0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
            ]),
        )?;

        // Enregistrement des métriques
        registry.register(Box::new(requests_total.clone()))?;
        registry.register(Box::new(stream_duration.clone()))?;
        registry.register(Box::new(active_streams.clone()))?;
        registry.register(Box::new(streams_total.clone()))?;
        registry.register(Box::new(streams_ended.clone()))?;
        registry.register(Box::new(bytes_streamed_total.clone()))?;
        registry.register(Box::new(stream_errors_total.clone()))?;
        registry.register(Box::new(http_request_duration.clone()))?;

        Ok(Self {
            requests_total,
            stream_duration,
            active_streams,
            streams_total,
            streams_ended,
            bytes_streamed_total,
            stream_errors_total,
            http_request_duration,
            registry: Arc::new(registry),
        })
    }

    /// Exporte les métriques au format Prometheus
    pub fn gather(&self) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
        let encoder = TextEncoder::new();
        let metric_families = self.registry.gather();
        let mut buffer = Vec::new();
        encoder.encode(&metric_families, &mut buffer)?;
        Ok(String::from_utf8(buffer)?)
    }

    /// Incrémente le compteur de requêtes
    pub fn inc_requests(&self) {
        self.requests_total.inc();
    }

    /// Enregistre la durée d'un stream
    pub fn record_stream_duration(&self, duration: f64) {
        self.stream_duration.observe(duration);
    }

    /// Incrémente le nombre de streams actifs
    pub fn inc_active_streams(&self) {
        self.active_streams.inc();
    }

    /// Décrémente le nombre de streams actifs
    pub fn dec_active_streams(&self) {
        self.active_streams.dec();
    }

    /// Définit le nombre de streams actifs
    pub fn set_active_streams(&self, count: f64) {
        self.active_streams.set(count);
    }

    /// Incrémente le compteur de streams créés
    pub fn inc_streams_total(&self) {
        self.streams_total.inc();
    }

    /// Incrémente le compteur de streams terminés
    pub fn inc_streams_ended(&self) {
        self.streams_ended.inc();
    }

    /// Ajoute des bytes au compteur de bytes streamés
    pub fn add_bytes_streamed(&self, bytes: f64) {
        self.bytes_streamed_total.inc_by(bytes);
    }

    /// Incrémente le compteur d'erreurs de streaming
    pub fn inc_stream_errors(&self) {
        self.stream_errors_total.inc();
    }

    /// Enregistre la durée d'une requête HTTP
    pub fn record_http_request_duration(&self, duration: f64) {
        self.http_request_duration.observe(duration);
    }
}

impl Default for StreamMetrics {
    fn default() -> Self {
        Self::new().expect("Failed to create default StreamMetrics")
    }
}

/// Gestionnaire global des métriques
pub struct MetricsManager {
    metrics: Arc<RwLock<StreamMetrics>>,
}

impl MetricsManager {
    /// Crée un nouveau gestionnaire de métriques
    pub fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let metrics = StreamMetrics::new()?;
        Ok(Self {
            metrics: Arc::new(RwLock::new(metrics)),
        })
    }

    /// Obtient une référence aux métriques
    pub async fn get(&self) -> impl std::ops::Deref<Target = StreamMetrics> + '_ {
        self.metrics.read().await
    }

    /// Exporte les métriques au format Prometheus
    pub async fn gather(&self) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
        let metrics = self.metrics.read().await;
        metrics.gather()
    }
}

impl Default for MetricsManager {
    fn default() -> Self {
        Self::new().expect("Failed to create default MetricsManager")
    }
}

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

    #[test]
    fn test_stream_metrics_creation() {
        let metrics = StreamMetrics::new();
        assert!(metrics.is_ok());
    }

    #[test]
    fn test_metrics_operations() {
        let metrics = StreamMetrics::new().unwrap();

        // Test compteurs
        metrics.inc_requests();
        assert_eq!(metrics.requests_total.get() as u64, 1);

        metrics.inc_streams_total();
        assert_eq!(metrics.streams_total.get() as u64, 1);

        metrics.inc_streams_ended();
        assert_eq!(metrics.streams_ended.get() as u64, 1);

        // Test gauge
        metrics.set_active_streams(5.0);
        assert_eq!(metrics.active_streams.get(), 5.0);

        metrics.inc_active_streams();
        assert_eq!(metrics.active_streams.get(), 6.0);

        metrics.dec_active_streams();
        assert_eq!(metrics.active_streams.get(), 5.0);

        // Test histogram
        metrics.record_stream_duration(10.5);
        metrics.record_stream_duration(5.2);

        // Test bytes
        metrics.add_bytes_streamed(1024.0);
        assert_eq!(metrics.bytes_streamed_total.get() as u64, 1024);

        // Test erreurs
        metrics.inc_stream_errors();
        assert_eq!(metrics.stream_errors_total.get() as u64, 1);
    }

    #[test]
    fn test_metrics_gather() {
        let metrics = StreamMetrics::new().unwrap();
        metrics.inc_requests();

        let output = metrics.gather();
        assert!(output.is_ok());

        let output_str = output.unwrap();
        assert!(output_str.contains("veza_stream_requests_total"));
        assert!(output_str.contains("1"));
    }

    #[tokio::test]
    async fn test_metrics_manager() {
        let manager = MetricsManager::new();
        assert!(manager.is_ok());

        let manager = manager.unwrap();
        let metrics = manager.get().await;
        metrics.inc_requests();

        let output = manager.gather().await;
        assert!(output.is_ok());
        let output_str = output.unwrap();
        assert!(output_str.contains("veza_stream_requests_total"));
    }
}
