use photon_config::load_toml_config;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::path::{Path, PathBuf};

/// Deployer server configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// HTTP server bind address
    #[serde(default = "default_bind_addr")]
    pub bind_addr: SocketAddr,

    /// ChainFire cluster endpoints
    #[serde(default)]
    pub chainfire: ChainFireConfig,

    /// UltraCloud cluster ID (for writing desired state under ultracloud/clusters/...)
    #[serde(default)]
    pub cluster_id: Option<String>,

    /// Namespace prefix for UltraCloud cluster state
    #[serde(default = "default_cluster_namespace")]
    pub cluster_namespace: String,

    /// Node heartbeat timeout (seconds)
    #[serde(default = "default_heartbeat_timeout")]
    pub heartbeat_timeout_secs: u64,

    /// Local state path for bootstrapper mode (file or directory)
    #[serde(default = "default_local_state_path")]
    pub local_state_path: Option<PathBuf>,

    /// Optional tar.gz bundle containing the UltraCloud flake source tree for bootstrap installs
    #[serde(default)]
    pub bootstrap_flake_bundle_path: Option<PathBuf>,

    /// Shared bootstrap token required for phone-home/admin APIs
    #[serde(default)]
    pub bootstrap_token: Option<String>,

    /// Shared admin token required for admin APIs
    #[serde(default)]
    pub admin_token: Option<String>,

    /// Allow admin APIs to fall back to bootstrap token (unsafe; for dev only)
    #[serde(default = "default_allow_admin_fallback")]
    pub allow_admin_fallback: bool,

    /// Allow unauthenticated requests (unsafe; for dev only)
    #[serde(default = "default_allow_unauthenticated")]
    pub allow_unauthenticated: bool,

    /// Require ChainFire to be available at startup (fail fast if unavailable)
    #[serde(default = "default_require_chainfire")]
    pub require_chainfire: bool,

    /// Allow nodes with unknown machine-id to auto-register (unsafe)
    #[serde(default = "default_allow_unknown_nodes")]
    pub allow_unknown_nodes: bool,

    /// Enable hardcoded test machine mappings (unsafe)
    #[serde(default = "default_allow_test_mappings")]
    pub allow_test_mappings: bool,

    /// Optional CA certificate path for issuing node TLS certs
    #[serde(default)]
    pub tls_ca_cert_path: Option<String>,

    /// Optional CA private key path for issuing node TLS certs
    #[serde(default)]
    pub tls_ca_key_path: Option<String>,

    /// Allow self-signed TLS certificates when no CA is configured
    #[serde(default = "default_tls_self_signed")]
    pub tls_self_signed: bool,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            bind_addr: default_bind_addr(),
            chainfire: ChainFireConfig::default(),
            cluster_id: None,
            cluster_namespace: default_cluster_namespace(),
            heartbeat_timeout_secs: default_heartbeat_timeout(),
            local_state_path: default_local_state_path(),
            bootstrap_flake_bundle_path: None,
            bootstrap_token: None,
            admin_token: None,
            allow_admin_fallback: default_allow_admin_fallback(),
            allow_unauthenticated: default_allow_unauthenticated(),
            require_chainfire: default_require_chainfire(),
            allow_unknown_nodes: default_allow_unknown_nodes(),
            allow_test_mappings: default_allow_test_mappings(),
            tls_ca_cert_path: None,
            tls_ca_key_path: None,
            tls_self_signed: default_tls_self_signed(),
        }
    }
}

impl Config {
    pub fn redacted(&self) -> Self {
        let mut redacted = self.clone();
        if redacted.bootstrap_token.is_some() {
            redacted.bootstrap_token = Some("<redacted>".to_string());
        }
        if redacted.admin_token.is_some() {
            redacted.admin_token = Some("<redacted>".to_string());
        }
        redacted
    }

    pub fn apply_secret_env_overrides(&mut self) {
        if let Ok(token) = std::env::var("DEPLOYER_BOOTSTRAP_TOKEN") {
            let trimmed = token.trim();
            if !trimmed.is_empty() {
                self.bootstrap_token = Some(trimmed.to_string());
            }
        }

        if let Ok(token) = std::env::var("DEPLOYER_ADMIN_TOKEN") {
            let trimmed = token.trim();
            if !trimmed.is_empty() {
                self.admin_token = Some(trimmed.to_string());
            }
        }
    }
}

/// ChainFire configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainFireConfig {
    /// ChainFire cluster endpoints
    #[serde(default = "default_chainfire_endpoints")]
    pub endpoints: Vec<String>,

    /// Namespace for deployer state
    #[serde(default = "default_chainfire_namespace")]
    pub namespace: String,
}

impl Default for ChainFireConfig {
    fn default() -> Self {
        Self {
            endpoints: default_chainfire_endpoints(),
            namespace: default_chainfire_namespace(),
        }
    }
}

pub fn load_config(path: &Path) -> anyhow::Result<Config> {
    let mut config: Config = load_toml_config(path)?;
    config.apply_secret_env_overrides();
    Ok(config)
}

fn default_bind_addr() -> SocketAddr {
    "0.0.0.0:8080".parse().unwrap()
}

fn default_chainfire_endpoints() -> Vec<String> {
    vec![]
}

fn default_chainfire_namespace() -> String {
    "deployer".to_string()
}

fn default_cluster_namespace() -> String {
    "ultracloud".to_string()
}

fn default_heartbeat_timeout() -> u64 {
    300 // 5 minutes
}

fn default_local_state_path() -> Option<PathBuf> {
    Some(PathBuf::from("/var/lib/deployer/state"))
}

fn default_allow_unauthenticated() -> bool {
    false
}

fn default_require_chainfire() -> bool {
    true
}

fn default_allow_admin_fallback() -> bool {
    false
}

fn default_allow_unknown_nodes() -> bool {
    false
}

fn default_allow_test_mappings() -> bool {
    false
}

fn default_tls_self_signed() -> bool {
    false
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::time::{SystemTime, UNIX_EPOCH};

    fn temp_path(name: &str) -> PathBuf {
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        std::env::temp_dir().join(format!("{}-{}-{}.toml", name, std::process::id(), nanos))
    }

    #[test]
    fn test_default_config() {
        let config = Config::default();
        assert_eq!(config.bind_addr.to_string(), "0.0.0.0:8080");
        assert_eq!(config.chainfire.namespace, "deployer");
        assert_eq!(config.cluster_namespace, "ultracloud");
        assert!(config.cluster_id.is_none());
        assert_eq!(config.heartbeat_timeout_secs, 300);
        assert_eq!(
            config.local_state_path,
            Some(PathBuf::from("/var/lib/deployer/state"))
        );
        assert!(config.bootstrap_flake_bundle_path.is_none());
        assert!(config.bootstrap_token.is_none());
        assert!(config.admin_token.is_none());
        assert!(!config.allow_admin_fallback);
        assert!(!config.allow_unauthenticated);
        assert!(!config.allow_unknown_nodes);
        assert!(!config.allow_test_mappings);
        assert!(config.tls_ca_cert_path.is_none());
        assert!(config.tls_ca_key_path.is_none());
        assert!(!config.tls_self_signed);
        assert!(config.chainfire.endpoints.is_empty());
    }

    #[test]
    fn test_config_serialization() {
        let config = Config::default();
        let json = serde_json::to_string(&config).unwrap();
        let deserialized: Config = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.bind_addr, config.bind_addr);
    }

    #[test]
    fn test_loads_toml_config() {
        let path = temp_path("deployer-config");
        fs::write(
            &path,
            r#"
                bind_addr = "127.0.0.1:18080"
                cluster_id = "cluster-a"
                allow_unauthenticated = true
                bootstrap_flake_bundle_path = "/tmp/ultracloud-flake-bundle.tar.gz"

                [chainfire]
                endpoints = ["http://10.0.0.1:2379"]
                namespace = "bootstrap"
            "#,
        )
        .unwrap();

        let config = load_config(&path).unwrap();
        assert_eq!(config.bind_addr.to_string(), "127.0.0.1:18080");
        assert_eq!(config.cluster_id.as_deref(), Some("cluster-a"));
        assert_eq!(
            config.bootstrap_flake_bundle_path,
            Some(PathBuf::from("/tmp/ultracloud-flake-bundle.tar.gz"))
        );
        assert!(config.allow_unauthenticated);
        assert_eq!(config.chainfire.namespace, "bootstrap");
        assert_eq!(config.chainfire.endpoints, vec!["http://10.0.0.1:2379"]);

        let _ = fs::remove_file(path);
    }
}
