//! Configuration module for reading and managing /etc/arc.json

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;

use crate::error::Result;

const DEFAULT_CONFIG_PATH: &str = "/etc/arc.json";

/// Configuration structure for the Arc package manager
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArcConfig {
    /// Maps Flatpak names to preferred pacman packages
    /// Key: Flatpak name (extracted from bundle ID)
    /// Value: Pacman package name to install instead
    #[serde(default)]
    pub flatpak_to_pacman: HashMap<String, String>,

    /// List of blacklisted Flatpak names that cannot be installed
    #[serde(default)]
    pub flatpak_blacklist: Vec<String>,

    /// List of whitelisted pacman packages that are allowed to be installed
    /// If empty, all pacman packages are allowed
    #[serde(default)]
    pub pacman_whitelist: Vec<String>,

    /// Maps pacman package aliases to actual package names
    /// Key: Alias name (e.g., "vim-nightly", "python3")
    /// Value: Actual pacman package name (e.g., "vim-git", "python")
    #[serde(default)]
    pub pacman_aliases: HashMap<String, String>,

    /// Maps packages to companion packages that should be installed/uninstalled together
    /// Key: Main package name
    /// Value: List of companion packages to install/uninstall with the main package
    #[serde(default)]
    pub pacman_companions: HashMap<String, Vec<String>>,

    /// Whether this config was loaded from a file (not default)
    #[serde(skip)]
    pub config_loaded: bool,
}

impl Default for ArcConfig {
    fn default() -> Self {
        Self {
            flatpak_to_pacman: HashMap::new(),
            flatpak_blacklist: Vec::new(),
            pacman_whitelist: Vec::new(),
            pacman_aliases: HashMap::new(),
            pacman_companions: HashMap::new(),
            config_loaded: false,
        }
    }
}

impl ArcConfig {
    /// Load configuration from the default path (/etc/arc.json)
    pub fn load() -> Result<Self> {
        Self::load_from(DEFAULT_CONFIG_PATH)
    }

    /// Load configuration from a specific path
    pub fn load_from<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        
        if !path.exists() {
            return Ok(Self::default());
        }

        let content = fs::read_to_string(path)?;
        let mut config: ArcConfig = serde_json::from_str(&content)?;
        config.config_loaded = true;
        Ok(config)
    }

    /// Check if a flatpak is a runtime/platform package that should be ignored
    /// These are base runtimes like org.freedesktop.Platform, org.kde.Platform, etc.
    pub fn is_flatpak_runtime(&self, bundle_id: &str) -> bool {
        // Common runtime prefixes that should be filtered out
        let runtime_patterns = [
            "org.freedesktop.Platform",
            "org.freedesktop.Sdk",
            "org.kde.Platform",
            "org.kde.Sdk",
            "org.gnome.Platform",
            "org.gnome.Sdk",
            "com.github.Ld86.RuntimesDir",
            "org.openslides.OpenSlides.Xwayland",
            "org.winehq.Wine",
            "org.gtk.Gtk3theme"
        ];

        runtime_patterns.iter().any(|pattern| bundle_id.starts_with(pattern))
    }

    /// Check if a flatpak is blacklisted
    pub fn is_flatpak_blacklisted(&self, name: &str) -> bool {
        self.flatpak_blacklist.iter().any(|b| b == name)
    }

    /// Check if a pacman package is whitelisted
    /// When config is loaded from file: strictly enforces whitelist (all packages must be whitelisted)
    /// When using default config: allows all packages
    pub fn is_pacman_whitelisted(&self, name: &str) -> bool {
        if self.config_loaded {
            // If config was loaded from file, strictly enforce whitelist
            // Empty whitelist means NO packages are allowed (strict mode)
            self.pacman_whitelist.iter().any(|w| w == name)
        } else {
            // Default config allows all packages
            true
        }
    }

    /// Get the preferred pacman package for a flatpak, if one exists
    pub fn get_pacman_alternative(&self, flatpak_name: &str) -> Option<&String> {
        self.flatpak_to_pacman.get(flatpak_name)
    }

    /// Resolve a pacman package alias to its actual package name
    /// Returns the actual package name if an alias exists, otherwise returns the input
    pub fn resolve_pacman_alias<'a>(&'a self, name: &'a str) -> &'a str {
        self.pacman_aliases.get(name).map(|s| s.as_str()).unwrap_or(name)
    }

    /// Get companion packages that should be installed/uninstalled with the given package
    /// Returns an empty slice if no companions are configured
    pub fn get_companion_packages(&self, package: &str) -> &[String] {
        self.pacman_companions
            .get(package)
            .map(|v| v.as_slice())
            .unwrap_or(&[])
    }

    /// Extract the flatpak name from a bundle ID
    /// Example: "org.mozilla.Firefox" -> "firefox"
    pub fn extract_flatpak_name(bundle_id: &str) -> String {
        bundle_id
            .split('.')
            .last()
            .unwrap_or(bundle_id)
            .to_lowercase()
    }
}

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

    #[test]
    fn test_extract_flatpak_name() {
        assert_eq!(ArcConfig::extract_flatpak_name("org.mozilla.Firefox"), "firefox");
        assert_eq!(ArcConfig::extract_flatpak_name("com.spotify.Client"), "client");
        assert_eq!(ArcConfig::extract_flatpak_name("firefox"), "firefox");
    }

    #[test]
    fn test_default_config() {
        let config = ArcConfig::default();
        assert!(config.flatpak_to_pacman.is_empty());
        assert!(config.flatpak_blacklist.is_empty());
        assert!(config.pacman_whitelist.is_empty());
    }

    #[test]
    fn test_is_pacman_whitelisted() {
        let mut config = ArcConfig::default();
        // Default config allows everything
        assert!(config.is_pacman_whitelisted("any-package"));

        // Loaded config enforces whitelist strictly
        config.config_loaded = true;
        config.pacman_whitelist = vec!["firefox".to_string(), "chromium".to_string()];
        assert!(config.is_pacman_whitelisted("firefox"));
        assert!(!config.is_pacman_whitelisted("random-package"));
        
        // Even empty whitelist in default config allows
        let default = ArcConfig::default();
        assert!(default.is_pacman_whitelisted("anything"));
    }

    #[test]
    fn test_flatpak_runtime_detection() {
        let config = ArcConfig::default();
        
        // Should detect runtime packages
        assert!(config.is_flatpak_runtime("org.freedesktop.Platform"));
        assert!(config.is_flatpak_runtime("org.freedesktop.Platform.GL.nvidia"));
        assert!(config.is_flatpak_runtime("org.kde.Platform"));
        assert!(config.is_flatpak_runtime("org.gnome.Sdk"));
        
        // Should not detect normal apps
        assert!(!config.is_flatpak_runtime("org.mozilla.firefox"));
        assert!(!config.is_flatpak_runtime("com.spotify.Client"));
        assert!(!config.is_flatpak_runtime("org.gimp.GIMP"));
    }

    #[test]
    fn test_pacman_aliases() {
        let mut config = ArcConfig::default();
        config.pacman_aliases.insert("vim-nightly".to_string(), "vim-git".to_string());
        config.pacman_aliases.insert("python3".to_string(), "python".to_string());

        assert_eq!(config.resolve_pacman_alias("vim-nightly"), "vim-git");
        assert_eq!(config.resolve_pacman_alias("python3"), "python");
        assert_eq!(config.resolve_pacman_alias("firefox"), "firefox");
    }

    #[test]
    fn test_companion_packages() {
        let mut config = ArcConfig::default();
        config.pacman_companions.insert(
            "steam".to_string(),
            vec!["steam-native-runtime".to_string(), "lib32-nvidia-utils".to_string()]
        );
        config.pacman_companions.insert(
            "wine".to_string(),
            vec!["wine-mono".to_string(), "wine-gecko".to_string()]
        );

        let steam_companions = config.get_companion_packages("steam");
        assert_eq!(steam_companions.len(), 2);
        assert!(steam_companions.contains(&"steam-native-runtime".to_string()));
        assert!(steam_companions.contains(&"lib32-nvidia-utils".to_string()));

        let wine_companions = config.get_companion_packages("wine");
        assert_eq!(wine_companions.len(), 2);

        // Package without companions returns empty slice
        let no_companions = config.get_companion_packages("firefox");
        assert_eq!(no_companions.len(), 0);
    }
}
