//! Main Configuration struct and implementation
//!
//! This module contains the main Config struct and all its implementation
//! methods.

use std::env;
use std::path::{Path, PathBuf};

use dotenv;
use regex;
use serde::{Deserialize, Serialize};

use super::*;

/// Main configuration structure containing all server settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    pub server: ServerConfig,
    pub database: DatabaseConfig,
    pub session: SessionConfig,
    pub cors: CorsConfig,
    #[serde(rename = "static")]
    pub static_files: StaticConfig,
    pub server_dirs: ServerDirConfig,
    pub security: SecurityConfig,
    pub oauth: OAuthConfig,
    pub email: EmailConfig,
    pub redis: RedisConfig,
    pub app: AppConfig,
    pub logging: LoggingConfig,
    #[cfg(feature = "content-db")]
    pub content: ContentConfig,
    pub cache: super::ServerCacheConfig,
    pub features: super::FeatureConfig,
    #[serde(default = "default_root_path")]
    pub root_path: String,
    #[serde(default)]
    pub routing: RoutingConfig,

    /// Navigation and application log paths (from `site/config/logs.ncl`).
    #[serde(default)]
    pub logs: super::LogsConfig,

    /// File-based RBAC policy configuration (optional, feature-gated)
    #[cfg(feature = "rbac")]
    #[serde(default)]
    pub rbac: super::RbacNclConfig,

    /// Event pipeline configuration (path to `site/pipelines/index.ncl`).
    #[cfg(feature = "nats-admin")]
    #[serde(default)]
    pub pipelines: super::PipelinesNclConfig,

    /// External services configuration (NATS admin, etc.) from
    /// `external-services.ncl`.
    #[serde(default)]
    pub external_services: super::ExternalServicesConfig,

    // Encryption instance (not serialized)
    #[serde(skip)]
    #[cfg(feature = "crypto")]
    pub encryption: Option<super::ConfigEncryption>,
}

fn default_root_path() -> String {
    // Use standard env variable for root path with fallback
    std::env::var("SITE_CONTENT_PATH")
        .or_else(|_| std::env::var("PROJECT_ROOT"))
        .unwrap_or_else(|_| ".".to_string())
}

impl Default for Config {
    fn default() -> Self {
        Self {
            server: ServerConfig::default(),
            database: DatabaseConfig {
                url: "postgresql://username:password@localhost:5432/database_name".to_string(),
                max_connections: 10,
                min_connections: 1,
                connect_timeout: 30,
                idle_timeout: 600,
                max_lifetime: 1800,
                create_if_missing: true,
            },
            session: SessionConfig {
                secret: "change-this-in-production-to-a-secure-random-string".to_string(),
                cookie_name: "session_id".to_string(),
                cookie_secure: false,
                cookie_http_only: true,
                cookie_same_site: "lax".to_string(),
                max_age: 3600,
            },
            cors: CorsConfig {
                allowed_origins: vec![
                    "http://localhost:3030".to_string(),
                    "http://127.0.0.1:3030".to_string(),
                ],
                allowed_methods: vec![
                    "GET".to_string(),
                    "POST".to_string(),
                    "PUT".to_string(),
                    "DELETE".to_string(),
                    "OPTIONS".to_string(),
                ],
                allowed_headers: vec![
                    "Content-Type".to_string(),
                    "Authorization".to_string(),
                    "X-Requested-With".to_string(),
                ],
                allow_credentials: true,
                max_age: 3600,
            },
            static_files: StaticConfig {
                assets_dir: "public".to_string(),
                site_root: "target/site".to_string(),
                site_pkg_dir: "pkg".to_string(),
            },
            server_dirs: ServerDirConfig {
                public_dir: "public".to_string(),
                uploads_dir: "uploads".to_string(),
                logs_dir: "logs".to_string(),
                temp_dir: "tmp".to_string(),
                cache_dir: "cache".to_string(),
                config_dir: "config".to_string(),
                data_dir: "data".to_string(),
                backup_dir: "backups".to_string(),
                template_dir: Some("templates".to_string()),
            },
            security: SecurityConfig {
                enable_csrf: true,
                csrf_token_name: "csrf_token".to_string(),
                rate_limit_requests: 100,
                rate_limit_window: 60,
                bcrypt_cost: 12,
            },
            oauth: OAuthConfig {
                enabled: false,
                google: None,
                github: None,
            },
            email: EmailConfig {
                enabled: false,
                provider: "console".to_string(),
                smtp_host: "smtp.gmail.com".to_string(),
                smtp_port: 587,
                smtp_username: "your-email@gmail.com".to_string(),
                smtp_password: "your-app-password".to_string(),
                smtp_use_tls: false,
                smtp_use_starttls: true,
                sendgrid_api_key: "".to_string(),
                sendgrid_endpoint: "https://api.sendgrid.com/v3/mail/send".to_string(),
                from_email: "noreply@yourapp.com".to_string(),
                from_name: "Your App".to_string(),
                template_dir: None,
                email_enabled: Some(true),
            },
            redis: RedisConfig {
                enabled: false,
                url: "redis://localhost:6379".to_string(),
                pool_size: 10,
                connection_timeout: 5,
                command_timeout: 5,
            },
            app: AppConfig {
                name: "My Rust App".to_string(),
                version: "0.1.0".to_string(),
                debug: true,
                enable_metrics: false,
                enable_health_check: true,
                enable_compression: true,
                max_request_size: 10485760,
                admin_email: Some("admin@example.com".to_string()),
            },
            logging: LoggingConfig {
                format: "json".to_string(),
                level: "info".to_string(),
                file_path: "logs/app.log".to_string(),
                max_file_size: 10485760,
                max_files: 5,
                enable_console: true,
                enable_file: false,
            },
            #[cfg(feature = "content-db")]
            content: ContentConfig {
                enabled: false,
                content_dir: "content".to_string(),
                cache_enabled: true,
                cache_ttl: 3600,
                max_file_size: 5242880,
            },
            cache: super::ServerCacheConfig::default(),
            features: super::FeatureConfig::default(),
            #[cfg(feature = "rbac")]
            rbac: super::RbacNclConfig::default(),
            #[cfg(feature = "nats-admin")]
            pipelines: super::PipelinesNclConfig::default(),
            #[cfg(feature = "crypto")]
            encryption: None,
            root_path: default_root_path(),
            routing: RoutingConfig::default(),
            logs: super::LogsConfig::default(),
            external_services: super::ExternalServicesConfig::default(),
        }
    }
}

impl Config {
    /// Load configuration from the unified NCL config file.
    ///
    /// Resolution order for the config path:
    ///   1. `SITE_CONFIG_PATH` env var
    ///   2. `<current_dir>/site/config/config.ncl` (project-local default)
    ///
    /// The `nickel` binary must be available in `PATH` at runtime.
    pub fn load() -> Result<Self, ConfigError> {
        dotenv::dotenv().ok();

        let ncl_path = env::var("SITE_CONFIG_PATH")
            .ok()
            .map(PathBuf::from)
            .and_then(|p| {
                if p.is_absolute() {
                    Some(p)
                } else {
                    env::current_dir().ok().map(|d| d.join(p))
                }
            })
            .unwrap_or_else(|| {
                env::current_dir()
                    .ok()
                    .map(|d| d.join("site/config/config.ncl"))
                    .unwrap_or_else(|| PathBuf::from("site/config/config.ncl"))
            });

        let json = rustelo_config::nickel::export_to_json(&ncl_path).map_err(|e| {
            ConfigError::ReadError(format!(
                "NCL config export failed for '{}': {}\nEnsure `nickel` is in PATH and \
                 SITE_CONFIG_PATH points to site/config.ncl",
                ncl_path.display(),
                e
            ))
        })?;

        let mut config: Self = serde_json::from_str(&json)
            .map_err(|e| ConfigError::ParseError(format!("Config deserialisation failed: {e}")))?;

        // Initialize encryption using root_path resolved from env (via serde default).
        #[cfg(feature = "crypto")]
        {
            config.encryption = Some(super::ConfigEncryption::new(&config.root_path).map_err(
                |e| ConfigError::EncryptionError(format!("Encryption init failed: {e}")),
            )?);
        }

        config = Self::apply_env_overrides(config)?;
        config = Self::substitute_env_vars(config)?;
        config = Self::decrypt_encrypted_values(config)?;
        config = Self::resolve_paths(config)?;
        config.validate()?;

        Ok(config)
    }

    /// Resolve relative paths to absolute paths using ROOT_PATH
    fn resolve_paths(mut config: Self) -> Result<Self, ConfigError> {
        let root_path = PathBuf::from(&config.root_path);

        // Resolve static file paths
        config.static_files.assets_dir =
            Self::resolve_path(&root_path, &config.static_files.assets_dir)?;
        config.static_files.site_root =
            Self::resolve_path(&root_path, &config.static_files.site_root)?;
        config.static_files.site_pkg_dir =
            Self::resolve_path(&root_path, &config.static_files.site_pkg_dir)?;

        // Resolve server directory paths
        config.server_dirs.public_dir =
            Self::resolve_path(&root_path, &config.server_dirs.public_dir)?;
        config.server_dirs.uploads_dir =
            Self::resolve_path(&root_path, &config.server_dirs.uploads_dir)?;
        config.server_dirs.logs_dir = Self::resolve_path(&root_path, &config.server_dirs.logs_dir)?;
        config.server_dirs.temp_dir = Self::resolve_path(&root_path, &config.server_dirs.temp_dir)?;
        config.server_dirs.cache_dir =
            Self::resolve_path(&root_path, &config.server_dirs.cache_dir)?;
        config.server_dirs.config_dir =
            Self::resolve_path(&root_path, &config.server_dirs.config_dir)?;
        config.server_dirs.data_dir = Self::resolve_path(&root_path, &config.server_dirs.data_dir)?;
        config.server_dirs.backup_dir =
            Self::resolve_path(&root_path, &config.server_dirs.backup_dir)?;

        // Resolve TLS certificate paths if present
        if let Some(ref mut tls) = config.server.tls {
            tls.cert_path = PathBuf::from(Self::resolve_path(
                &root_path,
                &tls.cert_path.to_string_lossy(),
            )?);
            tls.key_path = PathBuf::from(Self::resolve_path(
                &root_path,
                &tls.key_path.to_string_lossy(),
            )?);
        }

        // Resolve logging file path
        config.logging.file_path = Self::resolve_path(&root_path, &config.logging.file_path)?;

        // Resolve navigation log directory
        config.logs.nav_log_dir = Self::resolve_path(&root_path, &config.logs.nav_log_dir)?;

        // Resolve content directory path if content feature is enabled
        #[cfg(feature = "content-db")]
        {
            config.content.content_dir =
                Self::resolve_path(&root_path, &config.content.content_dir)?;
        }

        Ok(config)
    }

    /// Resolve a relative path to an absolute path using the root path
    fn resolve_path(root_path: &Path, path: &str) -> Result<String, ConfigError> {
        let path_buf = PathBuf::from(path);

        // If the path is already absolute, return it as-is
        if path_buf.is_absolute() {
            return Ok(path.to_string());
        }

        // Resolve relative path against root path
        let resolved = root_path.join(path_buf);
        let canonical = resolved.canonicalize().unwrap_or(resolved);

        Ok(canonical.to_string_lossy().to_string())
    }

    /// Get the absolute path for a given relative path
    #[allow(dead_code)]
    pub fn get_absolute_path(&self, relative_path: &str) -> Result<String, ConfigError> {
        let root_path = PathBuf::from(&self.root_path);
        Self::resolve_path(&root_path, relative_path)
    }

    /// Apply environment variable overrides to configuration
    fn apply_env_overrides(mut config: Self) -> Result<Self, ConfigError> {
        // Server overrides
        if let Ok(protocol) = env::var("SERVER_PROTOCOL") {
            config.server.protocol = match protocol.to_lowercase().as_str() {
                "https" => Protocol::Https,
                _ => Protocol::Http,
            };
        }
        if let Ok(host) = env::var("SERVER_HOST") {
            config.server.host = host;
        }
        if let Ok(port) = env::var("SERVER_PORT") {
            config.server.port = port
                .parse()
                .map_err(|_| ConfigError::ParseError(format!("Invalid port number: {port}")))?;
        }
        if let Ok(env) = env::var("ENVIRONMENT") {
            config.server.environment = match env.to_lowercase().as_str() {
                "production" | "prod" => Environment::Production,
                _ => Environment::Development,
            };
        }
        if let Ok(log_level) = env::var("LOG_LEVEL") {
            config.server.log_level = log_level;
        }

        // TLS overrides
        if let Ok(cert_path) = env::var("TLS_CERT_PATH") {
            if let Ok(key_path) = env::var("TLS_KEY_PATH") {
                config.server.tls = Some(TlsConfig {
                    cert_path: PathBuf::from(cert_path),
                    key_path: PathBuf::from(key_path),
                });
            }
        }

        // Database overrides — priority: DATABASE_URL > NCL_DATABASE_URL > TOML default
        if let Ok(database_url) = env::var("DATABASE_URL") {
            config.database.url = database_url;
        } else if let Ok(ncl_url) = env::var("NCL_DATABASE_URL") {
            config.database.url = ncl_url;
        }

        // NCL pool/behaviour overrides — applied regardless of URL source
        if let Ok(val) = env::var("NCL_DATABASE_CREATE_IF_MISSING") {
            config.database.create_if_missing = val != "false" && val != "0";
        }
        if let Ok(val) = env::var("NCL_DATABASE_MAX_CONNECTIONS") {
            if let Ok(n) = val.parse::<u32>() {
                config.database.max_connections = n;
            }
        }
        if let Ok(val) = env::var("NCL_DATABASE_MIN_CONNECTIONS") {
            if let Ok(n) = val.parse::<u32>() {
                config.database.min_connections = n;
            }
        }
        if let Ok(val) = env::var("NCL_DATABASE_CONNECT_TIMEOUT") {
            if let Ok(n) = val.parse::<u64>() {
                config.database.connect_timeout = n;
            }
        }
        if let Ok(val) = env::var("NCL_DATABASE_IDLE_TIMEOUT") {
            if let Ok(n) = val.parse::<u64>() {
                config.database.idle_timeout = n;
            }
        }
        if let Ok(val) = env::var("NCL_DATABASE_MAX_LIFETIME") {
            if let Ok(n) = val.parse::<u64>() {
                config.database.max_lifetime = n;
            }
        }

        // Session overrides
        if let Ok(session_secret) = env::var("SESSION_SECRET") {
            config.session.secret = session_secret;
        }

        // Root path override
        if let Ok(root_path) = env::var("ROOT_PATH") {
            config.root_path = root_path;
        }

        // Nav log directory override — takes precedence over logs.ncl value.
        // resolve_paths() will make it absolute afterwards.
        if let Ok(nav_log_dir) = env::var("RUSTELO_NAV_LOG_DIR") {
            config.logs.nav_log_dir = nav_log_dir;
        }

        Ok(config)
    }

    /// Substitute environment variables in string values
    fn substitute_env_vars(mut config: Self) -> Result<Self, ConfigError> {
        // Database URL substitution
        config.database.url = Self::substitute_env_in_string(&config.database.url);

        // Session secret substitution
        config.session.secret = Self::substitute_env_in_string(&config.session.secret);

        // Email configuration substitution. Cover host + sender fields too, not
        // just the credentials, so `${SMTP_HOST}` / `${EMAIL_FROM}` in email.ncl
        // resolve from the environment like the username/password do.
        config.email.smtp_host = Self::substitute_env_in_string(&config.email.smtp_host);
        config.email.smtp_username = Self::substitute_env_in_string(&config.email.smtp_username);
        config.email.smtp_password = Self::substitute_env_in_string(&config.email.smtp_password);
        config.email.from_email = Self::substitute_env_in_string(&config.email.from_email);
        config.email.from_name = Self::substitute_env_in_string(&config.email.from_name);
        config.email.sendgrid_api_key =
            Self::substitute_env_in_string(&config.email.sendgrid_api_key);

        // OAuth configuration substitution
        if let Some(ref mut google) = config.oauth.google {
            google.client_id = Self::substitute_env_in_string(&google.client_id);
            google.client_secret = Self::substitute_env_in_string(&google.client_secret);
        }
        if let Some(ref mut github) = config.oauth.github {
            github.client_id = Self::substitute_env_in_string(&github.client_id);
            github.client_secret = Self::substitute_env_in_string(&github.client_secret);
        }

        // Redis URL substitution
        config.redis.url = Self::substitute_env_in_string(&config.redis.url);

        // NATS admin substitution
        if let Some(ref mut na) = config.external_services.nats_admin {
            na.url = Self::substitute_env_in_string(&na.url);
            na.credentials_file = Self::substitute_env_in_string(&na.credentials_file);
            na.namespace.prefix = Self::substitute_env_in_string(&na.namespace.prefix);
            na.namespace.env = Self::substitute_env_in_string(&na.namespace.env);
            na.nkey_seed = na
                .nkey_seed
                .as_deref()
                .map(|s| Self::substitute_env_in_string(s));
        }

        // Pipeline subsystem substitution
        #[cfg(feature = "nats-admin")]
        {
            config.pipelines.ncl_path = Self::substitute_env_in_string(&config.pipelines.ncl_path);
            config.pipelines.scripts_root =
                Self::substitute_env_in_string(&config.pipelines.scripts_root);
        }

        Ok(config)
    }

    /// Substitute environment variables in a string
    pub fn substitute_env_in_string(input: &str) -> String {
        let mut result = input.to_string();

        // Match patterns like ${VAR_NAME} or $VAR_NAME
        let re = regex::Regex::new(r"\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)").unwrap();

        result = re
            .replace_all(&result, |caps: &regex::Captures| {
                let var_name = caps.get(1).or_else(|| caps.get(2)).unwrap().as_str();
                env::var(var_name).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_string())
            })
            .to_string();

        result
    }

    /// Decrypt encrypted configuration values (values starting with '@')
    #[cfg_attr(not(feature = "crypto"), allow(unused_mut))]
    fn decrypt_encrypted_values(mut config: Self) -> Result<Self, ConfigError> {
        #[cfg(feature = "crypto")]
        if let Some(ref encryption) = config.encryption {
            // Decrypt database configuration
            config.database.url = encryption
                .decrypt_if_encrypted(&config.database.url)
                .map_err(|e| {
                    ConfigError::EncryptionError(format!("Failed to decrypt database.url: {}", e))
                })?;

            // Decrypt session configuration
            config.session.secret = encryption
                .decrypt_if_encrypted(&config.session.secret)
                .map_err(|e| {
                    ConfigError::EncryptionError(format!("Failed to decrypt session.secret: {}", e))
                })?;

            // Decrypt email configuration
            config.email.smtp_username = encryption
                .decrypt_if_encrypted(&config.email.smtp_username)
                .map_err(|e| {
                    ConfigError::EncryptionError(format!(
                        "Failed to decrypt email.smtp_username: {}",
                        e
                    ))
                })?;
            config.email.smtp_password = encryption
                .decrypt_if_encrypted(&config.email.smtp_password)
                .map_err(|e| {
                    ConfigError::EncryptionError(format!(
                        "Failed to decrypt email.smtp_password: {}",
                        e
                    ))
                })?;
            config.email.sendgrid_api_key = encryption
                .decrypt_if_encrypted(&config.email.sendgrid_api_key)
                .map_err(|e| {
                    ConfigError::EncryptionError(format!(
                        "Failed to decrypt email.sendgrid_api_key: {}",
                        e
                    ))
                })?;

            // Decrypt OAuth configuration
            if let Some(ref mut google) = config.oauth.google {
                google.client_id =
                    encryption
                        .decrypt_if_encrypted(&google.client_id)
                        .map_err(|e| {
                            ConfigError::EncryptionError(format!(
                                "Failed to decrypt oauth.google.client_id: {}",
                                e
                            ))
                        })?;
                google.client_secret = encryption
                    .decrypt_if_encrypted(&google.client_secret)
                    .map_err(|e| {
                        ConfigError::EncryptionError(format!(
                            "Failed to decrypt oauth.google.client_secret: {}",
                            e
                        ))
                    })?;
            }
            if let Some(ref mut github) = config.oauth.github {
                github.client_id =
                    encryption
                        .decrypt_if_encrypted(&github.client_id)
                        .map_err(|e| {
                            ConfigError::EncryptionError(format!(
                                "Failed to decrypt oauth.github.client_id: {}",
                                e
                            ))
                        })?;
                github.client_secret = encryption
                    .decrypt_if_encrypted(&github.client_secret)
                    .map_err(|e| {
                        ConfigError::EncryptionError(format!(
                            "Failed to decrypt oauth.github.client_secret: {}",
                            e
                        ))
                    })?;
            }

            // Decrypt Redis configuration
            config.redis.url = encryption
                .decrypt_if_encrypted(&config.redis.url)
                .map_err(|e| {
                    ConfigError::EncryptionError(format!("Failed to decrypt redis.url: {}", e))
                })?;
        }

        Ok(config)
    }

    /// Validate the configuration
    pub fn validate(&self) -> Result<(), ConfigError> {
        // Validate port range
        if self.server.port == 0 {
            return Err(ConfigError::ValidationError(
                "Server port must be between 1 and 65535".to_string(),
            ));
        }

        // Validate HTTPS configuration
        if self.server.protocol == Protocol::Https && self.server.tls.is_none() {
            return Err(ConfigError::ValidationError(
                "HTTPS protocol requires TLS configuration".to_string(),
            ));
        }

        // Validate database connection string only if database features are enabled
        let database_features_enabled = self.features.auth.enabled || self.features.content.enabled;
        if database_features_enabled && self.database.url.is_empty() {
            return Err(ConfigError::ValidationError(
                "Database URL cannot be empty when database features are enabled".to_string(),
            ));
        }

        // Validate session secret
        if self.session.secret.is_empty() {
            return Err(ConfigError::ValidationError(
                "Session secret cannot be empty".to_string(),
            ));
        }

        // Validate root path exists
        let root_path = Path::new(&self.root_path);
        if !root_path.exists() {
            return Err(ConfigError::ValidationError(format!(
                "Root path '{}' does not exist",
                self.root_path
            )));
        }

        Ok(())
    }

    /// Get server address for binding
    pub fn server_address(&self) -> String {
        format!("{}:{}", self.server.host, self.server.port)
    }

    /// Check if running in production mode
    pub fn is_production(&self) -> bool {
        matches!(self.server.environment, Environment::Production)
    }

    /// Check if running in development mode
    #[allow(dead_code)]
    pub fn is_development(&self) -> bool {
        matches!(self.server.environment, Environment::Development)
    }

    /// Get full server URL
    #[allow(dead_code)]
    pub fn server_url(&self) -> String {
        match self.server.protocol {
            Protocol::Http => format!("http://{}:{}", self.server.host, self.server.port),
            Protocol::Https => format!("https://{}:{}", self.server.host, self.server.port),
        }
    }

    /// Check if TLS is required
    #[allow(dead_code)]
    pub fn requires_tls(&self) -> bool {
        matches!(self.server.protocol, Protocol::Https)
    }

    /// Get database pool configuration
    #[allow(dead_code)]
    pub fn database_pool_config(&self) -> DatabasePoolConfig {
        DatabasePoolConfig {
            url: self.database.url.clone(),
            max_connections: self.database.max_connections,
            min_connections: self.database.min_connections,
            connect_timeout: std::time::Duration::from_secs(self.database.connect_timeout),
            idle_timeout: std::time::Duration::from_secs(self.database.idle_timeout),
            max_lifetime: std::time::Duration::from_secs(self.database.max_lifetime),
        }
    }

    /// Create a shared CacheConfig with the proper cache directory path
    #[allow(dead_code)]
    pub fn create_content_cache_config(&self) -> rustelo_core_lib::cache::CacheConfig {
        rustelo_core_lib::cache::CacheConfig {
            persistent_cache_dir: Some(std::path::PathBuf::from(&self.server_dirs.cache_dir)),
            enable_persistent_cache: self.cache.persistent_enabled,
            max_entries: self.cache.memory_max_size as usize / 1000, /* Rough estimate for entries from memory size */
            max_memory_bytes: self.cache.memory_max_size as usize,
            default_ttl_seconds: self.cache.memory_ttl,
            ..Default::default()
        }
    }
}
