//! Cliente HTTP para interagir com a API da Konduto

use crate::environment::EnvironmentVariables;
use crate::errors::error::{KondutoError, KondutoResult};
use crate::sdk::api_key::KondutoApiKey;
use reqwest::{Client, Url};
use reqwest_middleware::ClientBuilder;
use reqwest_middleware::ClientWithMiddleware;
use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff};
use reqwest_tracing::TracingMiddleware;
use std::sync::OnceLock;

const API_BASE_URL: &str = "https://api.konduto.com";
static USER_AGENT: OnceLock<String> = OnceLock::new();

pub(crate) fn user_agent() -> &'static str {
    USER_AGENT.get_or_init(|| format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")))
}

/// Inicializa o cliente StatsD a partir de variáveis de ambiente
///
/// Esta função tenta configurar automaticamente o cliente StatsD quando a feature
/// `statsd` está habilitada. Retorna `None` se houver erro na configuração.
#[cfg(feature = "statsd")]
fn initialize_statsd_client() -> Option<cadence::StatsdClient> {
    use crate::sdk::metrics::KondutoStatsdConfig;
    KondutoStatsdConfig::from_env().build().ok()
}

fn build_client_with_middleware(
    #[cfg(feature = "statsd")] statsd_client: Option<cadence::StatsdClient>,
) -> ClientWithMiddleware {
    let max_retries = std::env::var(EnvironmentVariables::KondutoHttpMaxRetries.as_str())
        .ok()
        .and_then(|v| v.parse::<u32>().ok())
        .unwrap_or(3);

    let retry_policy = ExponentialBackoff::builder().build_with_max_retries(max_retries);

    // if this expected fails this means that was changed and mutability always needs to be used
    let mut builder = ClientBuilder::new(Client::new())
        .with(TracingMiddleware::default())
        .with(RetryTransientMiddleware::new_with_policy(retry_policy));

    // add middleware of metrics if StatsD feature is enabled
    #[cfg(feature = "statsd")]
    {
        if let Some(client) = statsd_client {
            builder = builder.with(crate::sdk::metrics::StatsdMiddleware::new(client));
        }
    }

    builder.build()
}

/// Cliente principal para interagir com a API da Konduto
#[derive(Clone)]
pub struct KondutoClient {
    api_key: KondutoApiKey,
    client: ClientWithMiddleware,
    base_url: Url,
}

impl KondutoClient {
    /// Cria um novo cliente buscando configurações das variáveis de ambiente.
    ///
    /// Variáveis esperadas:
    /// * `KONDUTO_API_KEY` (Obrigatória)
    /// * `KONDUTO_URL` (Opcional, padrão: "<https://api.konduto.com>")
    ///
    /// # Exemplo
    ///
    /// ```no_run
    /// use konduto::{KondutoClient, KondutoResult};
    ///
    /// // Requer KONDUTO_API_KEY definida no ambiente
    /// let client = KondutoClient::new();
    ///
    /// match client {
    ///     Ok(sdk) => println!("SDK criado com sucesso"),
    ///     Err(err) => println!("Erro ao criar SDK: {:?}", err)
    /// }
    /// ```
    pub fn new() -> KondutoResult<Self> {
        let api_key_str =
            std::env::var(EnvironmentVariables::KondutoApiKey.as_str()).map_err(|_| {
                KondutoError::MissingEnvVar(EnvironmentVariables::KondutoApiKey.as_str())
            })?;

        let base_url_str = std::env::var(EnvironmentVariables::KondutoUrl.as_str())
            .unwrap_or_else(|_| API_BASE_URL.to_string());

        let api_key = KondutoApiKey::new(api_key_str)
            .map_err(|e| KondutoError::ValidationError(e.to_string()))?;

        let base_url = base_url_str
            .parse::<Url>()
            .map_err(|e| KondutoError::GenericError(format!("invalid url: {}", e)))?;

        Ok(Self {
            api_key,
            client: build_client_with_middleware(
                #[cfg(feature = "statsd")]
                initialize_statsd_client(),
            ),
            base_url,
        })
    }

    pub fn with_base_url(api_key: KondutoApiKey, base_url: Url) -> Self {
        Self {
            api_key,
            client: build_client_with_middleware(
                #[cfg(feature = "statsd")]
                initialize_statsd_client(),
            ),
            base_url,
        }
    }

    pub(crate) fn auth_header(&self) -> String {
        self.api_key.as_b64()
    }

    pub fn client(&self) -> &ClientWithMiddleware {
        &self.client
    }

    pub fn base_url(&self) -> &Url {
        &self.base_url
    }
}

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

    #[test]
    fn test_should_create_client_using_env_vars() {
        temp_env::with_vars(
            vec![
                (
                    EnvironmentVariables::KondutoApiKey.as_str(),
                    Some("env_test_key"),
                ),
                (
                    EnvironmentVariables::KondutoUrl.as_str(),
                    Some("https://api.test.konduto.com/v1"),
                ),
            ],
            || {
                let result = KondutoClient::new();
                match result {
                    Ok(client) => {
                        // "env_test_key:" em base64 é "ZW52X3Rlc3Rfa2V5Og=="
                        assert_eq!(client.auth_header(), "Basic ZW52X3Rlc3Rfa2V5");
                        assert_eq!(client.base_url.as_str(), "https://api.test.konduto.com/v1");
                    }
                    Err(err) => {
                        unreachable!("was expected an konduto client, got error: {:?}", err);
                    }
                }
            },
        );
    }

    #[test]
    fn test_should_fail_without_api_key() {
        temp_env::with_vars(
            vec![("KONDUTO_API_KEY", None::<&str>), ("KONDUTO_URL", None)],
            || {
                let result = KondutoClient::new();
                match result {
                    Err(KondutoError::MissingEnvVar(var)) => assert_eq!(var, "KONDUTO_API_KEY"),
                    _ => unreachable!("was expected an konduto error, but receive success"),
                }
            },
        );
    }

    #[test]
    fn test_auth_header() {
        let api_key = KondutoApiKey::new("env_test_key").expect("Invalid API key");
        let url = Url::parse("https://api.konduto.com").unwrap();
        let client = KondutoClient::with_base_url(api_key, url);
        assert_eq!(client.auth_header(), "Basic ZW52X3Rlc3Rfa2V5");
    }

    #[test]
    fn test_user_agent() {
        let ua = user_agent();
        assert!(ua.starts_with("konduto-rs/"));
        assert!(ua.contains("0.1.0")); // Verifica que contém a versão
    }

    #[test]
    fn test_should_use_default_max_retries_when_env_not_set() {
        temp_env::with_vars(
            vec![(
                EnvironmentVariables::KondutoHttpMaxRetries.as_str(),
                None::<&str>,
            )],
            || {
                let _client = build_client_with_middleware(
                    #[cfg(feature = "statsd")]
                    None,
                );
                // Se construiu sem erro, o padrão de 3 retries foi aplicado
                assert_eq!(1, 1);
            },
        );
    }

    #[test]
    fn test_should_use_custom_max_retries_from_env() {
        temp_env::with_vars(
            vec![(
                EnvironmentVariables::KondutoHttpMaxRetries.as_str(),
                Some("5"),
            )],
            || {
                let _client = build_client_with_middleware(
                    #[cfg(feature = "statsd")]
                    None,
                );
                assert_eq!(1, 1);
            },
        );
    }

    #[test]
    fn test_should_use_default_when_env_is_invalid() {
        temp_env::with_vars(
            vec![(
                EnvironmentVariables::KondutoHttpMaxRetries.as_str(),
                Some("invalid"),
            )],
            || {
                let _client = build_client_with_middleware(
                    #[cfg(feature = "statsd")]
                    None,
                );
                assert_eq!(1, 1);
            },
        );
    }
}
