//! Configuração e integração com StatsD para métricas

use crate::KondutoError;
use crate::environment::EnvironmentVariables;
use cadence::{BufferedUdpMetricSink, QueuingMetricSink, StatsdClient};
use std::net::UdpSocket;

/// Configuração para o cliente StatsD
#[derive(Debug, Clone)]
pub struct KondutoStatsdConfig {
    /// Tamanho máximo do buffer (em bytes)
    pub max_buffer_size: usize,
    /// Tempo máximo para enviar métricas (em milissegundos)
    pub max_time_ms: u64,
    /// Host do servidor StatsD
    pub host: String,
    /// Porta do servidor StatsD
    pub port: u16,
    /// Tags padrão que serão aplicadas a todas as métricas
    pub default_tags: Vec<(String, String)>,
}

impl Default for KondutoStatsdConfig {
    fn default() -> Self {
        Self {
            max_buffer_size: 1432, // MTU típico para UDP
            max_time_ms: 1000,     // 1 segundo
            host: "127.0.0.1".to_string(),
            port: 8125, // Porta padrão do StatsD
            default_tags: vec![],
        }
    }
}

impl KondutoStatsdConfig {
    /// Cria uma nova configuração com valores padrão
    pub fn new() -> Self {
        Self::default()
    }

    /// Define o host do servidor StatsD
    pub fn with_host(mut self, host: impl Into<String>) -> Self {
        self.host = host.into();
        self
    }

    /// Define a porta do servidor StatsD
    pub fn with_port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    /// Define o tamanho máximo do buffer
    pub fn with_max_buffer_size(mut self, size: usize) -> Self {
        self.max_buffer_size = size;
        self
    }

    /// Define o tempo máximo para enviar métricas
    pub fn with_max_time_ms(mut self, time_ms: u64) -> Self {
        self.max_time_ms = time_ms;
        self
    }

    /// Adiciona uma tag padrão que será aplicada a todas as métricas
    pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.default_tags.push((key.into(), value.into()));
        self
    }

    /// Define todas as tags padrão de uma vez
    pub fn with_tags(mut self, tags: Vec<(String, String)>) -> Self {
        self.default_tags = tags;
        self
    }

    /// Constrói o cliente StatsD a partir da configuração
    pub fn build(self) -> Result<StatsdClient, KondutoError> {
        let addr = format!("{}:{}", self.host, self.port);

        // Cria o socket UDP
        let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| {
            KondutoError::GenericError(format!("failed to create UDP socket: {}", e))
        })?;

        socket.set_nonblocking(true).map_err(|e| {
            KondutoError::GenericError(format!("failed to set UDP socket to nonblocking: {}", e))
        })?;

        // Cria o sink com buffer usando a capacidade configurada
        let udp_sink = BufferedUdpMetricSink::with_capacity(&addr, socket, self.max_buffer_size)
            .map_err(|e| KondutoError::GenericError(format!("failed to create UDP sink: {}", e)))?;

        // Cria o sink com queue (para envio periódico)
        let queuing_sink = QueuingMetricSink::from(udp_sink);

        // Cria o cliente StatsD com prefixo e tags
        let mut builder = StatsdClient::builder("konduto", queuing_sink);

        // Adiciona tag padrão indicando que é da SDK
        builder = builder.with_tag("sdk", "konduto-rs");

        // Adiciona as tags customizadas
        for (key, value) in &self.default_tags {
            builder = builder.with_tag(key, value);
        }

        let client = builder.build();

        Ok(client)
    }

    /// Cria a configuração a partir de variáveis de ambiente
    ///
    /// Variáveis esperadas (use `EnvironmentVariables` para descobrir os nomes):
    /// - `KONDUTO_STATSD_HOST` (opcional, padrão: "127.0.0.1")
    /// - `KONDUTO_STATSD_PORT` (opcional, padrão: 8125)
    /// - `KONDUTO_STATSD_MAX_BUFFER_SIZE` (opcional, padrão: 1432)
    /// - `KONDUTO_STATSD_MAX_TIME_MS` (opcional, padrão: 1000)
    /// - `KONDUTO_STATSD_TAGS` (opcional, formato: "key1:value1,key2:value2")
    pub fn from_env() -> Self {
        let host = std::env::var(EnvironmentVariables::KondutoStatsdHost.as_str())
            .unwrap_or_else(|_| "127.0.0.1".to_string());

        let port = std::env::var(EnvironmentVariables::KondutoStatsdPort.as_str())
            .ok()
            .and_then(|v| v.parse::<u16>().ok())
            .unwrap_or(8125);

        let max_buffer_size =
            std::env::var(EnvironmentVariables::KondutoStatsdMaxBufferSize.as_str())
                .ok()
                .and_then(|v| v.parse::<usize>().ok())
                .unwrap_or(1432);

        let max_time_ms = std::env::var(EnvironmentVariables::KondutoStatsdMaxTimeMs.as_str())
            .ok()
            .and_then(|v| v.parse::<u64>().ok())
            .unwrap_or(1000);

        // Parse tags no formato "key1:value1,key2:value2"
        let default_tags = std::env::var(EnvironmentVariables::KondutoStatsdTags.as_str())
            .ok()
            .map(|tags_str| {
                tags_str
                    .split(',')
                    .filter_map(|tag| {
                        let parts: Vec<&str> = tag.split(':').collect();
                        if parts.len() == 2 {
                            Some((parts[0].trim().to_string(), parts[1].trim().to_string()))
                        } else {
                            None
                        }
                    })
                    .collect()
            })
            .unwrap_or_default();

        Self {
            host,
            port,
            max_buffer_size,
            max_time_ms,
            default_tags,
        }
    }
}

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

    #[test]
    fn test_default_config() {
        let config = KondutoStatsdConfig::default();
        assert_eq!(config.host, "127.0.0.1");
        assert_eq!(config.port, 8125);
        assert_eq!(config.max_buffer_size, 1432);
        assert_eq!(config.max_time_ms, 1000);
    }

    #[test]
    fn test_builder_pattern() {
        let config = KondutoStatsdConfig::new()
            .with_host("statsd.example.com")
            .with_port(9125)
            .with_max_buffer_size(2048)
            .with_max_time_ms(500);

        assert_eq!(config.host, "statsd.example.com");
        assert_eq!(config.port, 9125);
        assert_eq!(config.max_buffer_size, 2048);
        assert_eq!(config.max_time_ms, 500);
    }

    #[test]
    fn test_from_env_with_defaults() {
        temp_env::with_vars(
            vec![
                (
                    EnvironmentVariables::KondutoStatsdHost.as_str(),
                    None::<&str>,
                ),
                (EnvironmentVariables::KondutoStatsdPort.as_str(), None),
                (
                    EnvironmentVariables::KondutoStatsdMaxBufferSize.as_str(),
                    None,
                ),
                (EnvironmentVariables::KondutoStatsdMaxTimeMs.as_str(), None),
            ],
            || {
                let config = KondutoStatsdConfig::from_env();
                assert_eq!(config.host, "127.0.0.1");
                assert_eq!(config.port, 8125);
                assert_eq!(config.max_buffer_size, 1432);
                assert_eq!(config.max_time_ms, 1000);
            },
        );
    }

    #[test]
    fn test_from_env_with_custom_values() {
        temp_env::with_vars(
            vec![
                (
                    EnvironmentVariables::KondutoStatsdHost.as_str(),
                    Some("custom.host"),
                ),
                (
                    EnvironmentVariables::KondutoStatsdPort.as_str(),
                    Some("9999"),
                ),
                (
                    EnvironmentVariables::KondutoStatsdMaxBufferSize.as_str(),
                    Some("4096"),
                ),
                (
                    EnvironmentVariables::KondutoStatsdMaxTimeMs.as_str(),
                    Some("2000"),
                ),
            ],
            || {
                let config = KondutoStatsdConfig::from_env();
                assert_eq!(config.host, "custom.host");
                assert_eq!(config.port, 9999);
                assert_eq!(config.max_buffer_size, 4096);
                assert_eq!(config.max_time_ms, 2000);
            },
        );
    }

    #[test]
    fn test_from_env_with_invalid_values() {
        temp_env::with_vars(
            vec![
                (
                    EnvironmentVariables::KondutoStatsdPort.as_str(),
                    Some("invalid"),
                ),
                (
                    EnvironmentVariables::KondutoStatsdMaxBufferSize.as_str(),
                    Some("not_a_number"),
                ),
                (
                    EnvironmentVariables::KondutoStatsdMaxTimeMs.as_str(),
                    Some("abc"),
                ),
            ],
            || {
                let config = KondutoStatsdConfig::from_env();
                // Deve usar valores padrão quando parsing falha
                assert_eq!(config.port, 8125);
                assert_eq!(config.max_buffer_size, 1432);
                assert_eq!(config.max_time_ms, 1000);
            },
        );
    }

    #[test]
    fn test_with_tag() {
        let config = KondutoStatsdConfig::new()
            .with_tag("env", "production")
            .with_tag("service", "api");

        assert_eq!(config.default_tags.len(), 2);
        assert_eq!(
            config.default_tags[0],
            ("env".to_string(), "production".to_string())
        );
        assert_eq!(
            config.default_tags[1],
            ("service".to_string(), "api".to_string())
        );
    }

    #[test]
    fn test_with_tags() {
        let tags = vec![
            ("env".to_string(), "staging".to_string()),
            ("region".to_string(), "us-east-1".to_string()),
        ];

        let config = KondutoStatsdConfig::new().with_tags(tags.clone());

        assert_eq!(config.default_tags, tags);
    }

    #[test]
    fn test_from_env_with_tags() {
        temp_env::with_vars(
            vec![(
                EnvironmentVariables::KondutoStatsdTags.as_str(),
                Some("env:production,region:us-west-2,service:konduto-api"),
            )],
            || {
                let config = KondutoStatsdConfig::from_env();
                assert_eq!(config.default_tags.len(), 3);
                assert_eq!(
                    config.default_tags[0],
                    ("env".to_string(), "production".to_string())
                );
                assert_eq!(
                    config.default_tags[1],
                    ("region".to_string(), "us-west-2".to_string())
                );
                assert_eq!(
                    config.default_tags[2],
                    ("service".to_string(), "konduto-api".to_string())
                );
            },
        );
    }

    #[test]
    fn test_from_env_with_invalid_tags() {
        temp_env::with_vars(
            vec![(
                EnvironmentVariables::KondutoStatsdTags.as_str(),
                Some("invalid_tag,env:production,also_invalid"),
            )],
            || {
                let config = KondutoStatsdConfig::from_env();
                // Apenas a tag válida deve ser parseada
                assert_eq!(config.default_tags.len(), 1);
                assert_eq!(
                    config.default_tags[0],
                    ("env".to_string(), "production".to_string())
                );
            },
        );
    }

    #[test]
    fn test_from_env_without_tags() {
        temp_env::with_vars(
            vec![(
                EnvironmentVariables::KondutoStatsdTags.as_str(),
                None::<&str>,
            )],
            || {
                let config = KondutoStatsdConfig::from_env();
                assert_eq!(config.default_tags.len(), 0);
            },
        );
    }
}
