#![forbid(unsafe_code)]

//! Configuration module for the lifecycle API server.
//!
//! Configuration is loaded from a TOML file specified via `--config` command line argument.
//! All settings must be explicitly configured in the TOML file - there are no defaults or fallbacks.
//!
//! # Modular Configuration Architecture
//!
//! The configuration system uses a two-tier approach:
//! 1. **Main config** (`syntaxis-api-config.toml`): Core server settings + feature flags
//! 2. **Feature configs** (`configs/features/{feature}.toml`): Feature-specific settings
//!
//! Each feature can be enabled/disabled via the main config. If enabled, the feature's
//! configuration is loaded from the corresponding feature config file.

use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use thiserror::Error;

/// Configuration errors
#[derive(Error, Debug)]
pub enum ConfigError {
    /// I/O error reading configuration file
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    /// TOML parsing error
    #[error("TOML parsing error: {0}")]
    TomlError(#[from] toml::de::Error),

    /// Custom configuration error
    #[error("Configuration error: {0}")]
    Custom(String),
}

/// Feature flag configuration
/// Indicates whether a feature is enabled and contains raw config data if present
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FeatureFlag {
    /// Is this feature enabled?
    pub enabled: bool,
}

/// Features configuration
/// Defines which features are enabled and manages their individual configurations
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FeaturesConfig {
    /// Database feature (migrations, connection pooling, etc.)
    #[serde(default)]
    pub database: FeatureFlag,

    /// Health check feature (status endpoint, database ping, etc.)
    #[serde(default)]
    pub health: FeatureFlag,

    /// Metrics feature (Prometheus, tracing, etc.)
    #[serde(default)]
    pub metrics: FeatureFlag,

    /// Rate limiting feature
    #[serde(default)]
    pub rate_limit: FeatureFlag,

    /// Authentication feature (API keys, etc.)
    #[serde(default)]
    pub auth: FeatureFlag,

    /// Cache feature (in-memory or Redis)
    #[serde(default)]
    pub cache: FeatureFlag,

    /// Multi-tenant feature
    #[serde(default)]
    pub multi_tenant: FeatureFlag,
}

impl FeaturesConfig {
    /// Get list of enabled feature names
    pub fn enabled_features(&self) -> Vec<&'static str> {
        let mut features = Vec::new();
        if self.database.enabled {
            features.push("database");
        }
        if self.health.enabled {
            features.push("health");
        }
        if self.metrics.enabled {
            features.push("metrics");
        }
        if self.rate_limit.enabled {
            features.push("rate_limit");
        }
        if self.auth.enabled {
            features.push("auth");
        }
        if self.cache.enabled {
            features.push("cache");
        }
        if self.multi_tenant.enabled {
            features.push("multi_tenant");
        }
        features
    }

    /// Load features configuration from [server.features] section of main config
    fn load_from_table(table: &toml::Table) -> Result<Self, ConfigError> {
        if let Some(features_section) = table.get("features") {
            let features_str = toml::to_string(features_section).map_err(|e| {
                ConfigError::Custom(format!("Failed to convert features section: {}", e))
            })?;

            toml::from_str(&features_str).map_err(|e| {
                ConfigError::Custom(format!(
                    "Failed to deserialize features configuration: {}",
                    e
                ))
            })
        } else {
            // If no features section, all features are disabled
            Ok(FeaturesConfig::default())
        }
    }
}

/// TLS configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
    /// Enable TLS/HTTPS
    pub enabled: bool,

    /// Path to certificate file (required if enabled)
    pub cert_path: Option<String>,

    /// Path to private key file (required if enabled)
    pub key_path: Option<String>,
}

/// Metrics configuration
/// Loaded from configs/features/metrics.toml when metrics feature is enabled
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsConfig {
    /// Metrics endpoint path
    pub metrics_path: String,

    /// Histogram buckets for request duration (in milliseconds)
    #[serde(default = "default_request_duration_buckets")]
    pub request_duration_buckets: Vec<f64>,

    /// Enable detailed request tracing
    #[serde(default)]
    pub trace_requests: bool,

    /// Enable performance metrics collection
    #[serde(default = "default_true")]
    pub collect_performance_metrics: bool,

    /// Enable memory usage tracking
    #[serde(default = "default_true")]
    pub track_memory: bool,
}

fn default_request_duration_buckets() -> Vec<f64> {
    vec![
        1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0, 2500.0, 5000.0,
    ]
}

fn default_true() -> bool {
    true
}

/// Rate limit configuration
/// Loaded from configs/features/rate_limit.toml when rate_limit feature is enabled
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
    /// Requests allowed per second (can be fractional)
    pub requests_per_second: f64,

    /// Maximum burst size
    pub burst_size: f64,

    /// Enable rate limit metric collection
    #[serde(default = "default_true")]
    pub rate_limit_health_metrics: bool,

    /// Custom rate limit error message
    #[serde(default = "default_rate_limit_message")]
    pub rate_limit_message: String,
}

fn default_rate_limit_message() -> String {
    "Rate limit exceeded".to_string()
}

/// Authentication configuration
/// Loaded from configs/features/auth.toml when auth feature is enabled
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
    /// List of valid API keys
    pub api_keys: Vec<String>,

    /// JWT secret key (minimum 32 characters for HS256)
    /// If not provided, JWT functionality will be disabled
    #[serde(default)]
    pub jwt_secret: Option<String>,

    /// Header name for authentication (default: Authorization)
    #[serde(default = "default_auth_header")]
    pub auth_header: String,

    /// Authentication prefix (default: Bearer)
    #[serde(default = "default_auth_prefix")]
    pub auth_prefix: String,

    /// Enable strict key validation
    #[serde(default = "default_true")]
    pub strict_validation: bool,

    /// Log authentication failures
    #[serde(default = "default_true")]
    pub log_failures: bool,

    /// Enable JWT token validation (default: false)
    /// If true, jwt_secret must be provided
    #[serde(default)]
    pub jwt_enabled: bool,

    /// JWT token expiration in seconds (default: 3600 = 1 hour)
    #[serde(default = "default_jwt_expiration")]
    pub jwt_expiration: u64,
}

fn default_auth_header() -> String {
    "Authorization".to_string()
}

fn default_auth_prefix() -> String {
    "Bearer".to_string()
}

fn default_jwt_expiration() -> u64 {
    3600 // 1 hour in seconds
}

/// Server configuration
/// All fields are required and must be specified in the configuration file.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    /// Server host address (required)
    pub host: String,

    /// Server port (required)
    pub port: u16,

    /// Database file path (required)
    pub database_path: String,

    /// Enable CORS (required)
    pub cors_enabled: bool,

    /// Log level (required)
    pub log_level: String,

    /// Path to directory containing static files (public folder)
    /// Can be absolute or relative to current working directory
    pub public_files_path: String,

    /// TLS configuration (optional)
    #[serde(default)]
    pub tls: Option<TlsConfig>,

    /// Feature flags (optional)
    #[serde(default)]
    pub features: FeaturesConfig,
}

impl ServerConfig {
    /// Load configuration from TOML file.
    ///
    /// # Arguments
    ///
    /// * `config_path` - Path to TOML configuration file
    ///
    /// Configuration is strictly loaded from the TOML file.
    /// The TOML file must have a [server] section containing all required fields.
    ///
    /// # Modular Configuration
    ///
    /// Features are defined in the main config via [server.features] section.
    /// When a feature is enabled, its configuration is expected to be loadable
    /// from the `configs/features/{feature}.toml` file (relative to the main config).
    pub fn load<P: AsRef<std::path::Path>>(config_path: P) -> Result<Self, ConfigError> {
        let path = config_path.as_ref();
        let content = std::fs::read_to_string(path).map_err(|e| {
            ConfigError::Custom(format!("Failed to read config file {:?}: {}", path, e))
        })?;

        // Parse as a table first to extract the [server] section
        let table: toml::Table = toml::from_str(&content).map_err(|e| {
            ConfigError::Custom(format!(
                "Failed to parse TOML config from {:?}: {}",
                path, e
            ))
        })?;

        // Extract the server section
        let server_section = table.get("server").ok_or_else(|| {
            ConfigError::Custom("Missing [server] section in config file".to_string())
        })?;

        // Convert Value to string and deserialize
        let server_str = toml::to_string(server_section)
            .map_err(|e| ConfigError::Custom(format!("Failed to convert server section: {}", e)))?;

        let mut config: Self = toml::from_str(&server_str).map_err(|e| {
            ConfigError::Custom(format!("Failed to deserialize server configuration: {}", e))
        })?;

        // Load features configuration from the [server.features] section
        if let Some(server_value) = table.get("server") {
            if let Some(server_table) = server_value.as_table() {
                config.features = FeaturesConfig::load_from_table(server_table)?;
            }
        }

        Ok(config)
    }

    /// Validate that feature configuration files exist for enabled features
    ///
    /// # Arguments
    ///
    /// * `config_path` - Path to the main config file (used to find features directory)
    /// * `features_dir` - Directory name where feature configs are stored (default: "configs/features")
    ///
    /// This checks that each enabled feature has a corresponding configuration file.
    /// Feature config files should be named `{feature}.toml` in the features directory.
    #[allow(dead_code)]
    pub fn validate_feature_configs<P: AsRef<Path>>(
        &self,
        config_path: P,
        features_dir: &str,
    ) -> Result<(), ConfigError> {
        let config_path = config_path.as_ref();

        // Get the directory containing the main config
        let config_dir = config_path.parent().unwrap_or_else(|| Path::new("."));
        let features_path = config_dir.join(features_dir);

        // Map feature names to config filenames
        let feature_files = vec![
            ("database", "database.toml"),
            ("health", "health.toml"),
            ("metrics", "metrics.toml"),
            ("rate_limit", "rate_limit.toml"),
            ("auth", "auth.toml"),
            ("cache", "cache.toml"),
            ("multi_tenant", "multi_tenant.toml"),
        ];

        for (feature_name, filename) in feature_files {
            let enabled = match feature_name {
                "database" => self.features.database.enabled,
                "health" => self.features.health.enabled,
                "metrics" => self.features.metrics.enabled,
                "rate_limit" => self.features.rate_limit.enabled,
                "auth" => self.features.auth.enabled,
                "cache" => self.features.cache.enabled,
                "multi_tenant" => self.features.multi_tenant.enabled,
                _ => false,
            };

            if enabled {
                let feature_config_path = features_path.join(filename);
                if !feature_config_path.exists() {
                    return Err(ConfigError::Custom(format!(
                        "Feature '{}' is enabled but config file not found: {}",
                        feature_name,
                        feature_config_path.display()
                    )));
                }
            }
        }

        Ok(())
    }

    /// Get the server socket address
    pub fn socket_addr(&self) -> Result<SocketAddr, ConfigError> {
        format!("{}:{}", self.host, self.port).parse().map_err(|_| {
            ConfigError::Custom(format!(
                "Invalid socket address: {}:{}",
                self.host, self.port
            ))
        })
    }

    /// Get the database path as PathBuf
    /// If the path is relative, it's resolved from the current working directory
    pub fn database_path_buf(&self) -> PathBuf {
        let path = PathBuf::from(&self.database_path);
        if path.is_absolute() {
            path
        } else {
            // For relative paths, try to resolve from current directory
            match std::env::current_dir() {
                Ok(cwd) => cwd.join(&path),
                Err(_) => path, // Fallback to original if can't get cwd
            }
        }
    }

    /// Ensure database parent directory exists
    pub fn ensure_database_dir(&self) -> Result<(), ConfigError> {
        let path = self.database_path_buf();
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent).map_err(|e| {
                ConfigError::Custom(format!(
                    "Failed to create database directory {:?}: {}",
                    parent, e
                ))
            })?;
        }
        Ok(())
    }

    /// Get the public files path as PathBuf
    /// If the path is relative, it's resolved from the current working directory
    pub fn public_files_path_buf(&self) -> PathBuf {
        let path = PathBuf::from(&self.public_files_path);
        if path.is_absolute() {
            path
        } else {
            // For relative paths, try to resolve from current directory
            match std::env::current_dir() {
                Ok(cwd) => cwd.join(&path),
                Err(_) => path, // Fallback to original if can't get cwd
            }
        }
    }

    /// Log configuration (with sensitive values masked)
    pub fn log_config(&self) {
        let tls_status = match &self.tls {
            Some(tls) if tls.enabled => "enabled",
            Some(_) => "configured but disabled",
            None => "not configured",
        };

        let enabled_features = self.features.enabled_features();
        let features_str = if enabled_features.is_empty() {
            "none".to_string()
        } else {
            enabled_features.join(", ")
        };

        tracing::info!(
            "Server configuration: {}:{}, database: {}, public_files: {}, cors: {}, log_level: {}, tls: {}, features: [{}]",
            self.host,
            self.port,
            self.database_path,
            self.public_files_path,
            self.cors_enabled,
            self.log_level,
            tls_status,
            features_str
        );
    }

    /// Validate TLS configuration if enabled
    /// Returns an error if TLS is enabled but cert/key paths are not provided
    pub fn validate_tls(&self) -> Result<(), ConfigError> {
        if let Some(tls) = &self.tls {
            if tls.enabled {
                if tls.cert_path.is_none() {
                    return Err(ConfigError::Custom(
                        "TLS enabled but cert_path is not provided".to_string(),
                    ));
                }
                if tls.key_path.is_none() {
                    return Err(ConfigError::Custom(
                        "TLS enabled but key_path is not provided".to_string(),
                    ));
                }
            }
        }
        Ok(())
    }
}

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

    fn create_test_config() -> ServerConfig {
        ServerConfig {
            host: "127.0.0.1".to_string(),
            port: 3000,
            database_path: "/tmp/test.db".to_string(),
            public_files_path: "./public".to_string(),
            cors_enabled: true,
            log_level: "info".to_string(),
            tls: None,
            features: FeaturesConfig::default(),
        }
    }

    #[test]
    fn test_socket_addr() {
        let config = create_test_config();
        let addr = config.socket_addr().unwrap();
        assert_eq!(addr.to_string(), "127.0.0.1:3000");
    }

    #[test]
    fn test_public_files_path() {
        let config = create_test_config();
        let path = config.public_files_path_buf();
        assert!(path.ends_with("public"));
    }

    #[test]
    fn test_validate_tls_disabled() {
        let mut config = create_test_config();
        config.tls = Some(TlsConfig {
            enabled: false,
            cert_path: None,
            key_path: None,
        });
        assert!(config.validate_tls().is_ok());
    }

    #[test]
    fn test_validate_tls_enabled_missing_cert() {
        let mut config = create_test_config();
        config.tls = Some(TlsConfig {
            enabled: true,
            cert_path: None,
            key_path: Some("/path/to/key".to_string()),
        });
        assert!(config.validate_tls().is_err());
    }

    #[test]
    fn test_validate_tls_enabled_missing_key() {
        let mut config = create_test_config();
        config.tls = Some(TlsConfig {
            enabled: true,
            cert_path: Some("/path/to/cert".to_string()),
            key_path: None,
        });
        assert!(config.validate_tls().is_err());
    }

    #[test]
    fn test_features_default_all_disabled() {
        let config = create_test_config();
        assert!(!config.features.database.enabled);
        assert!(!config.features.health.enabled);
        assert!(!config.features.metrics.enabled);
        assert!(!config.features.rate_limit.enabled);
        assert!(!config.features.auth.enabled);
        assert!(!config.features.cache.enabled);
        assert!(!config.features.multi_tenant.enabled);
    }

    #[test]
    fn test_enabled_features_empty() {
        let config = create_test_config();
        let enabled = config.features.enabled_features();
        assert!(enabled.is_empty());
    }

    #[test]
    fn test_enabled_features_with_database() {
        let mut config = create_test_config();
        config.features.database.enabled = true;
        let enabled = config.features.enabled_features();
        assert_eq!(enabled, vec!["database"]);
    }

    #[test]
    fn test_enabled_features_multiple() {
        let mut config = create_test_config();
        config.features.database.enabled = true;
        config.features.health.enabled = true;
        config.features.metrics.enabled = true;
        let enabled = config.features.enabled_features();
        assert_eq!(enabled, vec!["database", "health", "metrics"]);
    }
}
