use platform_config::format::ConfigLoader;
use serde::{Deserialize, Serialize};

use crate::error::{infrastructure, ControlCenterError, Result};
use crate::simple_config::{
    Config, CorsConfig, DatabaseConfig, JwtConfig, LoggingConfig, RateLimitConfig, SecurityConfig,
    ServerConfig,
};

/// Root wrapper matching the NCL export key `control_center`.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ControlCenterNclConfig {
    #[serde(default)]
    pub control_center: NclSettings,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NclSettings {
    #[serde(default)]
    pub server: NclServer,
    #[serde(default)]
    pub database: NclDatabase,
    #[serde(default)]
    pub security: NclSecurity,
    #[serde(default)]
    pub logging: NclLogging,
    #[serde(default)]
    pub users: NclUsers,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NclServer {
    #[serde(default = "default_host")]
    pub host: String,
    #[serde(default = "default_port")]
    pub port: u16,
    pub workers: Option<usize>,
    pub keep_alive: Option<u64>,
    pub max_connections: Option<usize>,
}

impl Default for NclServer {
    fn default() -> Self {
        Self {
            host: default_host(),
            port: default_port(),
            workers: Some(2),
            keep_alive: Some(75),
            max_connections: Some(100),
        }
    }
}

fn default_host() -> String {
    "0.0.0.0".to_string()
}
fn default_port() -> u16 {
    9012
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NclDatabase {
    /// Internal storage backend of the SurrealDB server (e.g. "rocksdb").
    /// Does NOT determine how the Rust client connects — always WebSocket for Docker.
    #[serde(default)]
    pub backend: Option<String>,
    #[serde(default)]
    pub mode: Option<String>,
    /// RocksDB data path (server-side configuration, not used by Rust client)
    #[serde(default)]
    pub path: Option<String>,
    /// WebSocket connection string for remote SurrealDB (Docker).
    /// Falls back to "127.0.0.1:8000" if unset.
    #[serde(default)]
    pub connection_string: Option<String>,
    #[serde(default)]
    pub namespace: Option<String>,
    /// SurrealDB authentication credentials (required when server runs with --user/--pass)
    #[serde(default)]
    pub credentials: Option<NclDbCredentials>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NclDbCredentials {
    pub username: Option<String>,
    pub password: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NclSecurity {
    #[serde(default)]
    pub jwt: NclJwt,
    #[serde(default)]
    pub rate_limiting: NclRateLimiting,
    #[serde(default)]
    pub cors: NclCors,
    #[serde(default)]
    pub session: NclSession,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NclJwt {
    #[serde(default = "default_issuer")]
    pub issuer: String,
    #[serde(default = "default_audience")]
    pub audience: String,
    /// Access token lifetime in seconds
    #[serde(default = "default_expiration")]
    pub expiration: i64,
    /// Refresh token lifetime in seconds
    #[serde(default = "default_refresh_expiration")]
    pub refresh_expiration: i64,
}

impl Default for NclJwt {
    fn default() -> Self {
        Self {
            issuer: default_issuer(),
            audience: default_audience(),
            expiration: default_expiration(),
            refresh_expiration: default_refresh_expiration(),
        }
    }
}

fn default_issuer() -> String {
    "control-center".to_string()
}
fn default_audience() -> String {
    "provisioning".to_string()
}
fn default_expiration() -> i64 {
    3600
}
fn default_refresh_expiration() -> i64 {
    86400
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NclRateLimiting {
    #[serde(default)]
    pub enabled: bool,
    /// NCL schema types this field as String ("1000"), so we use Value to handle both.
    #[serde(default = "default_max_requests_json")]
    pub max_requests: serde_json::Value,
    #[serde(default = "default_window_secs")]
    pub window_seconds: u64,
}

impl Default for NclRateLimiting {
    fn default() -> Self {
        Self {
            enabled: false,
            max_requests: serde_json::Value::Number(100.into()),
            window_seconds: 60,
        }
    }
}

impl NclRateLimiting {
    fn max_requests_u32(&self) -> u32 {
        match &self.max_requests {
            serde_json::Value::Number(n) => n.as_u64().unwrap_or(100) as u32,
            serde_json::Value::String(s) => s.parse().unwrap_or(100),
            _ => 100,
        }
    }
}

fn default_max_requests_json() -> serde_json::Value {
    serde_json::Value::Number(100.into())
}
fn default_window_secs() -> u64 {
    60
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NclCors {
    #[serde(default)]
    pub enabled: bool,
    #[serde(default)]
    pub allow_credentials: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NclSession {
    /// Idle timeout in seconds — drives session_cleanup_interval_minutes
    #[serde(default = "default_idle_timeout")]
    pub idle_timeout: u64,
}

impl Default for NclSession {
    fn default() -> Self {
        Self {
            idle_timeout: default_idle_timeout(),
        }
    }
}

fn default_idle_timeout() -> u64 {
    3600
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NclUsers {
    #[serde(default)]
    pub sessions: NclUserSessions,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NclUserSessions {
    pub max_active: Option<usize>,
    #[serde(default = "default_idle_timeout")]
    pub idle_timeout: u64,
}

impl Default for NclUserSessions {
    fn default() -> Self {
        Self {
            max_active: Some(5),
            idle_timeout: 3600,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NclLogging {
    #[serde(default = "default_log_level")]
    pub level: String,
    #[serde(default = "default_log_format")]
    pub format: String,
}

impl Default for NclLogging {
    fn default() -> Self {
        Self {
            level: default_log_level(),
            format: default_log_format(),
        }
    }
}

fn default_log_level() -> String {
    "info".to_string()
}
fn default_log_format() -> String {
    "json".to_string()
}

impl ConfigLoader for ControlCenterNclConfig {
    fn service_name() -> &'static str {
        "control-center"
    }

    fn load_from_hierarchy(
    ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let service = Self::service_name();
        if let Some(path) = platform_config::resolve_config_path(service) {
            return Self::from_path(&path);
        }
        Ok(Self::default())
    }

    fn from_path<P: AsRef<std::path::Path>>(
        path: P,
    ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let path = path.as_ref();
        let json_value = platform_config::format::load_config(path).map_err(|e| {
            Box::new(e) as Box<dyn std::error::Error + Send + Sync>
        })?;
        serde_json::from_value(json_value).map_err(|e| {
            Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "Failed to deserialize control-center NCL config from {:?}: {}",
                    path, e
                ),
            )) as Box<dyn std::error::Error + Send + Sync>
        })
    }
}

impl ControlCenterNclConfig {
    /// Convert the NCL platform config into the runtime `Config` used by services.
    ///
    /// JWT RSA keys are generated fresh on each startup (NCL uses HS256 which
    /// is incompatible with the service's RS256 implementation). For solo mode,
    /// sessions do not persist across restarts, so ephemeral keys are acceptable.
    pub fn to_runtime_config(&self) -> Result<Config> {
        let cc = &self.control_center;

        let key_pair =
            crate::services::jwt::generate_rsa_key_pair().map_err(|e| {
                ControlCenterError::Infrastructure(
                    infrastructure::InfrastructureError::Configuration(format!(
                        "Failed to generate RSA key pair: {}",
                        e
                    )),
                )
            })?;

        let db_url = cc
            .database
            .connection_string
            .as_deref()
            .unwrap_or("127.0.0.1:8000")
            .to_string();

        let namespace = cc
            .database
            .namespace
            .as_deref()
            .unwrap_or("control_center")
            .to_string();

        // Credentials: NCL → env vars SURREALDB_USER / SURREALDB_PASS → None
        let db_username = cc
            .database
            .credentials
            .as_ref()
            .and_then(|c| c.username.clone())
            .or_else(|| std::env::var("SURREALDB_USER").ok());
        let db_password = cc
            .database
            .credentials
            .as_ref()
            .and_then(|c| c.password.clone())
            .or_else(|| std::env::var("SURREALDB_PASS").ok());

        Ok(Config {
            server: ServerConfig {
                host: cc.server.host.clone(),
                port: cc.server.port,
                workers: cc.server.workers,
                keep_alive: cc.server.keep_alive,
                max_connections: cc.server.max_connections,
            },
            database: DatabaseConfig {
                url: db_url,
                namespace,
                database: "main".to_string(),
                username: db_username,
                password: db_password,
            },
            jwt: JwtConfig {
                issuer: cc.security.jwt.issuer.clone(),
                audience: cc.security.jwt.audience.clone(),
                access_token_expiration_hours: (cc.security.jwt.expiration / 3600).max(1),
                refresh_token_expiration_hours: (cc.security.jwt.refresh_expiration / 3600).max(1),
                private_key_pem: key_pair.private_key_pem,
                public_key_pem: key_pair.public_key_pem,
            },
            rate_limiting: RateLimitConfig {
                max_requests: cc.security.rate_limiting.max_requests_u32(),
                window_seconds: cc.security.rate_limiting.window_seconds,
                per_ip: true,
                global: false,
            },
            cors: CorsConfig {
                allowed_origins: vec!["*".to_string()],
                allowed_methods: vec![
                    "GET".to_string(),
                    "POST".to_string(),
                    "PUT".to_string(),
                    "DELETE".to_string(),
                    "PATCH".to_string(),
                    "OPTIONS".to_string(),
                ],
                allowed_headers: vec![
                    "content-type".to_string(),
                    "authorization".to_string(),
                    "accept".to_string(),
                    "x-requested-with".to_string(),
                    "x-session-id".to_string(),
                ],
                expose_headers: vec![
                    "x-total-count".to_string(),
                    "x-rate-limit-remaining".to_string(),
                    "x-rate-limit-limit".to_string(),
                    "x-rate-limit-reset".to_string(),
                ],
                max_age: 86400,
                allow_credentials: cc.security.cors.allow_credentials,
            },
            security: SecurityConfig {
                session_cleanup_interval_minutes: (cc.security.session.idle_timeout / 60).max(1),
                max_sessions_per_user: cc.users.sessions.max_active,
                password_min_length: 8,
                password_require_special_chars: false,
                password_require_numbers: false,
                password_require_uppercase: false,
                failed_login_lockout_attempts: Some(5),
                failed_login_lockout_duration_minutes: Some(15),
            },
            logging: LoggingConfig {
                level: cc.logging.level.clone(),
                format: cc.logging.format.clone(),
                file_path: None,
                max_file_size: Some("100MB".to_string()),
                max_files: Some(10),
            },
        })
    }
}
