//! Server configuration
//!
//! Configuration for the IAM server including storage backend, authentication, and server settings.

use std::net::SocketAddr;
use std::path::Path;

use serde::{Deserialize, Serialize};

/// Main server configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ServerConfig {
    /// Server settings
    pub server: ServerSettings,

    /// Cluster coordination configuration
    #[serde(default)]
    pub cluster: ClusterConfig,

    /// Storage backend configuration
    pub store: StoreConfig,

    /// Authentication configuration
    #[serde(default)]
    pub authn: AuthnConfig,

    /// Logging configuration
    #[serde(default)]
    pub logging: LoggingConfig,

    /// Admin API policy configuration
    #[serde(default)]
    pub admin: AdminConfig,

    /// Development-only safety valves
    #[serde(default)]
    pub dev: DevConfig,
}

impl ServerConfig {
    /// Load configuration from a TOML file
    pub fn from_file(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
        let content = std::fs::read_to_string(path).map_err(|e| ConfigError::Io(e.to_string()))?;
        toml::from_str(&content).map_err(|e| ConfigError::Parse(e.to_string()))
    }

    /// Load configuration from environment variables
    pub fn from_env() -> Result<Self, ConfigError> {
        // Try to find config file from environment
        if let Ok(path) = std::env::var("IAM_CONFIG") {
            return Self::from_file(path);
        }

        // Default paths to try
        let default_paths = ["iam.toml", "/etc/iam/iam.toml", "config/iam.toml"];

        for path in &default_paths {
            if Path::new(path).exists() {
                return Self::from_file(path);
            }
        }

        // Use default configuration and apply environment overrides
        let mut config = Self::default();

        if let Ok(endpoint) =
            std::env::var("FLAREDB_ENDPOINT").or_else(|_| std::env::var("IAM_FLAREDB_ENDPOINT"))
        {
            let trimmed = strip_scheme(endpoint.trim());
            if !trimmed.is_empty() {
                config.store.backend = BackendKind::FlareDb;
                config.store.flaredb_endpoint = Some(trimmed.to_string());
            }
        }

        if let Ok(namespace) =
            std::env::var("FLAREDB_NAMESPACE").or_else(|_| std::env::var("IAM_FLAREDB_NAMESPACE"))
        {
            let trimmed = namespace.trim();
            if !trimmed.is_empty() {
                config.store.flaredb_namespace = Some(trimmed.to_string());
            }
        }

        if let Ok(backend) = std::env::var("IAM_STORE_BACKEND") {
            let trimmed = backend.trim();
            if !trimmed.is_empty() {
                config.store.backend = parse_backend_kind(trimmed)?;
            }
        }

        if let Ok(database_url) = std::env::var("IAM_DATABASE_URL") {
            let trimmed = database_url.trim();
            if !trimmed.is_empty() {
                config.store.database_url = Some(trimmed.to_string());
            }
        }

        if let Ok(single_node) = std::env::var("IAM_SINGLE_NODE") {
            let value = single_node.trim().to_ascii_lowercase();
            config.store.single_node = matches!(value.as_str(), "1" | "true" | "yes" | "on");
        }

        if let Ok(endpoint) =
            std::env::var("CHAINFIRE_ENDPOINT").or_else(|_| std::env::var("IAM_CHAINFIRE_ENDPOINT"))
        {
            let trimmed = endpoint.trim();
            if !trimmed.is_empty() {
                config.cluster.chainfire_endpoint = Some(trimmed.to_string());
            }
        }

        if let Ok(value) = std::env::var("IAM_ALLOW_UNAUTHENTICATED_ADMIN")
            .or_else(|_| std::env::var("PHOTON_IAM_ALLOW_UNAUTHENTICATED_ADMIN"))
        {
            let value = value.trim().to_ascii_lowercase();
            config.admin.allow_unauthenticated =
                matches!(value.as_str(), "1" | "true" | "yes" | "on");
        }

        if let Ok(value) = std::env::var("IAM_ALLOW_RANDOM_SIGNING_KEY")
            .or_else(|_| std::env::var("PHOTON_IAM_ALLOW_RANDOM_SIGNING_KEY"))
        {
            let value = value.trim().to_ascii_lowercase();
            config.dev.allow_random_signing_key =
                matches!(value.as_str(), "1" | "true" | "yes" | "on");
        }

        if let Ok(value) = std::env::var("IAM_ALLOW_MEMORY_BACKEND")
            .or_else(|_| std::env::var("PHOTON_IAM_ALLOW_MEMORY_BACKEND"))
        {
            let value = value.trim().to_ascii_lowercase();
            config.dev.allow_memory_backend = matches!(value.as_str(), "1" | "true" | "yes" | "on");
        }

        Ok(config)
    }

    /// Create a minimal configuration for testing
    #[cfg(test)]
    pub fn for_testing() -> Self {
        Self {
            server: ServerSettings {
                addr: "127.0.0.1:50051".parse().unwrap(),
                http_addr: "127.0.0.1:8083".parse().unwrap(),
                tls: None,
            },
            cluster: ClusterConfig::default(),
            store: StoreConfig {
                backend: BackendKind::Memory,
                flaredb_endpoint: None,
                flaredb_namespace: None,
                database_url: None,
                single_node: false,
            },
            authn: AuthnConfig {
                jwt: None,
                internal_token: InternalTokenConfig {
                    signing_key: "test-signing-key-for-development-only".into(),
                    issuer: "iam-test".into(),
                    default_ttl_seconds: 3600,
                    max_ttl_seconds: 86400,
                },
            },
            logging: LoggingConfig::default(),
            admin: AdminConfig::default(),
            dev: DevConfig::default(),
        }
    }
}

/// Server settings
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerSettings {
    /// Listen address
    #[serde(default = "default_addr")]
    pub addr: SocketAddr,

    /// HTTP REST API listen address
    #[serde(default = "default_http_addr")]
    pub http_addr: SocketAddr,

    /// TLS configuration
    pub tls: Option<TlsConfig>,
}

impl Default for ServerSettings {
    fn default() -> Self {
        Self {
            addr: default_addr(),
            http_addr: default_http_addr(),
            tls: None,
        }
    }
}

fn default_addr() -> SocketAddr {
    "0.0.0.0:50051".parse().unwrap()
}

fn default_http_addr() -> SocketAddr {
    "127.0.0.1:8083".parse().unwrap()
}

/// TLS configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
    /// Path to certificate file (PEM)
    pub cert_file: String,

    /// Path to private key file (PEM)
    pub key_file: String,

    /// Path to CA certificate file for client verification (optional)
    pub ca_file: Option<String>,

    /// Require client certificates
    #[serde(default)]
    pub require_client_cert: bool,
}

/// Storage backend configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreConfig {
    /// Backend type
    #[serde(default)]
    pub backend: BackendKind,

    /// FlareDB endpoint (for flaredb backend)
    pub flaredb_endpoint: Option<String>,

    /// FlareDB namespace (for flaredb backend)
    pub flaredb_namespace: Option<String>,

    /// SQL database URL (required for postgres/sqlite backend)
    pub database_url: Option<String>,

    /// Allow single-node mode (required for SQLite)
    #[serde(default)]
    pub single_node: bool,
}

impl Default for StoreConfig {
    fn default() -> Self {
        Self {
            backend: BackendKind::FlareDb,
            flaredb_endpoint: Some("127.0.0.1:2479".into()),
            flaredb_namespace: Some("iam".into()),
            database_url: None,
            single_node: false,
        }
    }
}

/// Cluster coordination configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ClusterConfig {
    /// ChainFire endpoint used for cluster coordination
    pub chainfire_endpoint: Option<String>,
}

/// Admin API policy configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AdminConfig {
    /// Allow admin APIs to run without an explicit admin token (dev only)
    #[serde(default)]
    pub allow_unauthenticated: bool,
}

/// Development-only safety valves
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DevConfig {
    /// Allow generating a random signing key when none is configured
    #[serde(default)]
    pub allow_random_signing_key: bool,

    /// Allow the in-memory backend
    #[serde(default)]
    pub allow_memory_backend: bool,
}

/// Backend type
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BackendKind {
    /// In-memory backend (for testing)
    Memory,
    /// FlareDB distributed database
    #[default]
    FlareDb,
    /// PostgreSQL database
    Postgres,
    /// SQLite database (single-node only)
    Sqlite,
}

/// Authentication configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AuthnConfig {
    /// JWT/OIDC configuration
    pub jwt: Option<JwtConfig>,

    /// Internal token configuration
    pub internal_token: InternalTokenConfig,
}

/// JWT/OIDC configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtConfig {
    /// JWKS endpoint URL
    pub jwks_url: String,

    /// Expected issuer
    pub issuer: String,

    /// Expected audience
    pub audience: String,

    /// JWKS cache TTL in seconds
    #[serde(default = "default_jwks_cache_ttl")]
    pub jwks_cache_ttl_seconds: u64,
}

fn default_jwks_cache_ttl() -> u64 {
    3600
}

/// Internal token configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InternalTokenConfig {
    /// Signing key (base64 encoded or raw string)
    pub signing_key: String,

    /// Token issuer identifier
    #[serde(default = "default_issuer")]
    pub issuer: String,

    /// Default token TTL in seconds
    #[serde(default = "default_token_ttl")]
    pub default_ttl_seconds: u64,

    /// Maximum token TTL in seconds
    #[serde(default = "default_max_token_ttl")]
    pub max_ttl_seconds: u64,
}

impl Default for InternalTokenConfig {
    fn default() -> Self {
        Self {
            signing_key: String::new(),
            issuer: default_issuer(),
            default_ttl_seconds: default_token_ttl(),
            max_ttl_seconds: default_max_token_ttl(),
        }
    }
}

fn default_issuer() -> String {
    "iam".into()
}

fn default_token_ttl() -> u64 {
    3600 // 1 hour
}

fn default_max_token_ttl() -> u64 {
    86400 * 7 // 7 days
}

/// Logging configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
    /// Log level
    #[serde(default = "default_log_level")]
    pub level: String,

    /// Log format (json or text)
    #[serde(default = "default_log_format")]
    pub format: String,
}

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

fn default_log_level() -> String {
    "info".into()
}

fn default_log_format() -> String {
    "text".into()
}

fn strip_scheme(endpoint: &str) -> &str {
    endpoint
        .strip_prefix("http://")
        .or_else(|| endpoint.strip_prefix("https://"))
        .unwrap_or(endpoint)
}

fn parse_backend_kind(value: &str) -> Result<BackendKind, ConfigError> {
    match value.trim().to_ascii_lowercase().as_str() {
        "memory" => Ok(BackendKind::Memory),
        "flaredb" => Ok(BackendKind::FlareDb),
        "postgres" => Ok(BackendKind::Postgres),
        "sqlite" => Ok(BackendKind::Sqlite),
        other => Err(ConfigError::Parse(format!(
            "invalid IAM_STORE_BACKEND '{}', expected one of: flaredb, postgres, sqlite, memory",
            other
        ))),
    }
}

/// Configuration errors
#[derive(Debug)]
pub enum ConfigError {
    /// IO error
    Io(String),
    /// Parse error
    Parse(String),
}

impl std::fmt::Display for ConfigError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConfigError::Io(e) => write!(f, "IO error: {}", e),
            ConfigError::Parse(e) => write!(f, "Parse error: {}", e),
        }
    }
}

impl std::error::Error for ConfigError {}

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

    #[test]
    fn test_default_config() {
        let config = ServerConfig::default();
        assert_eq!(config.server.addr, "0.0.0.0:50051".parse().unwrap());
        assert!(matches!(config.store.backend, BackendKind::FlareDb));
    }

    #[test]
    fn test_for_testing() {
        let config = ServerConfig::for_testing();
        assert!(!config.authn.internal_token.signing_key.is_empty());
    }

    #[test]
    fn test_toml_parse() {
        let toml = r#"
[server]
addr = "127.0.0.1:8080"

[store]
backend = "memory"

[authn.internal_token]
signing_key = "secret"
issuer = "test"
default_ttl_seconds = 1800

[logging]
level = "debug"
"#;
        let config: ServerConfig = toml::from_str(toml).unwrap();
        assert_eq!(config.server.addr, "127.0.0.1:8080".parse().unwrap());
        assert_eq!(config.authn.internal_token.default_ttl_seconds, 1800);
        assert_eq!(config.logging.level, "debug");
    }
}
