//! Configuration management for Veza Rust services
//! 
//! This module provides centralized configuration management with validation
//! and environment variable support.

use serde::{Deserialize, Serialize};
use std::env;
use tracing::{info, warn};

/// Main configuration trait for Veza services
pub trait VezaConfig: Default + Clone + Serialize {
    /// Load configuration from environment variables
    fn from_env() -> crate::VezaResult<Self> {
        let mut config = Self::default();
        config.load_from_env()?;
        config.validate()?;
        Ok(config)
    }

    /// Load configuration from environment variables
    fn load_from_env(&mut self) -> crate::VezaResult<()>;

    /// Validate configuration
    fn validate(&self) -> crate::VezaResult<()>;

    /// Get configuration as JSON for debugging
    fn to_json(&self) -> crate::VezaResult<String> {
        Ok(serde_json::to_string_pretty(self)?)
    }
}

/// Database configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
    pub url: String,
    pub max_connections: u32,
    pub min_connections: u32,
    pub connect_timeout: u64,
    pub idle_timeout: u64,
    pub acquire_timeout: u64,
}

impl Default for DatabaseConfig {
    fn default() -> Self {
        Self {
            url: "postgresql://veza:password@localhost:5432/veza_db".to_string(),
            max_connections: 20,
            min_connections: 5,
            connect_timeout: 5,
            idle_timeout: 600,
            acquire_timeout: 10,
        }
    }
}

impl VezaConfig for DatabaseConfig {
    fn load_from_env(&mut self) -> crate::VezaResult<()> {
        if let Ok(url) = env::var("DATABASE_URL") {
            self.url = url;
        }
        if let Ok(max_conn) = env::var("DATABASE_MAX_CONNECTIONS") {
            self.max_connections = max_conn.parse()
                .map_err(|_| crate::VezaError::Config("Invalid DATABASE_MAX_CONNECTIONS".to_string()))?;
        }
        if let Ok(min_conn) = env::var("DATABASE_MIN_CONNECTIONS") {
            self.min_connections = min_conn.parse()
                .map_err(|_| crate::VezaError::Config("Invalid DATABASE_MIN_CONNECTIONS".to_string()))?;
        }
        if let Ok(timeout) = env::var("DATABASE_CONNECT_TIMEOUT") {
            self.connect_timeout = timeout.parse()
                .map_err(|_| crate::VezaError::Config("Invalid DATABASE_CONNECT_TIMEOUT".to_string()))?;
        }
        if let Ok(timeout) = env::var("DATABASE_IDLE_TIMEOUT") {
            self.idle_timeout = timeout.parse()
                .map_err(|_| crate::VezaError::Config("Invalid DATABASE_IDLE_TIMEOUT".to_string()))?;
        }
        if let Ok(timeout) = env::var("DATABASE_ACQUIRE_TIMEOUT") {
            self.acquire_timeout = timeout.parse()
                .map_err(|_| crate::VezaError::Config("Invalid DATABASE_ACQUIRE_TIMEOUT".to_string()))?;
        }
        Ok(())
    }

    fn validate(&self) -> crate::VezaResult<()> {
        if self.url.is_empty() {
            return Err(crate::VezaError::Config("Database URL cannot be empty".to_string()));
        }
        if self.max_connections < self.min_connections {
            return Err(crate::VezaError::Config("Max connections must be >= min connections".to_string()));
        }
        if self.max_connections == 0 {
            return Err(crate::VezaError::Config("Max connections must be > 0".to_string()));
        }
        Ok(())
    }
}

/// Redis configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedisConfig {
    pub url: String,
    pub max_connections: u32,
    pub connection_timeout: u64,
    pub command_timeout: u64,
    pub retry_attempts: u32,
}

impl Default for RedisConfig {
    fn default() -> Self {
        Self {
            url: "redis://localhost:6379".to_string(),
            max_connections: 10,
            connection_timeout: 5,
            command_timeout: 3,
            retry_attempts: 3,
        }
    }
}

impl VezaConfig for RedisConfig {
    fn load_from_env(&mut self) -> crate::VezaResult<()> {
        if let Ok(url) = env::var("REDIS_URL") {
            self.url = url;
        }
        if let Ok(max_conn) = env::var("REDIS_MAX_CONNECTIONS") {
            self.max_connections = max_conn.parse()
                .map_err(|_| crate::VezaError::Config("Invalid REDIS_MAX_CONNECTIONS".to_string()))?;
        }
        if let Ok(timeout) = env::var("REDIS_CONNECTION_TIMEOUT") {
            self.connection_timeout = timeout.parse()
                .map_err(|_| crate::VezaError::Config("Invalid REDIS_CONNECTION_TIMEOUT".to_string()))?;
        }
        if let Ok(timeout) = env::var("REDIS_COMMAND_TIMEOUT") {
            self.command_timeout = timeout.parse()
                .map_err(|_| crate::VezaError::Config("Invalid REDIS_COMMAND_TIMEOUT".to_string()))?;
        }
        if let Ok(retries) = env::var("REDIS_RETRY_ATTEMPTS") {
            self.retry_attempts = retries.parse()
                .map_err(|_| crate::VezaError::Config("Invalid REDIS_RETRY_ATTEMPTS".to_string()))?;
        }
        Ok(())
    }

    fn validate(&self) -> crate::VezaResult<()> {
        if self.url.is_empty() {
            return Err(crate::VezaError::Config("Redis URL cannot be empty".to_string()));
        }
        if self.max_connections == 0 {
            return Err(crate::VezaError::Config("Redis max connections must be > 0".to_string()));
        }
        Ok(())
    }
}

/// Server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
    pub workers: Option<usize>,
    pub keep_alive: u64,
    pub max_connections: u32,
    pub timeout: u64,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            host: "0.0.0.0".to_string(),
            port: 8080,
            workers: None,
            keep_alive: 30,
            max_connections: 1000,
            timeout: 30,
        }
    }
}

impl VezaConfig for ServerConfig {
    fn load_from_env(&mut self) -> crate::VezaResult<()> {
        if let Ok(host) = env::var("SERVER_HOST") {
            self.host = host;
        }
        if let Ok(port) = env::var("SERVER_PORT") {
            self.port = port.parse()
                .map_err(|_| crate::VezaError::Config("Invalid SERVER_PORT".to_string()))?;
        }
        if let Ok(workers) = env::var("SERVER_WORKERS") {
            self.workers = Some(workers.parse()
                .map_err(|_| crate::VezaError::Config("Invalid SERVER_WORKERS".to_string()))?);
        }
        if let Ok(keep_alive) = env::var("SERVER_KEEP_ALIVE") {
            self.keep_alive = keep_alive.parse()
                .map_err(|_| crate::VezaError::Config("Invalid SERVER_KEEP_ALIVE".to_string()))?;
        }
        if let Ok(max_conn) = env::var("SERVER_MAX_CONNECTIONS") {
            self.max_connections = max_conn.parse()
                .map_err(|_| crate::VezaError::Config("Invalid SERVER_MAX_CONNECTIONS".to_string()))?;
        }
        if let Ok(timeout) = env::var("SERVER_TIMEOUT") {
            self.timeout = timeout.parse()
                .map_err(|_| crate::VezaError::Config("Invalid SERVER_TIMEOUT".to_string()))?;
        }
        Ok(())
    }

    fn validate(&self) -> crate::VezaResult<()> {
        if self.host.is_empty() {
            return Err(crate::VezaError::Config("Server host cannot be empty".to_string()));
        }
        if self.port == 0 {
            return Err(crate::VezaError::Config("Server port must be > 0".to_string()));
        }
        if self.max_connections == 0 {
            return Err(crate::VezaError::Config("Server max connections must be > 0".to_string()));
        }
        Ok(())
    }
}

/// JWT configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtConfig {
    pub secret: String,
    pub access_token_ttl: u64,
    pub refresh_token_ttl: u64,
    pub issuer: String,
    pub audience: String,
}

impl Default for JwtConfig {
    fn default() -> Self {
        Self {
            secret: "your-super-secret-jwt-key".to_string(),
            access_token_ttl: 3600, // 1 hour
            refresh_token_ttl: 604800, // 7 days
            issuer: "veza".to_string(),
            audience: "veza-users".to_string(),
        }
    }
}

impl VezaConfig for JwtConfig {
    fn load_from_env(&mut self) -> crate::VezaResult<()> {
        if let Ok(secret) = env::var("JWT_SECRET") {
            self.secret = secret;
        }
        if let Ok(ttl) = env::var("JWT_ACCESS_TOKEN_TTL") {
            self.access_token_ttl = ttl.parse()
                .map_err(|_| crate::VezaError::Config("Invalid JWT_ACCESS_TOKEN_TTL".to_string()))?;
        }
        if let Ok(ttl) = env::var("JWT_REFRESH_TOKEN_TTL") {
            self.refresh_token_ttl = ttl.parse()
                .map_err(|_| crate::VezaError::Config("Invalid JWT_REFRESH_TOKEN_TTL".to_string()))?;
        }
        if let Ok(issuer) = env::var("JWT_ISSUER") {
            self.issuer = issuer;
        }
        if let Ok(audience) = env::var("JWT_AUDIENCE") {
            self.audience = audience;
        }
        Ok(())
    }

    fn validate(&self) -> crate::VezaResult<()> {
        if self.secret.is_empty() {
            return Err(crate::VezaError::Config("JWT secret cannot be empty".to_string()));
        }
        if self.secret.len() < 32 {
            warn!("JWT secret is shorter than recommended 32 characters");
        }
        if self.access_token_ttl == 0 {
            return Err(crate::VezaError::Config("JWT access token TTL must be > 0".to_string()));
        }
        if self.refresh_token_ttl == 0 {
            return Err(crate::VezaError::Config("JWT refresh token TTL must be > 0".to_string()));
        }
        if self.issuer.is_empty() {
            return Err(crate::VezaError::Config("JWT issuer cannot be empty".to_string()));
        }
        if self.audience.is_empty() {
            return Err(crate::VezaError::Config("JWT audience cannot be empty".to_string()));
        }
        Ok(())
    }
}

/// Rate limiting configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
    pub enabled: bool,
    pub global_limit: u32,
    pub global_window: u64,
    pub per_user_limit: u32,
    pub per_user_window: u64,
    pub per_ip_limit: u32,
    pub per_ip_window: u64,
}

impl Default for RateLimitConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            global_limit: 1000,
            global_window: 60,
            per_user_limit: 100,
            per_user_window: 60,
            per_ip_limit: 60,
            per_ip_window: 60,
        }
    }
}

impl VezaConfig for RateLimitConfig {
    fn load_from_env(&mut self) -> crate::VezaResult<()> {
        if let Ok(enabled) = env::var("RATE_LIMIT_ENABLED") {
            self.enabled = enabled.parse()
                .map_err(|_| crate::VezaError::Config("Invalid RATE_LIMIT_ENABLED".to_string()))?;
        }
        if let Ok(limit) = env::var("RATE_LIMIT_GLOBAL_LIMIT") {
            self.global_limit = limit.parse()
                .map_err(|_| crate::VezaError::Config("Invalid RATE_LIMIT_GLOBAL_LIMIT".to_string()))?;
        }
        if let Ok(window) = env::var("RATE_LIMIT_GLOBAL_WINDOW") {
            self.global_window = window.parse()
                .map_err(|_| crate::VezaError::Config("Invalid RATE_LIMIT_GLOBAL_WINDOW".to_string()))?;
        }
        if let Ok(limit) = env::var("RATE_LIMIT_PER_USER_LIMIT") {
            self.per_user_limit = limit.parse()
                .map_err(|_| crate::VezaError::Config("Invalid RATE_LIMIT_PER_USER_LIMIT".to_string()))?;
        }
        if let Ok(window) = env::var("RATE_LIMIT_PER_USER_WINDOW") {
            self.per_user_window = window.parse()
                .map_err(|_| crate::VezaError::Config("Invalid RATE_LIMIT_PER_USER_WINDOW".to_string()))?;
        }
        if let Ok(limit) = env::var("RATE_LIMIT_PER_IP_LIMIT") {
            self.per_ip_limit = limit.parse()
                .map_err(|_| crate::VezaError::Config("Invalid RATE_LIMIT_PER_IP_LIMIT".to_string()))?;
        }
        if let Ok(window) = env::var("RATE_LIMIT_PER_IP_WINDOW") {
            self.per_ip_window = window.parse()
                .map_err(|_| crate::VezaError::Config("Invalid RATE_LIMIT_PER_IP_WINDOW".to_string()))?;
        }
        Ok(())
    }

    fn validate(&self) -> crate::VezaResult<()> {
        if self.global_limit == 0 {
            return Err(crate::VezaError::Config("Global rate limit must be > 0".to_string()));
        }
        if self.global_window == 0 {
            return Err(crate::VezaError::Config("Global rate limit window must be > 0".to_string()));
        }
        if self.per_user_limit == 0 {
            return Err(crate::VezaError::Config("Per-user rate limit must be > 0".to_string()));
        }
        if self.per_user_window == 0 {
            return Err(crate::VezaError::Config("Per-user rate limit window must be > 0".to_string()));
        }
        if self.per_ip_limit == 0 {
            return Err(crate::VezaError::Config("Per-IP rate limit must be > 0".to_string()));
        }
        if self.per_ip_window == 0 {
            return Err(crate::VezaError::Config("Per-IP rate limit window must be > 0".to_string()));
        }
        Ok(())
    }
}

/// Logging configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
    pub level: String,
    pub format: String,
    pub file: Option<String>,
    pub max_size: u64,
    pub max_files: u32,
    pub compress: bool,
}

impl Default for LoggingConfig {
    fn default() -> Self {
        Self {
            level: "info".to_string(),
            format: "json".to_string(),
            file: None,
            max_size: 100 * 1024 * 1024, // 100MB
            max_files: 5,
            compress: true,
        }
    }
}

impl VezaConfig for LoggingConfig {
    fn load_from_env(&mut self) -> crate::VezaResult<()> {
        if let Ok(level) = env::var("LOG_LEVEL") {
            self.level = level;
        }
        if let Ok(format) = env::var("LOG_FORMAT") {
            self.format = format;
        }
        if let Ok(file) = env::var("LOG_FILE") {
            self.file = Some(file);
        }
        if let Ok(size) = env::var("LOG_MAX_SIZE") {
            self.max_size = size.parse()
                .map_err(|_| crate::VezaError::Config("Invalid LOG_MAX_SIZE".to_string()))?;
        }
        if let Ok(files) = env::var("LOG_MAX_FILES") {
            self.max_files = files.parse()
                .map_err(|_| crate::VezaError::Config("Invalid LOG_MAX_FILES".to_string()))?;
        }
        if let Ok(compress) = env::var("LOG_COMPRESS") {
            self.compress = compress.parse()
                .map_err(|_| crate::VezaError::Config("Invalid LOG_COMPRESS".to_string()))?;
        }
        Ok(())
    }

    fn validate(&self) -> crate::VezaResult<()> {
        let valid_levels = ["trace", "debug", "info", "warn", "error"];
        if !valid_levels.contains(&self.level.as_str()) {
            return Err(crate::VezaError::Config(format!("Invalid log level: {}", self.level)));
        }
        let valid_formats = ["json", "text"];
        if !valid_formats.contains(&self.format.as_str()) {
            return Err(crate::VezaError::Config(format!("Invalid log format: {}", self.format)));
        }
        if self.max_size == 0 {
            return Err(crate::VezaError::Config("Log max size must be > 0".to_string()));
        }
        if self.max_files == 0 {
            return Err(crate::VezaError::Config("Log max files must be > 0".to_string()));
        }
        Ok(())
    }
}

/// Load configuration from environment with validation
pub fn load_config<T: VezaConfig>() -> crate::VezaResult<T> {
    let config = T::from_env()?;
    info!("Configuration loaded successfully");
    Ok(config)
}

/// Load configuration from file
pub fn load_config_from_file<T: VezaConfig>(path: &str) -> crate::VezaResult<T>
where
    T: for<'de> Deserialize<'de>,
{
    let content = std::fs::read_to_string(path)
        .map_err(|e| crate::VezaError::Config(format!("Failed to read config file {}: {}", path, e)))?;
    
    let config: T = serde_json::from_str(&content)
        .map_err(|e| crate::VezaError::Config(format!("Failed to parse config file {}: {}", path, e)))?;
    
    config.validate()?;
    info!("Configuration loaded from file: {}", path);
    Ok(config)
}
