use std::path::PathBuf;

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

/// Top-level config (~/.local/share/irohds/config.toml)
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct GlobalConfig {
    #[serde(default)]
    pub default_namespace: String,
    /// Fallback bootstrap peer node IDs (hex-encoded iroh public keys).
    /// Used when mainline DHT is blocked (universities, corporate networks).
    #[serde(default)]
    pub bootstrap_peers: Vec<String>,
}

/// Load global config, creating default if absent.
pub fn load_global_config() -> Result<GlobalConfig> {
    let path = data_dir().join("config.toml");
    if path.exists() {
        let text = std::fs::read_to_string(&path).context("read global config")?;
        Ok(toml::from_str(&text).context("parse global config")?)
    } else {
        let cfg = GlobalConfig::default();
        if let Ok(s) = toml::to_string_pretty(&cfg) {
            let _ = std::fs::write(&path, s);
        }
        Ok(cfg)
    }
}

/// Per-namespace config
#[derive(Debug, Serialize, Deserialize)]
pub struct NamespaceConfig {
    /// Max bytes to cache locally. 0 = unlimited.
    #[serde(default)]
    pub max_cache_bytes: u64,
    /// Eviction check interval in seconds.
    #[serde(default = "default_eviction_interval")]
    pub eviction_interval_secs: u64,
}

fn default_eviction_interval() -> u64 { 300 }

impl Default for NamespaceConfig {
    fn default() -> Self {
        Self {
            max_cache_bytes: 0,
            eviction_interval_secs: default_eviction_interval(),
        }
    }
}

/// Platform-aware data directory. Override with IROHDS_DATA_DIR.
pub fn data_dir() -> PathBuf {
    if let Ok(d) = std::env::var("IROHDS_DATA_DIR") {
        return PathBuf::from(d);
    }
    dirs::data_local_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join("irohds")
}

/// Runtime directory for sockets.
pub fn runtime_dir() -> PathBuf {
    dirs::runtime_dir()
        .or_else(|| {
            // macOS and Windows don't have XDG_RUNTIME_DIR
            if cfg!(target_os = "macos") {
                Some(PathBuf::from("/tmp"))
            } else if cfg!(windows) {
                dirs::cache_dir()
            } else {
                Some(PathBuf::from("/tmp"))
            }
        })
        .unwrap_or_else(|| PathBuf::from("/tmp"))
}

/// Path to namespace data.
pub fn namespace_dir(ns: &str) -> PathBuf {
    data_dir().join("namespaces").join(ns)
}

/// Load or create namespace config.
pub fn load_ns_config(ns: &str) -> Result<NamespaceConfig> {
    let dir = namespace_dir(ns);
    let path = dir.join("config.toml");
    if path.exists() {
        let text = std::fs::read_to_string(&path).context("read ns config")?;
        Ok(toml::from_str(&text).context("parse ns config")?)
    } else {
        std::fs::create_dir_all(&dir)?;
        let cfg = NamespaceConfig::default();
        std::fs::write(&path, toml::to_string_pretty(&cfg)?)?;
        Ok(cfg)
    }
}

/// Socket name for interprocess local socket. Override with IROHDS_SOCKET.
pub fn socket_name() -> String {
    if let Ok(s) = std::env::var("IROHDS_SOCKET") {
        return s;
    }
    if cfg!(windows) {
        "irohds".to_string()
    } else {
        let dir = runtime_dir();
        dir.join("irohds.sock").to_string_lossy().to_string()
    }
}

/// Ensure data directory exists with restrictive permissions.
pub fn ensure_dirs() -> Result<()> {
    let d = data_dir();
    std::fs::create_dir_all(&d)?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        std::fs::set_permissions(&d, std::fs::Permissions::from_mode(0o700))?;
    }
    std::fs::create_dir_all(d.join("namespaces"))?;
    std::fs::create_dir_all(runtime_dir())?;
    Ok(())
}

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

    #[test]
    fn namespace_config_defaults() {
        let cfg = NamespaceConfig::default();
        assert_eq!(cfg.max_cache_bytes, 0);
        assert_eq!(cfg.eviction_interval_secs, 300);
    }

    #[test]
    fn namespace_config_roundtrip() {
        let cfg = NamespaceConfig {
            max_cache_bytes: 1024 * 1024,
            eviction_interval_secs: 60,
        };
        let toml_str = toml::to_string_pretty(&cfg).unwrap();
        let parsed: NamespaceConfig = toml::from_str(&toml_str).unwrap();
        assert_eq!(parsed.max_cache_bytes, 1024 * 1024);
        assert_eq!(parsed.eviction_interval_secs, 60);
    }

    #[test]
    fn load_ns_config_creates_default() {
        let dir = tempfile::tempdir().unwrap();
        // Override data_dir by creating the ns dir manually
        let ns_dir = dir.path().join("test-ns");
        std::fs::create_dir_all(&ns_dir).unwrap();
        let config_path = ns_dir.join("config.toml");

        // Write a default config
        let cfg = NamespaceConfig::default();
        std::fs::write(&config_path, toml::to_string_pretty(&cfg).unwrap()).unwrap();

        // Read it back
        let text = std::fs::read_to_string(&config_path).unwrap();
        let loaded: NamespaceConfig = toml::from_str(&text).unwrap();
        assert_eq!(loaded.max_cache_bytes, 0);
    }

    #[test]
    fn socket_name_is_nonempty() {
        let name = socket_name();
        assert!(!name.is_empty());
    }
}

