//! High-level package manager interface with intelligent package matching

use crate::config::ArcConfig;
use crate::error::{ArcError, Result};
use crate::{flatpak, pacman, Package};

/// Main package manager that coordinates between pacman and flatpak
pub struct PackageManager {
    config: ArcConfig,
}

impl PackageManager {
    /// Create a new PackageManager with default config from /etc/arc.json
    pub fn new() -> Result<Self> {
        let config = ArcConfig::load()?;
        Ok(Self { config })
    }

    /// Create a new PackageManager with a custom config
    pub fn with_config(config: ArcConfig) -> Self {
        Self { config }
    }

    /// Search for packages across both pacman and flatpak
    /// Returns packages after applying filtering rules
    pub fn search(&self, query: &str) -> Result<Vec<Package>> {
        let mut results = Vec::new();

        // Search pacman packages
        let pacman_packages = pacman::search(query)?;
        for pkg in pacman_packages {
            let resolved = self.config.resolve_pacman_alias(&pkg);
            if self.config.is_pacman_whitelisted(resolved) {
                results.push(Package::Pacman(resolved.to_string()));
            }
        }

        // Search flatpak packages
        let flatpak_packages = flatpak::search(query)?;
        for pkg in flatpak_packages {
            // Skip runtime/platform packages
            if self.config.is_flatpak_runtime(&pkg.bundle_id) {
                continue;
            }

            // Check if blacklisted
            if self.config.is_flatpak_blacklisted(&pkg.name) {
                continue;
            }

            // Check if there's a pacman alternative
            if let Some(pacman_alt) = self.config.get_pacman_alternative(&pkg.name) {
                // Only add pacman alternative if it's whitelisted and not already in results
                if self.config.is_pacman_whitelisted(pacman_alt) {
                    let pacman_pkg = Package::Pacman(pacman_alt.clone());
                    if !results.contains(&pacman_pkg) {
                        results.push(pacman_pkg);
                    }
                } else {
                    // Pacman alternative exists but is not whitelisted, add the flatpak instead
                    results.push(Package::Flatpak(pkg.bundle_id));
                }
            } else {
                // No pacman alternative, add the flatpak
                results.push(Package::Flatpak(pkg.bundle_id));
            }
        }

        Ok(results)
    }

    /// Install a package with intelligent routing
    /// For flatpaks, checks if there's a preferred pacman alternative
    /// Also installs any configured companion packages
    pub fn install(&self, package: &Package) -> Result<()> {
        match package {
            Package::Pacman(name) => {
                // Resolve alias first
                let resolved = self.config.resolve_pacman_alias(name);
                
                // Check if whitelisted
                if !self.config.is_pacman_whitelisted(resolved) {
                    return Err(ArcError::PackageNotWhitelisted(resolved.to_string()));
                }
                
                // Install the main package
                pacman::install(resolved)?;
                
                // Install companion packages (without whitelist restrictions - they're automatic dependencies)
                for companion in self.config.get_companion_packages(resolved) {
                    let companion_resolved = self.config.resolve_pacman_alias(companion);
                    pacman::install(companion_resolved)?;
                }
            }
            Package::Flatpak(bundle_id) => {
                let name = ArcConfig::extract_flatpak_name(bundle_id);

                // Reject if it's a runtime package
                if self.config.is_flatpak_runtime(bundle_id) {
                    return Err(ArcError::PackageBlacklisted(format!(
                        "{} (runtime package)",
                        bundle_id
                    )));
                }

                // Check if blacklisted
                if self.config.is_flatpak_blacklisted(&name) {
                    return Err(ArcError::PackageBlacklisted(bundle_id.clone()));
                }

                // Check if there's a pacman alternative
                if let Some(pacman_alt) = self.config.get_pacman_alternative(&name) {
                    let resolved = self.config.resolve_pacman_alias(pacman_alt);
                    if self.config.is_pacman_whitelisted(resolved) {
                        // Install pacman package instead
                        pacman::install(resolved)?;
                        
                        // Install companion packages (without whitelist restrictions - they're automatic dependencies)
                        for companion in self.config.get_companion_packages(resolved) {
                            let companion_resolved = self.config.resolve_pacman_alias(companion);
                            pacman::install(companion_resolved)?;
                        }
                        
                        return Ok(());
                    }
                }

                // Install the flatpak
                flatpak::install(bundle_id)?;
            }
        }

        Ok(())
    }

    /// Uninstall a package
    /// Also uninstalls any configured companion packages
    pub fn uninstall(&self, package: &Package) -> Result<()> {
        match package {
            Package::Pacman(name) => {
                let resolved = self.config.resolve_pacman_alias(name);
                
                // Check if whitelisted (enforce same restrictions as install)
                if !self.config.is_pacman_whitelisted(resolved) {
                    return Err(ArcError::PackageNotWhitelisted(resolved.to_string()));
                }
                
                // Uninstall companion packages first
                for companion in self.config.get_companion_packages(resolved) {
                    let companion_resolved = self.config.resolve_pacman_alias(companion);
                    // Ignore errors when uninstalling companions (they might not be installed)
                    let _ = pacman::uninstall(companion_resolved);
                }
                
                // Uninstall the main package
                pacman::uninstall(resolved)?;
            }
            Package::Flatpak(bundle_id) => {
                let name = ArcConfig::extract_flatpak_name(bundle_id);

                // Check if blacklisted
                if self.config.is_flatpak_blacklisted(&name) {
                    return Err(ArcError::PackageBlacklisted(bundle_id.clone()));
                }

                flatpak::uninstall(bundle_id)?;
            }
        }

        Ok(())
    }

    /// Check if a package is installed
    pub fn is_installed(&self, package: &Package) -> Result<bool> {
        match package {
            Package::Pacman(name) => {
                let resolved = self.config.resolve_pacman_alias(name);
                pacman::is_installed(resolved)
            }
            Package::Flatpak(bundle_id) => flatpak::is_installed(bundle_id),
        }
    }

    /// List all installed packages (both pacman and flatpak)
    pub fn list_installed(&self) -> Result<Vec<Package>> {
        let mut packages = Vec::new();

        // List pacman packages
        let pacman_packages = pacman::list_installed()?;
        for pkg in pacman_packages {
            packages.push(Package::Pacman(pkg));
        }

        // List flatpak packages
        let flatpak_packages = flatpak::list_installed()?;
        for pkg in flatpak_packages {
            packages.push(Package::Flatpak(pkg.bundle_id));
        }

        Ok(packages)
    }

    /// Get the configuration
    pub fn config(&self) -> &ArcConfig {
        &self.config
    }
}

impl Default for PackageManager {
    fn default() -> Self {
        Self::new().unwrap_or_else(|_| Self::with_config(ArcConfig::default()))
    }
}

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

    #[test]
    fn test_package_manager_creation() {
        let config = ArcConfig::default();
        let pm = PackageManager::with_config(config);
        assert!(pm.config().flatpak_to_pacman.is_empty());
    }

    #[test]
    fn test_install_with_pacman_alternative() {
        let mut config = ArcConfig::default();
        config.flatpak_to_pacman.insert("firefox".to_string(), "firefox".to_string());
        config.pacman_whitelist = vec!["firefox".to_string()];

        let pm = PackageManager::with_config(config);
        
        // This would normally install the flatpak, but should redirect to pacman
        let _flatpak_pkg = Package::Flatpak("org.mozilla.Firefox".to_string());
        
        // We can't actually test the install without root, but we can verify the logic
        assert!(pm.config().get_pacman_alternative("firefox").is_some());
    }

    #[test]
    fn test_pacman_alias_resolution() {
        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());
        config.pacman_whitelist = vec!["vim-git".to_string(), "python".to_string()];

        let pm = PackageManager::with_config(config);
        
        // Verify that aliases are resolved correctly
        assert_eq!(pm.config().resolve_pacman_alias("vim-nightly"), "vim-git");
        assert_eq!(pm.config().resolve_pacman_alias("python3"), "python");
        
        // Non-aliased packages should return themselves
        assert_eq!(pm.config().resolve_pacman_alias("firefox"), "firefox");
    }

    #[test]
    fn test_companion_packages() {
        let mut config = ArcConfig::default();
        
        // Configure steam with companions
        config.pacman_companions.insert(
            "steam".to_string(),
            vec!["steam-native-runtime".to_string(), "lib32-nvidia-utils".to_string()]
        );
        
        // Whitelist all packages
        config.pacman_whitelist = vec![
            "steam".to_string(),
            "steam-native-runtime".to_string(),
            "lib32-nvidia-utils".to_string(),
        ];

        let pm = PackageManager::with_config(config);
        
        // Verify companion packages are configured
        let companions = pm.config().get_companion_packages("steam");
        assert_eq!(companions.len(), 2);
        assert!(companions.contains(&"steam-native-runtime".to_string()));
        assert!(companions.contains(&"lib32-nvidia-utils".to_string()));
        
        // Package without companions
        let no_companions = pm.config().get_companion_packages("firefox");
        assert_eq!(no_companions.len(), 0);
    }

    #[test]
    fn test_install_with_whitelist_enforcement() {
        let mut config = ArcConfig::default();
        config.config_loaded = true; // Enable whitelist enforcement
        config.pacman_whitelist = vec!["firefox".to_string()];

        let pm = PackageManager::with_config(config);
        
        // Whitelisted package should validate correctly
        let firefox = Package::Pacman("firefox".to_string());
        assert!(matches!(pm.install(&firefox), Err(ArcError::CommandFailed(_))));
        // (Would fail with CommandFailed because pacman command is not available in test)
        
        // Non-whitelisted package should be rejected
        let chromium = Package::Pacman("chromium".to_string());
        assert!(matches!(pm.install(&chromium), Err(ArcError::PackageNotWhitelisted(_))));
    }

    #[test]
    fn test_install_with_blacklist_enforcement() {
        let config = ArcConfig::default();
        let pm = PackageManager::with_config(config);

        // Blacklisted flatpak should fail
        let mut config = ArcConfig::default();
        config.flatpak_blacklist = vec!["malware".to_string()];
        let pm = PackageManager::with_config(config);
        
        let blacklisted = Package::Flatpak("org.example.malware".to_string());
        assert!(matches!(pm.install(&blacklisted), Err(ArcError::PackageBlacklisted(_))));
    }

    #[test]
    fn test_install_with_aliases_and_companions() {
        let mut config = ArcConfig::default();
        config.pacman_aliases.insert("vim-nightly".to_string(), "vim-git".to_string());
        config.pacman_companions.insert(
            "vim-git".to_string(),
            vec!["vim-plugins".to_string()]
        );
        config.pacman_whitelist = vec![
            "vim-git".to_string(),
            "vim-plugins".to_string(),
        ];

        let pm = PackageManager::with_config(config);
        
        // When installing aliased package, should use resolved name
        let aliased = Package::Pacman("vim-nightly".to_string());
        assert!(matches!(pm.install(&aliased), Err(ArcError::CommandFailed(_))));
        // (Would fail with CommandFailed because pacman command is not available in test)
        // But importantly, it doesn't fail with PackageNotWhitelisted
    }

    #[test]
    fn test_search_respects_whitelist() {
        let mut config = ArcConfig::default();
        config.config_loaded = true; // Enable strict whitelist
        config.pacman_whitelist = vec!["firefox".to_string()];

        let pm = PackageManager::with_config(config);
        
        // When whitelist is enforced, only whitelisted packages appear in search
        // (We can't test actual search without external commands, but verify the logic)
        let _pkg = Package::Pacman("firefox".to_string());
        assert!(pm.config().is_pacman_whitelisted("firefox"));
        
        let _pkg_not_whitelisted = Package::Pacman("chromium".to_string());
        assert!(!pm.config().is_pacman_whitelisted("chromium"));
    }
}
