use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::env;
use std::time::Duration;
use tracing::{debug, info, warn};

/// Configuration pour la connexion RabbitMQ
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RabbitMQConfig {
    pub url: String,
    pub max_retries: u32,
    pub retry_interval_secs: u64,
    pub enable: bool,
}

impl RabbitMQConfig {
    pub fn from_env() -> Self {
        dotenvy::dotenv().ok(); // S'assurer que les .env sont chargés
        Self {
            url: env::var("RABBITMQ_URL")
                .unwrap_or_else(|_| "amqp://guest:guest@localhost:5672/".to_string()),
            max_retries: env::var("RABBITMQ_MAX_RETRIES")
                .unwrap_or_else(|_| "3".to_string())
                .parse()
                .unwrap_or(3),
            retry_interval_secs: env::var("RABBITMQ_RETRY_INTERVAL_SECS")
                .unwrap_or_else(|_| "2".to_string())
                .parse()
                .unwrap_or(2),
            enable: env::var("RABBITMQ_ENABLE")
                .unwrap_or_else(|_| "true".to_string())
                .parse()
                .unwrap_or(true),
        }
    }
}

/// Configuration simple du chat server depuis variables d'environnement
#[derive(Debug, Clone)]
pub struct Config {
    /// URL de connexion à la base de données PostgreSQL
    pub database_url: String,
    /// Port d'écoute du serveur
    pub port: u16,
    /// Adresse IP d'écoute du serveur
    pub host: String,
    /// Configuration RabbitMQ
    pub rabbit_mq: RabbitMQConfig,
}

impl Config {
    /// Charge la configuration depuis les variables d'environnement
    ///
    /// Utilise `dotenvy` pour charger le fichier `.env` si présent.
    /// Variables d'environnement:
    /// - `DATABASE_URL`: URL de connexion PostgreSQL (requis)
    /// - `CHAT_SERVER_PORT`: Port d'écoute (défaut: 8081)
    /// - `CHAT_SERVER_HOST`: Adresse d'écoute (défaut: 0.0.0.0)
    /// - `RABBITMQ_URL`: URL de connexion RabbitMQ (défaut: amqp://guest:guest@localhost:5672/)
    /// - `RABBITMQ_MAX_RETRIES`: Nb max de tentatives RabbitMQ (défaut: 3)
    /// - `RABBITMQ_RETRY_INTERVAL_SECS`: Intervalle retry RabbitMQ (défaut: 2s)
    /// - `RABBITMQ_ENABLE`: Activer RabbitMQ (défaut: true)
    ///
    /// # Returns
    ///
    /// Un `Config` ou une erreur si `DATABASE_URL` n'est pas définie
    pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
        // Charger le fichier .env si présent (dotenvy ignore silencieusement si absent)
        dotenvy::dotenv().ok();

        Ok(Config {
            database_url: env::var("DATABASE_URL")
                .map_err(|_| "DATABASE_URL environment variable is required")?,
            port: env::var("CHAT_SERVER_PORT")
                .unwrap_or_else(|_| "8081".to_string())
                .parse()
                .map_err(|e| format!("Invalid CHAT_SERVER_PORT: {}", e))?,
            host: env::var("CHAT_SERVER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string()),
            rabbit_mq: RabbitMQConfig::from_env(),
        })
    }
}

/// Configuration optimisée pour le pool de connexions SQLx
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
    /// URL de connexion à la base de données
    pub database_url: String,

    /// Nombre maximum de connexions ouvertes
    pub max_connections: u32,

    /// Nombre minimum de connexions maintenues
    pub min_connections: u32,

    /// Timeout pour établir une nouvelle connexion
    pub connect_timeout: Duration,

    /// Timeout pour les requêtes
    pub acquire_timeout: Duration,

    /// Durée de vie maximale d'une connexion
    pub max_lifetime: Duration,

    /// Durée d'inactivité avant fermeture d'une connexion
    pub idle_timeout: Duration,

    /// Test des connexions avant utilisation
    pub test_before_acquire: bool,

    /// Configuration SSL
    pub ssl_mode: SslMode,

    /// Configuration du pool pour les performances
    pub pool_config: PoolConfig,
}

/// Mode SSL pour la connexion PostgreSQL
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SslMode {
    Disable,
    Prefer,
    Require,
    VerifyCa,
    VerifyFull,
}

impl SslMode {
    pub fn to_string(&self) -> &'static str {
        match self {
            SslMode::Disable => "disable",
            SslMode::Prefer => "prefer",
            SslMode::Require => "require",
            SslMode::VerifyCa => "verify-ca",
            SslMode::VerifyFull => "verify-full",
        }
    }
}

/// Configuration spécifique au pool
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolConfig {
    /// Taille du buffer pour les requêtes préparées
    pub prepared_statement_cache_size: usize,

    /// Nombre de connexions à créer au démarrage
    pub initial_connections: u32,

    /// Intervalle de nettoyage des connexions inactives
    pub cleanup_interval: Duration,

    /// Seuil pour déclencher un warning sur le nombre de connexions
    pub connection_warning_threshold: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
    pub jwt_secret: String,
    pub jwt_access_duration: Duration,
    pub jwt_refresh_duration: Duration,
    pub jwt_algorithm: String,
    pub jwt_audience: String,
    pub jwt_issuer: String,
    pub enable_2fa: bool,
    pub totp_window: u32,
    pub content_filtering: bool,
    pub password_min_length: u32,
    pub bcrypt_cost: u32,
}

impl Default for DatabaseConfig {
    fn default() -> Self {
        Self {
            database_url: "postgresql://localhost/veza_chat".to_string(),
            max_connections: 20,
            min_connections: 5,
            connect_timeout: Duration::from_secs(5),
            acquire_timeout: Duration::from_secs(10),
            max_lifetime: Duration::from_secs(1800), // 30 minutes
            idle_timeout: Duration::from_secs(600),  // 10 minutes
            test_before_acquire: true,
            ssl_mode: SslMode::Prefer,
            pool_config: PoolConfig::default(),
        }
    }
}

impl Default for SecurityConfig {
    fn default() -> Self {
        #[cfg(not(test))]
        {
            panic!(
                "SecurityConfig::default() cannot be used in production. \
                 Create SecurityConfig manually with require_env_min_length(\"JWT_SECRET\", 32)"
            );
        }

        #[cfg(test)]
        Self {
            jwt_secret: "test_jwt_secret_minimum_32_characters_long".to_string(),
            jwt_access_duration: Duration::from_secs(900), // 15 min
            jwt_refresh_duration: Duration::from_secs(86400 * 30), // 30 days
            jwt_algorithm: "HS256".to_string(),
            jwt_audience: "veza-chat".to_string(),
            jwt_issuer: "veza-backend".to_string(),
            enable_2fa: false,
            totp_window: 1,
            content_filtering: false,
            password_min_length: 8,
            bcrypt_cost: 12,
        }
    }
}

impl Default for PoolConfig {
    fn default() -> Self {
        Self {
            prepared_statement_cache_size: 100,
            initial_connections: 5,
            cleanup_interval: Duration::from_secs(300), // 5 minutes
            connection_warning_threshold: 15,
        }
    }
}

/// Gestionnaire de pool de base de données optimisé
pub struct DatabaseManager {
    pool: PgPool,
    config: DatabaseConfig,
    metrics: DatabaseMetrics,
}

/// Métriques du pool de base de données
#[derive(Debug, Clone)]
pub struct DatabaseMetrics {
    pub total_connections: u32,
    pub idle_connections: u32,
    pub active_connections: u32,
    pub waiting_requests: u32,
    pub connection_errors: u64,
    pub query_errors: u64,
    pub avg_query_duration_ms: f64,
    pub last_cleanup: std::time::Instant,
}

impl Default for DatabaseMetrics {
    fn default() -> Self {
        Self {
            total_connections: 0,
            idle_connections: 0,
            active_connections: 0,
            waiting_requests: 0,
            connection_errors: 0,
            query_errors: 0,
            avg_query_duration_ms: 0.0,
            last_cleanup: std::time::Instant::now(),
        }
    }
}

impl DatabaseManager {
    /// Crée une nouvelle instance du gestionnaire de base de données
    pub async fn new(config: DatabaseConfig) -> Result<Self, sqlx::Error> {
        info!(
            "Initializing database connection pool with config: {:?}",
            config
        );

        // Construire l'URL de connexion avec les paramètres SSL
        let mut database_url = config.database_url.clone();
        if !database_url.contains("sslmode=") {
            let separator = if database_url.contains('?') { "&" } else { "?" };
            database_url.push_str(&format!(
                "{}sslmode={}",
                separator,
                config.ssl_mode.to_string()
            ));
        }

        // Créer le pool avec la configuration optimisée
        let pool_options = sqlx::postgres::PgPoolOptions::new()
            .max_connections(config.max_connections)
            .min_connections(config.min_connections)
            .acquire_timeout(config.acquire_timeout)
            .max_lifetime(config.max_lifetime)
            .idle_timeout(config.idle_timeout)
            .test_before_acquire(config.test_before_acquire);

        let pool = pool_options.connect(&database_url).await?;

        // Tester la connexion
        sqlx::query("SELECT 1").fetch_one(&pool).await?;

        info!(
            "Database connection pool initialized successfully. Max connections: {}, Min connections: {}",
            config.max_connections, config.min_connections
        );

        Ok(Self {
            pool,
            config,
            metrics: DatabaseMetrics::default(),
        })
    }

    /// Obtient le pool de connexions
    pub fn pool(&self) -> &PgPool {
        &self.pool
    }

    /// Obtient les métriques actuelles du pool
    pub async fn get_metrics(&self) -> DatabaseMetrics {
        let pool_status = self.pool.size();
        let idle_count = self.pool.num_idle();
        let active_count = pool_status - idle_count as u32;

        DatabaseMetrics {
            total_connections: pool_status,
            idle_connections: idle_count as u32,
            active_connections: active_count,
            waiting_requests: 0, // SQLx ne fournit pas cette info directement
            connection_errors: self.metrics.connection_errors,
            query_errors: self.metrics.query_errors,
            avg_query_duration_ms: self.metrics.avg_query_duration_ms,
            last_cleanup: self.metrics.last_cleanup,
        }
    }

    /// Nettoie les connexions inactives
    pub async fn cleanup_idle_connections(&mut self) {
        let now = std::time::Instant::now();

        if now.duration_since(self.metrics.last_cleanup) >= self.config.pool_config.cleanup_interval
        {
            // SQLx gère automatiquement le nettoyage des connexions inactives
            // On peut juste mettre à jour nos métriques
            self.metrics.last_cleanup = now;

            let metrics = self.get_metrics().await;
            debug!(
                "Database cleanup completed. Active: {}, Idle: {}, Total: {}",
                metrics.active_connections, metrics.idle_connections, metrics.total_connections
            );

            // Warning si trop de connexions actives
            if metrics.active_connections > self.config.pool_config.connection_warning_threshold {
                warn!(
                    "High number of active database connections: {} (threshold: {})",
                    metrics.active_connections,
                    self.config.pool_config.connection_warning_threshold
                );
            }
        }
    }

    /// Teste la santé de la base de données
    pub async fn health_check(&self) -> Result<(), sqlx::Error> {
        let start = std::time::Instant::now();

        // Test de connexion simple
        sqlx::query("SELECT 1").fetch_one(&self.pool).await?;

        let duration = start.elapsed();
        debug!("Database health check completed in {:?}", duration);

        // Mettre à jour les métriques de performance
        // Note: Dans une vraie implémentation, on utiliserait des atomics pour thread-safety
        // self.metrics.avg_query_duration_ms = // calculer la moyenne

        Ok(())
    }

    /// Ferme le pool de connexions
    pub async fn close(self) {
        info!("Closing database connection pool");
        self.pool.close().await;
    }

    /// Obtient la configuration actuelle
    pub fn config(&self) -> &DatabaseConfig {
        &self.config
    }

    /// Met à jour la configuration (nécessite un redémarrage du pool)
    pub fn update_config(&mut self, new_config: DatabaseConfig) {
        self.config = new_config;
        info!("Database configuration updated");
    }

    /// Obtient des statistiques détaillées du pool
    pub async fn get_detailed_stats(&self) -> DetailedPoolStats {
        let metrics = self.get_metrics().await;

        DetailedPoolStats {
            pool_size: metrics.total_connections,
            idle_connections: metrics.idle_connections,
            active_connections: metrics.active_connections,
            max_connections: self.config.max_connections,
            min_connections: self.config.min_connections,
            connection_utilization: (metrics.active_connections as f64
                / self.config.max_connections as f64)
                * 100.0,
            avg_query_duration_ms: metrics.avg_query_duration_ms,
            connection_errors: metrics.connection_errors,
            query_errors: metrics.query_errors,
            last_cleanup: metrics.last_cleanup,
        }
    }
}

/// Statistiques détaillées du pool
#[derive(Debug, Clone)]
pub struct DetailedPoolStats {
    pub pool_size: u32,
    pub idle_connections: u32,
    pub active_connections: u32,
    pub max_connections: u32,
    pub min_connections: u32,
    pub connection_utilization: f64,
    pub avg_query_duration_ms: f64,
    pub connection_errors: u64,
    pub query_errors: u64,
    pub last_cleanup: std::time::Instant,
}

/// Configuration optimisée pour différents environnements
impl DatabaseConfig {
    /// Configuration pour le développement local
    pub fn development() -> Self {
        Self {
            database_url: "postgresql://localhost/veza_chat_dev".to_string(),
            max_connections: 10,
            min_connections: 2,
            connect_timeout: Duration::from_secs(5),
            acquire_timeout: Duration::from_secs(10),
            max_lifetime: Duration::from_secs(1800),
            idle_timeout: Duration::from_secs(300),
            test_before_acquire: true,
            ssl_mode: SslMode::Disable,
            pool_config: PoolConfig {
                prepared_statement_cache_size: 50,
                initial_connections: 2,
                cleanup_interval: Duration::from_secs(300),
                connection_warning_threshold: 8,
            },
        }
    }

    /// Configuration pour la production
    pub fn production() -> Self {
        Self {
            database_url: "postgresql://localhost/veza_chat_prod".to_string(),
            max_connections: 20,
            min_connections: 5,
            connect_timeout: Duration::from_secs(5),
            acquire_timeout: Duration::from_secs(10),
            max_lifetime: Duration::from_secs(1800),
            idle_timeout: Duration::from_secs(600),
            test_before_acquire: true,
            ssl_mode: SslMode::Require,
            pool_config: PoolConfig {
                prepared_statement_cache_size: 100,
                initial_connections: 5,
                cleanup_interval: Duration::from_secs(300),
                connection_warning_threshold: 15,
            },
        }
    }

    /// Configuration pour les tests
    pub fn testing() -> Self {
        Self {
            database_url: "postgresql://localhost/veza_chat_test".to_string(),
            max_connections: 5,
            min_connections: 1,
            connect_timeout: Duration::from_secs(2),
            acquire_timeout: Duration::from_secs(5),
            max_lifetime: Duration::from_secs(300),
            idle_timeout: Duration::from_secs(60),
            test_before_acquire: false,
            ssl_mode: SslMode::Disable,
            pool_config: PoolConfig {
                prepared_statement_cache_size: 10,
                initial_connections: 1,
                cleanup_interval: Duration::from_secs(60),
                connection_warning_threshold: 3,
            },
        }
    }
}

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

    #[test]
    fn test_config_from_env() {
        // Sauvegarder les valeurs originales
        let original_db_url = std::env::var("DATABASE_URL").ok();
        let original_port = std::env::var("CHAT_SERVER_PORT").ok();
        let original_host = std::env::var("CHAT_SERVER_HOST").ok();

        // Test avec des variables d'environnement définies
        std::env::set_var("DATABASE_URL", "postgresql://test:test@localhost/test_db");
        std::env::set_var("CHAT_SERVER_PORT", "9999");
        std::env::set_var("CHAT_SERVER_HOST", "127.0.0.1");

        let config = Config::from_env().unwrap();
        assert_eq!(
            config.database_url,
            "postgresql://test:test@localhost/test_db"
        );
        assert_eq!(config.port, 9999);
        assert_eq!(config.host, "127.0.0.1");

        // Restaurer les valeurs originales
        if let Some(url) = original_db_url {
            std::env::set_var("DATABASE_URL", url);
        } else {
            std::env::remove_var("DATABASE_URL");
        }
        if let Some(port) = original_port {
            std::env::set_var("CHAT_SERVER_PORT", port);
        } else {
            std::env::remove_var("CHAT_SERVER_PORT");
        }
        if let Some(host) = original_host {
            std::env::set_var("CHAT_SERVER_HOST", host);
        } else {
            std::env::remove_var("CHAT_SERVER_HOST");
        }
    }

    #[test]
    #[cfg_attr(not(feature = "serial-test"), ignore)] // Ignorer si pas de serial-test
    fn test_config_from_env_defaults() {
        // Sauvegarder les valeurs originales
        let original_db_url = std::env::var("DATABASE_URL").ok();
        let original_port = std::env::var("CHAT_SERVER_PORT").ok();
        let original_host = std::env::var("CHAT_SERVER_HOST").ok();

        // S'assurer que les variables sont bien supprimées
        std::env::remove_var("CHAT_SERVER_PORT");
        std::env::remove_var("CHAT_SERVER_HOST");

        // Test avec DATABASE_URL uniquement
        std::env::set_var("DATABASE_URL", "postgresql://test:test@localhost/test_db");

        let config = Config::from_env().unwrap();
        assert_eq!(
            config.database_url,
            "postgresql://test:test@localhost/test_db"
        );
        assert_eq!(config.port, 8081, "Port should default to 8081"); // Défaut
        assert_eq!(config.host, "0.0.0.0", "Host should default to 0.0.0.0"); // Défaut

        // Restaurer les valeurs originales
        if let Some(url) = original_db_url {
            std::env::set_var("DATABASE_URL", url);
        } else {
            std::env::remove_var("DATABASE_URL");
        }
        if let Some(port) = original_port {
            std::env::set_var("CHAT_SERVER_PORT", port);
        } else {
            std::env::remove_var("CHAT_SERVER_PORT");
        }
        if let Some(host) = original_host {
            std::env::set_var("CHAT_SERVER_HOST", host);
        } else {
            std::env::remove_var("CHAT_SERVER_HOST");
        }
    }

    #[test]
    #[cfg_attr(not(feature = "serial-test"), ignore)] // Ignorer si pas de serial-test
    fn test_config_from_env_missing_database_url() {
        // Sauvegarder la valeur originale
        let original_db_url = std::env::var("DATABASE_URL").ok();

        // S'assurer que DATABASE_URL est bien supprimé
        std::env::remove_var("DATABASE_URL");

        // Vérifier qu'il n'y a pas de .env qui pourrait définir DATABASE_URL
        // En forçant le rechargement, on s'assure que la variable n'est pas chargée
        let result = Config::from_env();

        // Si dotenvy charge un .env avec DATABASE_URL, le test peut échouer
        // Dans ce cas, on accepte que le test soit ignoré si DATABASE_URL est défini ailleurs
        if original_db_url.is_none() && std::env::var("DATABASE_URL").is_ok() {
            // DATABASE_URL a été chargé depuis .env, on ignore ce test
            eprintln!("Warning: DATABASE_URL found in .env, skipping test");
            return;
        }

        assert!(result.is_err(), "Should fail when DATABASE_URL is missing");

        // Restaurer la valeur originale
        if let Some(url) = original_db_url {
            std::env::set_var("DATABASE_URL", url);
        }
    }

    #[tokio::test]
    async fn test_database_config_defaults() {
        let config = DatabaseConfig::default();
        assert_eq!(config.max_connections, 20);
        assert_eq!(config.min_connections, 5);
        assert_eq!(config.connect_timeout, Duration::from_secs(5));
    }

    #[tokio::test]
    async fn test_database_config_environments() {
        let dev_config = DatabaseConfig::development();
        let prod_config = DatabaseConfig::production();
        let test_config = DatabaseConfig::testing();

        assert!(dev_config.max_connections < prod_config.max_connections);
        assert!(test_config.max_connections < dev_config.max_connections);
        assert_eq!(prod_config.ssl_mode, SslMode::Require);
        assert_eq!(dev_config.ssl_mode, SslMode::Disable);
    }

    #[test]
    fn test_ssl_mode_to_string() {
        assert_eq!(SslMode::Disable.to_string(), "disable");
        assert_eq!(SslMode::Require.to_string(), "require");
        assert_eq!(SslMode::VerifyFull.to_string(), "verify-full");
    }
}
