//! Package manager integration for apt-ostree compose
//! 
//! This module handles real APT package operations including:
//! - Package installation and removal
//! - Dependency resolution
//! - Cache management
//! - Repository configuration
//! - Post-installation script execution

use std::collections::HashSet;
use std::path::PathBuf;
use std::process::Command;
use tokio::fs;
use apt_ostree::lib::error::{AptOstreeError, AptOstreeResult};
use crate::treefile::{Repository, PackageOverride};

/// Package manager for APT operations
pub struct PackageManager {
    build_root: PathBuf,
    apt_config_dir: PathBuf,
    sources_list_path: PathBuf,
    preferences_path: PathBuf,
}

impl PackageManager {
    /// Create a new package manager instance
    pub fn new(options: &crate::ComposeOptions) -> AptOstreeResult<Self> {
        let build_root = options.workdir
            .clone()
            .unwrap_or_else(|| PathBuf::from("/tmp/apt-ostree-compose"));
        
        let apt_config_dir = build_root.join("etc/apt");
        let sources_list_path = apt_config_dir.join("sources.list");
        let preferences_path = apt_config_dir.join("preferences");
        
        Ok(Self {
            build_root,
            apt_config_dir,
            sources_list_path,
            preferences_path,
        })
    }
    
    /// Set up package sources from treefile repositories
    pub async fn setup_package_sources(&self, repositories: &[Repository]) -> AptOstreeResult<()> {
        println!("Setting up package sources...");
        
        // Create APT configuration directory
        fs::create_dir_all(&self.apt_config_dir).await
            .map_err(|e| AptOstreeError::System(format!("Failed to create APT config dir: {}", e)))?;
        
        // Create sources.list
        let mut sources_content = String::new();
        for repo in repositories {
            if repo.enabled {
                let components = repo.components.join(" ");
                sources_content.push_str(&format!(
                    "deb {} {} {}\n",
                    repo.url, repo.suite, components
                ));
                
                // Add source repositories if available
                if repo.components.contains(&"main".to_string()) {
                    sources_content.push_str(&format!(
                        "deb-src {} {} {}\n",
                        repo.url, repo.suite, components
                    ));
                }
            }
        }
        
        fs::write(&self.sources_list_path, sources_content).await
            .map_err(|e| AptOstreeError::System(format!("Failed to write sources.list: {}", e)))?;
        
        // Set up GPG keys if specified
        for repo in repositories {
            if let Some(ref gpg_key) = repo.gpg_key {
                self.setup_gpg_key(gpg_key).await?;
            }
        }
        
        println!("✅ Package sources configured");
        Ok(())
    }
    
    /// Set up GPG key for a repository
    async fn setup_gpg_key(&self, gpg_key: &str) -> AptOstreeResult<()> {
        let gpg_dir = self.build_root.join("etc/apt/trusted.gpg.d");
        fs::create_dir_all(&gpg_dir).await
            .map_err(|e| AptOstreeError::System(format!("Failed to create GPG dir: {}", e)))?;
        
        // Copy GPG key to trusted directory
        let key_path = gpg_dir.join("repository.gpg");
        fs::copy(gpg_key, &key_path).await
            .map_err(|e| AptOstreeError::System(format!("Failed to copy GPG key: {}", e)))?;
        
        Ok(())
    }
    
    /// Update package cache
    pub async fn update_cache(&self) -> AptOstreeResult<()> {
        println!("Updating package cache...");
        
        let output = Command::new("apt-get")
            .args([
                "-o", &format!("Dir::Etc::Dir={}", self.build_root.join("etc").display()),
                "-o", &format!("Dir::State::Lists={}", self.build_root.join("var/lib/apt/lists").display()),
                "-o", &format!("Dir::Cache::Archives={}", self.build_root.join("var/cache/apt/archives").display()),
                "update"
            ])
            .output()
            .map_err(|e| AptOstreeError::System(format!("Failed to run apt-get update: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(AptOstreeError::System(format!("apt-get update failed: {}", stderr)));
        }
        
        println!("✅ Package cache updated");
        Ok(())
    }
    
    /// Install a package
    pub async fn install_package(&self, package: &str) -> AptOstreeResult<()> {
        println!("Installing package: {}", package);
        
        let output = Command::new("apt-get")
            .args([
                "-y",
                "-o", &format!("Dir::Etc::Dir={}", self.build_root.join("etc").display()),
                "-o", &format!("Dir::State::Lists={}", self.build_root.join("var/lib/apt/lists").display()),
                "-o", &format!("Dir::Cache::Archives={}", self.build_root.join("var/cache/apt/archives").display()),
                "-o", &format!("Dir::State::Status={}", self.build_root.join("var/lib/dpkg/status").display()),
                "-o", &format!("Dir::State::StatusDir={}", self.build_root.join("var/lib/dpkg").display()),
                "-o", &format!("Dir::State::LogDir={}", self.build_root.join("var/log").display()),
                "-o", &format!("Dir::State::Log={}", self.build_root.join("var/log/apt/history.log").display()),
                "-o", &format!("Dir::State::ListsDir={}", self.build_root.join("var/lib/apt/lists").display()),
                "install",
                package
            ])
            .output()
            .map_err(|e| AptOstreeError::System(format!("Failed to run apt-get install: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(AptOstreeError::System(format!("apt-get install failed: {}", stderr)));
        }
        
        println!("✅ Package installed: {}", package);
        Ok(())
    }
    
    /// Resolve package dependencies
    pub async fn resolve_dependencies(&self, packages: &[String]) -> AptOstreeResult<Vec<String>> {
        println!("Resolving dependencies for packages: {:?}", packages);
        
        let mut all_packages = HashSet::new();
        
        for package in packages {
            all_packages.insert(package.clone());
            
            // Get package dependencies
            let deps = self.get_package_dependencies(package).await?;
            for dep in deps {
                all_packages.insert(dep);
            }
        }
        
        let result: Vec<String> = all_packages.into_iter().collect();
        println!("✅ Resolved {} packages", result.len());
        
        Ok(result)
    }
    
    /// Get package dependencies
    async fn get_package_dependencies(&self, package: &str) -> AptOstreeResult<Vec<String>> {
        let output = Command::new("apt-cache")
            .args([
                "-o", &format!("Dir::Etc::Dir={}", self.build_root.join("etc").display()),
                "depends",
                package
            ])
            .output()
            .map_err(|e| AptOstreeError::System(format!("Failed to run apt-cache depends: {}", e)))?;
        
        if !output.status.success() {
            return Ok(Vec::new());
        }
        
        let stdout = String::from_utf8_lossy(&output.stdout);
        let deps: Vec<String> = stdout
            .lines()
            .filter_map(|line| {
                let line = line.trim();
                if line.starts_with("Depends:") || line.starts_with("PreDepends:") {
                    line.split(':')
                        .nth(1)
                        .and_then(|s| s.split(',').next())
                        .map(|s| s.trim().to_string())
                } else {
                    None
                }
            })
            .collect();
        
        Ok(deps)
    }
    
    /// Run post-installation scripts
    pub async fn run_post_install_scripts(&self) -> AptOstreeResult<()> {
        println!("Running post-installation scripts...");
        
        // Find and run post-installation scripts
        let scripts_dir = self.build_root.join("var/lib/dpkg/info");
        if scripts_dir.exists() {
            let mut entries = fs::read_dir(&scripts_dir).await
                .map_err(|e| AptOstreeError::System(format!("Failed to read scripts dir: {}", e)))?;
            
            while let Some(entry) = entries.next_entry().await
                .map_err(|e| AptOstreeError::System(format!("Failed to read entry: {}", e)))? {
                let path = entry.path();
                if let Some(ext) = path.extension() {
                    if ext == "postinst" {
                        self.run_postinst_script(&path).await?;
                    }
                }
            }
        }
        
        println!("✅ Post-installation scripts completed");
        Ok(())
    }
    
    /// Run a postinst script
    async fn run_postinst_script(&self, script_path: &PathBuf) -> AptOstreeResult<()> {
        let script_name = script_path.file_name()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown");
        
        println!("Running postinst script: {}", script_name);
        
        let output = Command::new("chroot")
            .args([
                &self.build_root.to_string_lossy(),
                "/bin/bash",
                "-c",
                &format!("chmod +x {} && {}", script_path.display(), script_path.display())
            ])
            .output()
            .map_err(|e| AptOstreeError::System(format!("Failed to run postinst script: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            println!("Warning: postinst script {} failed: {}", script_name, stderr);
        }
        
        Ok(())
    }
    
    /// Update package database
    pub async fn update_package_database(&self) -> AptOstreeResult<()> {
        println!("Updating package database...");
        
        // Run dpkg --configure -a to configure any pending packages
        let output = Command::new("chroot")
            .args([
                &self.build_root.to_string_lossy(),
                "dpkg",
                "--configure",
                "-a"
            ])
            .output()
            .map_err(|e| AptOstreeError::System(format!("Failed to configure packages: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            println!("Warning: package configuration had issues: {}", stderr);
        }
        
        println!("✅ Package database updated");
        Ok(())
    }
    
    /// Apply package overrides
    pub async fn apply_package_overrides(&self, overrides: &[PackageOverride]) -> AptOstreeResult<()> {
        println!("Applying package overrides...");
        
        for override_pkg in overrides {
            match override_pkg.action {
                crate::treefile::OverrideAction::Replace => {
                    self.replace_package(&override_pkg.name, override_pkg.version.as_deref()).await?;
                }
                crate::treefile::OverrideAction::Remove => {
                    self.remove_package(&override_pkg.name).await?;
                }
                crate::treefile::OverrideAction::Pin => {
                    self.pin_package(&override_pkg.name, override_pkg.version.as_deref()).await?;
                }
            }
        }
        
        println!("✅ Package overrides applied");
        Ok(())
    }
    
    /// Replace a package
    async fn replace_package(&self, package: &str, version: Option<&str>) -> AptOstreeResult<()> {
        println!("Replacing package: {} with version: {:?}", package, version);
        
        // Remove existing package
        self.remove_package(package).await?;
        
        // Install new version
        let package_spec = if let Some(ver) = version {
            format!("{}={}", package, ver)
        } else {
            package.to_string()
        };
        
        self.install_package(&package_spec).await
    }
    
    /// Remove a package
    async fn remove_package(&self, package: &str) -> AptOstreeResult<()> {
        println!("Removing package: {}", package);
        
        let output = Command::new("chroot")
            .args([
                &self.build_root.to_string_lossy(),
                "apt-get",
                "remove",
                "-y",
                package
            ])
            .output()
            .map_err(|e| AptOstreeError::System(format!("Failed to remove package: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            println!("Warning: package removal had issues: {}", stderr);
        }
        
        Ok(())
    }
    
    /// Pin a package to a specific version
    async fn pin_package(&self, package: &str, version: Option<&str>) -> AptOstreeResult<()> {
        println!("Pinning package: {} to version: {:?}", package, version);
        
        if let Some(ver) = version {
            // Create preferences file entry
            let pin_entry = format!("Package: {}\nPin: version {}\nPin-Priority: 1001\n\n", package, ver);
            
            let mut preferences = fs::read_to_string(&self.preferences_path).await
                .unwrap_or_default();
            preferences.push_str(&pin_entry);
            
            fs::write(&self.preferences_path, preferences).await
                .map_err(|e| AptOstreeError::System(format!("Failed to write preferences: {}", e)))?;
        }
        
        Ok(())
    }
}
