use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
use tracing::{info, warn};

/// Runtime config sent from CNC to agent
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
    pub heartbeat_interval_secs: u64,
    pub work_dir: String,
    pub rustflags: String,
    pub max_restarts: u32,
    pub cores: Option<u32>,
}

impl Default for AgentConfig {
    fn default() -> Self {
        Self {
            heartbeat_interval_secs: 5,
            work_dir: default_work_dir(),
            rustflags: "-C target-cpu=native".to_string(),
            max_restarts: 5,
            cores: None,
        }
    }
}

fn default_work_dir() -> String {
    #[cfg(windows)]
    {
        dirs::data_local_dir()
            .map(|p| p.join("eridani-hpc").join("programs"))
            .unwrap_or_else(|| PathBuf::from("C:\\ProgramData\\eridani-hpc\\programs"))
            .to_string_lossy()
            .to_string()
    }
    #[cfg(not(windows))]
    {
        "/var/lib/eridani-hpc/programs".to_string()
    }
}

/// Agent-specific settings in a profile
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AgentSettings {
    /// Working directory for program builds and binaries
    pub work_dir: Option<String>,

    /// RUSTFLAGS to use when building programs
    pub rustflags: Option<String>,

    /// Maximum number of automatic restarts for crashed programs
    pub max_restarts: Option<u32>,

    /// Number of cores to use (None = all available)
    pub cores: Option<u32>,
}

/// A single profile configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Profile {
    /// CNC server URL
    pub cnc_url: Option<String>,

    /// CLI authentication token
    pub cli_token: Option<String>,

    /// Agent authentication token
    pub agent_token: Option<String>,

    /// Agent ID override
    pub agent_id: Option<String>,

    /// Agent-specific settings
    #[serde(default)]
    pub agent: AgentSettings,
}

/// Config file with multiple profiles
/// Location:
/// - Linux/macOS: ~/.config/erhpc/config.toml
/// - Windows: %APPDATA%\erhpc\config.toml
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
    /// Named profiles
    #[serde(default)]
    pub profiles: HashMap<String, Profile>,
}

impl Default for Config {
    fn default() -> Self {
        let mut profiles = HashMap::new();
        profiles.insert(
            "default".to_string(),
            Profile {
                cnc_url: Some("http://localhost:8080".to_string()),
                cli_token: None,
                agent_token: None,
                agent_id: None,
                agent: AgentSettings::default(),
            },
        );
        Self { profiles }
    }
}

impl Config {
    /// Load config from the standard location, creating default if it doesn't exist
    pub fn load() -> Self {
        let path = match Self::config_path() {
            Some(p) => p,
            None => {
                warn!("Could not determine config directory, using defaults");
                return Self::default();
            }
        };

        if path.exists() {
            match std::fs::read_to_string(&path) {
                Ok(content) => match toml::from_str(&content) {
                    Ok(config) => config,
                    Err(e) => {
                        warn!(error = %e, "Failed to parse config file, using defaults");
                        Self::default()
                    }
                },
                Err(e) => {
                    warn!(error = %e, "Failed to read config file, using defaults");
                    Self::default()
                }
            }
        } else {
            info!(path = %path.display(), "Config file does not exist, creating default");
            let config = Self::default();
            let _ = config.save();
            config
        }
    }

    /// Save config to the standard location
    pub fn save(&self) -> Result<(), std::io::Error> {
        let path = Self::config_path().ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::NotFound,
                "Could not determine config directory",
            )
        })?;

        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent)?;
        }

        let content = toml::to_string_pretty(self)
            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;

        std::fs::write(&path, content)
    }

    /// Get the config file path
    /// - Linux/macOS: ~/.erhpc/config.toml
    /// - Windows: %USERPROFILE%\.erhpc\config.toml
    pub fn config_path() -> Option<PathBuf> {
        dirs::home_dir().map(|p| p.join(".erhpc").join("config.toml"))
    }

    /// Get a profile by name, falls back to "default"
    pub fn profile(&self, name: Option<&str>) -> Profile {
        let requested = name.unwrap_or("default");

        if let Some(profile) = self.profiles.get(requested) {
            return profile.clone();
        }

        if requested != "default" {
            warn!(requested = requested, "Profile not found, trying 'default'");
            if let Some(profile) = self.profiles.get("default") {
                return profile.clone();
            }
        }

        warn!("No profiles found in config, using built-in defaults");
        Profile::default()
    }
}

impl Profile {
    /// Get CNC URL or default
    pub fn cnc_url(&self) -> String {
        self.cnc_url
            .clone()
            .unwrap_or_else(|| "http://localhost:8080".to_string())
    }

    /// Get CLI token
    pub fn cli_token(&self) -> Option<String> {
        self.cli_token.clone()
    }

    /// Get agent token
    pub fn agent_token(&self) -> Option<String> {
        self.agent_token.clone()
    }

    /// Get agent ID or hostname
    pub fn agent_id(&self) -> String {
        self.agent_id.clone().unwrap_or_else(|| {
            hostname::get()
                .map(|h| h.to_string_lossy().to_string())
                .unwrap_or_else(|_| "unknown".to_string())
        })
    }

    /// Get agent work directory or default
    pub fn work_dir(&self) -> String {
        self.agent.work_dir.clone().unwrap_or_else(default_work_dir)
    }

    /// Get agent rustflags or default
    pub fn rustflags(&self) -> String {
        self.agent
            .rustflags
            .clone()
            .unwrap_or_else(|| "-C target-cpu=native".to_string())
    }

    /// Get max restarts or default
    pub fn max_restarts(&self) -> u32 {
        self.agent.max_restarts.unwrap_or(5)
    }

    /// Get cores setting
    pub fn cores(&self) -> Option<u32> {
        self.agent.cores
    }
}
