//! Configuration management for Falcorn
//! Supports TOML files and programmatic configuration

pub mod parser;
pub mod validation;

pub use parser::{CliOverrides, ConfigParser};
pub use validation::{ConfigValidator, ValidationReport};

use crate::errors::{FalcornError, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::time::Duration;

pub use falcorn_proto::logging::LogLevel;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WorkerClass {
    /// Synchronous blocking workers
    Sync,
    /// Asynchronous non-blocking workers
    Async,
}

impl WorkerClass {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Sync => "sync",
            Self::Async => "async",
        }
    }

    pub fn from_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            "sync" => Ok(Self::Sync),
            "async" => Ok(Self::Async),
            other => Err(FalcornError::InvalidWorkerClass {
                class: other.to_string(),
            }),
        }
    }
}

/// Protocol type (WSGI or ASGI)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProtocolType {
    /// WSGI (Web Server Gateway Interface) - Python synchronous
    Wsgi,
    /// ASGI (Asynchronous Server Gateway Interface) - Python async
    Asgi,
}

impl ProtocolType {
    pub fn as_str(&self) -> &'static str {
        match self {
            ProtocolType::Wsgi => "wsgi",
            ProtocolType::Asgi => "asgi",
        }
    }

    pub fn from_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            "wsgi" => Ok(ProtocolType::Wsgi),
            "asgi" => Ok(ProtocolType::Asgi),
            _ => Err(FalcornError::ConfigError {
                reason: format!("Unknown protocol type: {}", s),
            }),
        }
    }
}

impl Default for ProtocolType {
    fn default() -> Self {
        ProtocolType::Wsgi
    }
}

/// Complete server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
    // Network binding
    pub host: String,
    pub port: u16,
    pub backlog: u32,
    pub protocol: ProtocolType,
    #[serde(default)]
    pub h2c_enabled: bool,
    #[serde(default)]
    pub strict_http: bool,

    // Worker configuration
    pub workers: Option<usize>,
    pub worker_class: WorkerClass,
    pub worker_timeout: u64,
    pub worker_mode: bool,
    pub max_requests: Option<usize>,
    pub max_requests_jitter: Option<usize>,

    // Performance
    pub buffer_size: usize,
    pub keepalive_timeout: u64,
    pub max_connections: usize,
    #[serde(default = "default_sync_handler_threads")]
    pub sync_handler_threads: usize,
    #[serde(default = "default_sync_conn_queue_capacity")]
    pub sync_conn_queue_capacity: usize,
    #[serde(default = "default_async_worker_threads")]
    pub async_worker_threads: usize,
    #[serde(default = "default_async_max_blocking_threads")]
    pub async_max_blocking_threads: usize,

    // Application
    pub app_module: String,
    pub app_callable: String,
    pub pythonpath: Vec<String>,
    pub asgi_event_loop: String,
    pub asgi_event_loop_fallback: bool,
    pub asgi_submit_timeout_ms: u64,
    #[serde(default = "default_asgi_scheduler")]
    pub asgi_scheduler: String,
    #[serde(default = "default_asgi_max_inflight")]
    pub asgi_max_inflight: usize,
    #[serde(default = "default_asgi_send_pool_size")]
    pub asgi_send_pool_size: usize,

    // Logging
    pub log_stdout: bool,
    pub log_level: String,
    pub access_log_format: String,
    pub access_log: Option<String>,
    pub error_log: Option<String>,
    pub plugin_socket: Option<String>,
    pub plugin_auth_token: Option<String>,
    pub plugin_drop_policy: String,
    pub plugin_max_lagged_events: usize,
    pub plugin_write_timeout_ms: u64,
    pub plugin_bus_capacity: usize,
    pub plugin_max_clients: usize,
    pub plugin_subscribe_timeout_ms: u64,
    pub plugin_auth_fail_window_secs: u64,
    pub plugin_auth_fail_max_attempts: usize,
    pub plugin_acl_mode: String,
    pub plugin_socket_mode: u32,
    pub metrics_interval_ms: u64,
    pub metrics_workers_enabled: bool,
    pub metrics_os_enabled: bool,
    pub metrics_percentiles_enabled: bool,
    pub metrics_top_errors_limit: usize,
    pub control_socket: Option<String>,
    pub control_auth_token: Option<String>,
    pub control_acl_mode: String,
    pub control_socket_mode: u32,
    #[serde(skip)]
    pub config_path: Option<String>,

    // SSL/TLS
    pub ssl_enabled: bool,
    pub ssl_certfile: Option<String>,
    pub ssl_keyfile: Option<String>,
}

impl ServerConfig {
    /// Create a new configuration with defaults
    pub fn new(app_module: String, app_callable: String) -> Self {
        Self {
            host: "127.0.0.1".to_string(),
            port: 8000,
            protocol: ProtocolType::Wsgi,
            h2c_enabled: false,
            strict_http: false,
            backlog: 2048,
            workers: None,
            worker_class: WorkerClass::Sync,
            worker_timeout: 30,
            worker_mode: false,
            max_requests: None,
            max_requests_jitter: None,
            buffer_size: 4096,
            keepalive_timeout: 5,
            max_connections: 2048,
            sync_handler_threads: default_sync_handler_threads(),
            sync_conn_queue_capacity: default_sync_conn_queue_capacity(),
            async_worker_threads: 4,
            async_max_blocking_threads: 100,
            app_module,
            app_callable,
            pythonpath: vec![],
            asgi_event_loop: "asyncio".to_string(),
            asgi_event_loop_fallback: true,
            asgi_submit_timeout_ms: 30_000,
            asgi_scheduler: default_asgi_scheduler(),
            asgi_max_inflight: default_asgi_max_inflight(),
            asgi_send_pool_size: default_asgi_send_pool_size(),
            log_stdout: false, // Disabled by default
            log_level: "INFO".to_string(),
            access_log_format: r#"%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s"#.to_string(),
            access_log: None,
            error_log: None,
            plugin_socket: None,
            plugin_auth_token: None,
            plugin_drop_policy: "drop_oldest".to_string(),
            plugin_max_lagged_events: 2048,
            plugin_write_timeout_ms: 500,
            plugin_bus_capacity: 8192,
            plugin_max_clients: 1024,
            plugin_subscribe_timeout_ms: 5000,
            plugin_auth_fail_window_secs: 60,
            plugin_auth_fail_max_attempts: 10,
            plugin_acl_mode: "same_user".to_string(),
            plugin_socket_mode: 0o660,
            metrics_interval_ms: 5000,
            metrics_workers_enabled: true,
            metrics_os_enabled: false,
            metrics_percentiles_enabled: false,
            metrics_top_errors_limit: 5,
            control_socket: Some(crate::control::ipc::DEFAULT_CONTROL_SOCKET.to_string()),
            control_auth_token: None,
            control_acl_mode: "same_user".to_string(),
            control_socket_mode: 0o660,
            config_path: None,
            ssl_enabled: false,
            ssl_certfile: None,
            ssl_keyfile: None,
        }
    }

    /// Load configuration from TOML file
    pub fn from_toml<P: AsRef<Path>>(path: P) -> Result<Self> {
        let content = std::fs::read_to_string(&path)
            .map_err(|e| FalcornError::config(format!("Failed to read config: {}", e)))?;

        let mut config: ServerConfig = toml::from_str(&content)
            .map_err(|e| FalcornError::config(format!("Invalid TOML: {}", e)))?;
        config.config_path = Some(path.as_ref().to_string_lossy().to_string());
        Ok(config)
    }

    /// Save configuration to TOML file
    pub fn to_toml<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let content = toml::to_string_pretty(self)
            .map_err(|e| FalcornError::config(format!("Failed to serialize: {}", e)))?;

        std::fs::write(path, content)
            .map_err(|e| FalcornError::config(format!("Failed to write config: {}", e)))
    }

    /// Get the worker count (defaults to number of CPUs)
    pub fn worker_count(&self) -> usize {
        self.workers.unwrap_or_else(num_cpus::get)
    }

    /// Validate the configuration
    pub fn validate(&self) -> Result<()> {
        // Get validation report
        ConfigValidator::validate(&self)?;

        // Print report
        // println!("{}", report.sformat());

        Ok(())
    }

    /// Get worker timeout as Duration
    pub fn worker_timeout_duration(&self) -> Duration {
        Duration::from_secs(self.worker_timeout)
    }

    /// Get keepalive timeout as Duration
    pub fn keepalive_timeout_duration(&self) -> Duration {
        Duration::from_secs(self.keepalive_timeout)
    }

    /// Bind address string
    pub fn bind_addr(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }

    /// Whether workers should emit per-request access events.
    ///
    /// Access events are required when at least one access consumer is enabled:
    /// - stdout logging
    /// - access log file
    /// - plugin IPC subscribers
    pub fn access_events_enabled(&self) -> bool {
        self.log_stdout
            || self
                .access_log
                .as_ref()
                .map(|s| !s.trim().is_empty())
                .unwrap_or(false)
            || self
                .plugin_socket
                .as_ref()
                .map(|s| !s.trim().is_empty())
                .unwrap_or(false)
    }

    /// Host
    pub fn host(&self) -> String {
        self.host.clone()
    }

    /// Port
    pub fn port(&self) -> u16 {
        self.port
    }

    /// Parse and validate log level
    pub fn log_level_enum(&self) -> LogLevel {
        match self.log_level.to_uppercase().as_str() {
            "TRACE" => LogLevel::Trace,
            "DEBUG" => LogLevel::Debug,
            "INFO" => LogLevel::Info,
            "WARN" => LogLevel::Warn,
            "ERROR" => LogLevel::Error,
            _ => LogLevel::Info,
        }
    }
}

fn default_async_worker_threads() -> usize {
    4
}

fn default_async_max_blocking_threads() -> usize {
    100
}

fn default_sync_handler_threads() -> usize {
    num_cpus::get().clamp(2, 8)
}

fn default_sync_conn_queue_capacity() -> usize {
    4096
}

fn default_asgi_max_inflight() -> usize {
    256
}

fn default_asgi_send_pool_size() -> usize {
    4096
}

fn default_asgi_scheduler() -> String {
    "task".to_string()
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self::new("app".to_string(), "application".to_string())
    }
}

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

    #[test]
    fn test_config_new() {
        let config = ServerConfig::new("myapp".to_string(), "app".to_string());
        assert_eq!(config.app_module, "myapp");
        assert_eq!(config.port, 8000);
    }

    #[test]
    fn test_worker_class_from_str() {
        assert_eq!(WorkerClass::from_str("sync").unwrap(), WorkerClass::Sync);
        assert_eq!(WorkerClass::from_str("ASYNC").unwrap(), WorkerClass::Async);
        assert!(WorkerClass::from_str("invalid").is_err());
    }

    #[test]
    fn test_config_validation() {
        let mut config = ServerConfig::new("app".to_string(), "app".to_string());
        assert!(config.validate().is_ok());

        config.port = 0;
        assert!(config.validate().is_err());
    }
}
