//! Middleware para instrumentação automática de requisições HTTP com StatsD

use cadence::StatsdClient;
use cadence::prelude::*;
use http::Extensions;
use reqwest::{Request, Response};
use reqwest_middleware::{Middleware, Next, Result as MiddlewareResult};
use std::sync::Arc;
use std::time::Instant;

/// Middleware que envia métricas para StatsD
pub struct StatsdMiddleware {
    client: Arc<StatsdClient>,
}

impl StatsdMiddleware {
    /// Cria um novo middleware com o cliente StatsD
    pub fn new(client: StatsdClient) -> Self {
        Self {
            client: Arc::new(client),
        }
    }

    /// Normaliza o path removendo IDs e valores dinâmicos
    /// Exemplo: /v1/orders/ORDER123 -> /v1/orders/:id
    fn normalize_path(path: &str) -> String {
        let segments: Vec<&str> = path.split('/').collect();
        let normalized: Vec<String> = segments
            .iter()
            .map(|segment| {
                // Identifica segmentos dinâmicos comuns
                if segment.is_empty() {
                    return String::from("");
                }

                // Versões da API (v1, v2, etc.)
                if segment.starts_with('v') && segment.len() == 2 {
                    return segment.to_string();
                }

                // Endpoints conhecidos
                match *segment {
                    "orders" | "blacklist" | "whitelist" | "greylist" | "email" | "ip"
                    | "tax_id" | "status" => segment.to_string(),
                    // Qualquer outro segmento é considerado dinâmico
                    _ => ":id".to_string(),
                }
            })
            .collect();

        normalized.join("/")
    }

    /// Categoriza a latência em buckets
    fn latency_bucket(duration_ms: u64) -> &'static str {
        match duration_ms {
            0..=100 => "fast",
            101..=500 => "medium",
            501..=1000 => "slow",
            _ => "very_slow",
        }
    }
}

#[async_trait::async_trait]
impl Middleware for StatsdMiddleware {
    async fn handle(
        &self,
        req: Request,
        extensions: &mut Extensions,
        next: Next<'_>,
    ) -> MiddlewareResult<Response> {
        let start = Instant::now();
        let method = req.method().to_string();
        let path = req.url().path().to_string();
        let normalized_path = Self::normalize_path(&path);

        // Executa a requisição
        let result = next.run(req, extensions).await;

        // Calcula a latência
        let duration = start.elapsed();
        let duration_ms = duration.as_millis() as u64;

        match &result {
            Ok(response) => {
                let status = response.status().as_u16();
                let status_class = format!("{}xx", status / 100);

                // Envia métricas
                // 1. Hit counter
                let _ = self
                    .client
                    .count_with_tags("konduto.requests.hits", 1)
                    .with_tag("method", &method)
                    .with_tag("endpoint", &normalized_path)
                    .with_tag("status", &status.to_string())
                    .with_tag("status_class", &status_class)
                    .send();
                println!("metrica requets.hts send");
                // 2. Latência (timer)
                let _ = self
                    .client
                    .time_with_tags("konduto.requests.latency", duration_ms)
                    .with_tag("method", &method)
                    .with_tag("endpoint", &normalized_path)
                    .with_tag("latency_bucket", Self::latency_bucket(duration_ms))
                    .send();

                // 3. Status code counter
                let _ = self
                    .client
                    .count_with_tags("konduto.requests.status", 1)
                    .with_tag("status", &status.to_string())
                    .with_tag("status_class", &status_class)
                    .send();
            }
            Err(_error) => {
                // Envia métricas de erro
                let _ = self
                    .client
                    .count_with_tags("konduto.requests.errors", 1)
                    .with_tag("method", &method)
                    .with_tag("endpoint", &normalized_path)
                    .with_tag("error_type", "network_error")
                    .send();

                // Ainda registra latência mesmo em erro
                let _ = self
                    .client
                    .time_with_tags("konduto.requests.latency", duration_ms)
                    .with_tag("method", &method)
                    .with_tag("endpoint", &normalized_path)
                    .with_tag("latency_bucket", Self::latency_bucket(duration_ms))
                    .with_tag("error", "true")
                    .send();
            }
        }

        result
    }
}

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

    #[test]
    fn test_normalize_path_orders() {
        assert_eq!(
            StatsdMiddleware::normalize_path("/v1/orders/ORDER123"),
            "/v1/orders/:id"
        );
    }

    #[test]
    fn test_normalize_path_decision_list() {
        assert_eq!(
            StatsdMiddleware::normalize_path("/v1/blacklist/email/test@example.com"),
            "/v1/blacklist/email/:id"
        );
    }

    #[test]
    fn test_normalize_path_simple() {
        assert_eq!(StatsdMiddleware::normalize_path("/v1/orders"), "/v1/orders");
    }

    #[test]
    fn test_normalize_path_with_status() {
        assert_eq!(
            StatsdMiddleware::normalize_path("/v1/orders/ORDER123/status"),
            "/v1/orders/:id/status"
        );
    }

    #[test]
    fn test_latency_bucket_fast() {
        assert_eq!(StatsdMiddleware::latency_bucket(50), "fast");
        assert_eq!(StatsdMiddleware::latency_bucket(100), "fast");
    }

    #[test]
    fn test_latency_bucket_medium() {
        assert_eq!(StatsdMiddleware::latency_bucket(101), "medium");
        assert_eq!(StatsdMiddleware::latency_bucket(500), "medium");
    }

    #[test]
    fn test_latency_bucket_slow() {
        assert_eq!(StatsdMiddleware::latency_bucket(501), "slow");
        assert_eq!(StatsdMiddleware::latency_bucket(1000), "slow");
    }

    #[test]
    fn test_latency_bucket_very_slow() {
        assert_eq!(StatsdMiddleware::latency_bucket(1001), "very_slow");
        assert_eq!(StatsdMiddleware::latency_bucket(5000), "very_slow");
    }
}
