//! Critical APT-OSTree Integration Nuances
//! 
//! This module implements the key differences between traditional APT and APT-OSTree:
//! 1. Package Database Location: Use /usr/share/apt instead of /var/lib/apt
//! 2. "From Scratch" Philosophy: Regenerate filesystem for every change
//! 3. Package Caching Strategy: Convert DEB packages to OSTree commits
//! 4. Script Execution Environment: Run DEB scripts in controlled sandboxed environment
//! 5. Filesystem Assembly Process: Proper layering and hardlink optimization
//! 6. Repository Integration: Customize APT behavior for OSTree compatibility

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use tracing::info;
use serde::{Serialize, Deserialize};

use crate::error::{AptOstreeError, AptOstreeResult};
use crate::apt::AptManager;
use crate::ostree::OstreeManager;

/// OSTree-specific APT configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OstreeAptConfig {
    /// APT database location (read-only in OSTree deployments)
    pub apt_db_path: PathBuf,
    /// Package cache location (OSTree repository)
    pub package_cache_path: PathBuf,
    /// Script execution environment
    pub script_env_path: PathBuf,
    /// Temporary working directory for package operations
    pub temp_work_path: PathBuf,
    /// OSTree repository path
    pub ostree_repo_path: PathBuf,
    /// Current deployment path
    pub deployment_path: PathBuf,
}

impl Default for OstreeAptConfig {
    fn default() -> Self {
        Self {
            apt_db_path: PathBuf::from("/usr/share/apt"),
            package_cache_path: PathBuf::from("/var/lib/apt-ostree/cache"),
            script_env_path: PathBuf::from("/var/lib/apt-ostree/scripts"),
            temp_work_path: PathBuf::from("/var/lib/apt-ostree/temp"),
            ostree_repo_path: PathBuf::from("/var/lib/apt-ostree/repo"),
            deployment_path: PathBuf::from("/var/lib/apt-ostree/deployments"),
        }
    }
}

/// Package to OSTree conversion manager
pub struct PackageOstreeConverter {
    config: OstreeAptConfig,
}

impl PackageOstreeConverter {
    /// Create a new package to OSTree converter
    pub fn new(config: OstreeAptConfig) -> Self {
        Self { config }
    }

    /// Convert a DEB package to an OSTree commit
    pub async fn deb_to_ostree_commit(&self, deb_path: &Path, ostree_manager: &OstreeManager) -> AptOstreeResult<String> {
        info!("Converting DEB package to OSTree commit: {}", deb_path.display());

        // Extract package metadata
        let metadata = self.extract_deb_metadata(deb_path).await?;
        
        // Create temporary extraction directory
        let temp_dir = self.config.temp_work_path.join(&metadata.name);
        if temp_dir.exists() {
            fs::remove_dir_all(&temp_dir)?;
        }
        fs::create_dir_all(&temp_dir)?;

        // Extract DEB package contents
        self.extract_deb_contents(deb_path, &temp_dir).await?;

        // Create OSTree commit from extracted contents
        let commit_id = self.create_ostree_commit_from_files(&metadata, &temp_dir, ostree_manager).await?;

        // Clean up temporary directory
        fs::remove_dir_all(&temp_dir)?;

        info!("Successfully converted DEB to OSTree commit: {}", commit_id);
        Ok(commit_id)
    }

    /// Extract metadata from DEB package
    pub async fn extract_deb_metadata(&self, deb_path: &Path) -> AptOstreeResult<DebPackageMetadata> {
        info!("Extracting metadata from: {:?}", deb_path);
        
        // Use dpkg-deb to extract control information
        let output = tokio::process::Command::new("dpkg-deb")
            .arg("-I")
            .arg(deb_path)
            .arg("control")
            .output()
            .await
            .map_err(|e| AptOstreeError::DebParsing(format!("Failed to run dpkg-deb: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(AptOstreeError::DebParsing(format!("dpkg-deb failed: {}", stderr)));
        }
        
        let control_content = String::from_utf8(output.stdout)
            .map_err(|e| AptOstreeError::Utf8(e))?;
        
        info!("Extracted control file for package");
        self.parse_control_file(&control_content)
    }
    
    fn parse_control_file(&self, control_content: &str) -> AptOstreeResult<DebPackageMetadata> {
        let mut metadata = DebPackageMetadata {
            name: String::new(),
            version: String::new(),
            architecture: String::new(),
            description: String::new(),
            depends: vec![],
            conflicts: vec![],
            provides: vec![],
            scripts: HashMap::new(),
        };
        
        // Parse control file line by line
        let mut current_field = String::new();
        let mut current_value = String::new();
        
        for line in control_content.lines() {
            if line.is_empty() {
                // End of current field
                if !current_field.is_empty() {
                    self.set_metadata_field(&mut metadata, &current_field, &current_value);
                    current_field.clear();
                    current_value.clear();
                }
            } else if line.starts_with(' ') || line.starts_with('\t') {
                // Continuation line
                current_value.push_str(line.trim_start());
            } else if line.contains(':') {
                // New field
                if !current_field.is_empty() {
                    self.set_metadata_field(&mut metadata, &current_field, &current_value);
                }
                
                let parts: Vec<&str> = line.splitn(2, ':').collect();
                if parts.len() == 2 {
                    current_field = parts[0].trim().to_lowercase();
                    current_value = parts[1].trim().to_string();
                }
            }
        }
        
        // Handle the last field
        if !current_field.is_empty() {
            self.set_metadata_field(&mut metadata, &current_field, &current_value);
        }
        
        // Validate required fields
        if metadata.name.is_empty() {
            return Err(AptOstreeError::DebParsing("Package name is required".to_string()));
        }
        if metadata.version.is_empty() {
            return Err(AptOstreeError::DebParsing("Package version is required".to_string()));
        }
        
        info!("Parsed metadata for package: {} {}", metadata.name, metadata.version);
        Ok(metadata)
    }
    
    fn set_metadata_field(&self, metadata: &mut DebPackageMetadata, field: &str, value: &str) {
        match field {
            "package" => metadata.name = value.to_string(),
            "version" => metadata.version = value.to_string(),
            "architecture" => metadata.architecture = value.to_string(),
            "description" => metadata.description = value.to_string(),
            "depends" => metadata.depends = self.parse_dependency_list(value),
            "conflicts" => metadata.conflicts = self.parse_dependency_list(value),
            "provides" => metadata.provides = self.parse_dependency_list(value),
            _ => {
                // Handle script fields
                if field.starts_with("preinst") || field.starts_with("postinst") || 
                   field.starts_with("prerm") || field.starts_with("postrm") {
                    metadata.scripts.insert(field.to_string(), value.to_string());
                }
            }
        }
    }
    
    fn parse_dependency_list(&self, deps_str: &str) -> Vec<String> {
        deps_str.split(',')
            .map(|s| s.trim())
            .filter(|s| !s.is_empty())
            .map(|s| {
                // Handle version constraints (e.g., "package (>= 1.0)")
                if let Some(pkg) = s.split_whitespace().next() {
                    pkg.to_string()
                } else {
                    s.to_string()
                }
            })
            .collect()
    }

    /// Extract DEB package contents
    async fn extract_deb_contents(&self, deb_path: &Path, extract_dir: &Path) -> AptOstreeResult<()> {
        info!("Extracting DEB contents from {:?} to {:?}", deb_path, extract_dir);
        
        // Create extraction directory
        tokio::fs::create_dir_all(extract_dir)
            .await
            .map_err(|e| AptOstreeError::Io(e))?;
        
        // Use dpkg-deb to extract data.tar.gz
        let output = tokio::process::Command::new("dpkg-deb")
            .arg("-R")  // Raw extraction
            .arg(deb_path)
            .arg(extract_dir)
            .output()
            .await
            .map_err(|e| AptOstreeError::DebParsing(format!("Failed to extract DEB: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(AptOstreeError::DebParsing(format!("dpkg-deb extraction failed: {}", stderr)));
        }
        
        info!("Successfully extracted DEB contents to {:?}", extract_dir);
        Ok(())
    }
    
    async fn extract_deb_scripts(&self, deb_path: &Path, extract_dir: &Path) -> AptOstreeResult<()> {
        info!("Extracting DEB scripts from {:?} to {:?}", deb_path, extract_dir);
        
        // Create scripts directory
        let scripts_dir = extract_dir.join("DEBIAN");
        tokio::fs::create_dir_all(&scripts_dir)
            .await
            .map_err(|e| AptOstreeError::Io(e))?;
        
        // Extract control.tar.gz to get scripts
        let output = tokio::process::Command::new("dpkg-deb")
            .arg("-e")  // Extract control
            .arg(deb_path)
            .arg(&scripts_dir)
            .output()
            .await
            .map_err(|e| AptOstreeError::DebParsing(format!("Failed to extract scripts: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(AptOstreeError::DebParsing(format!("dpkg-deb script extraction failed: {}", stderr)));
        }
        
        info!("Successfully extracted DEB scripts to {:?}", scripts_dir);
        Ok(())
    }

    /// Create OSTree commit from extracted files
    async fn create_ostree_commit_from_files(
        &self,
        package_metadata: &DebPackageMetadata,
        files_dir: &Path,
        ostree_manager: &OstreeManager,
    ) -> AptOstreeResult<String> {
        info!("Creating OSTree commit for package: {}", package_metadata.name);
        
        // Create a temporary staging directory for OSTree commit
        let staging_dir = tempfile::tempdir()
            .map_err(|e| AptOstreeError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
        let staging_path = staging_dir.path();
        
        // Create the atomic filesystem layout in staging
        self.create_atomic_filesystem_layout(staging_path).await?;
        
        // Copy package files to appropriate locations
        self.copy_package_files_to_layout(files_dir, staging_path).await?;
        
        // Create package metadata for OSTree
        let commit_metadata = serde_json::json!({
            "package": {
                "name": package_metadata.name,
                "version": package_metadata.version,
                "architecture": package_metadata.architecture,
                "description": package_metadata.description,
                "depends": package_metadata.depends,
                "conflicts": package_metadata.conflicts,
                "provides": package_metadata.provides,
                "scripts": package_metadata.scripts,
                "installed_at": chrono::Utc::now().to_rfc3339(),
            },
            "apt_ostree": {
                "version": env!("CARGO_PKG_VERSION"),
                "commit_type": "package_layer",
                "atomic_filesystem": true,
            }
        });
        
        // Create OSTree commit
        let commit_id = ostree_manager.create_commit(
            staging_path,
            &format!("Package: {} {}", package_metadata.name, package_metadata.version),
            Some(&format!("Install package {} version {}", package_metadata.name, package_metadata.version)),
            &commit_metadata,
        ).await?;
        
        info!("Created OSTree commit: {} for package: {}", commit_id, package_metadata.name);
        Ok(commit_id)
    }
    
    async fn create_atomic_filesystem_layout(&self, staging_path: &Path) -> AptOstreeResult<()> {
        info!("Creating atomic filesystem layout in {:?}", staging_path);
        
        // Create the standard atomic filesystem structure
        let dirs = [
            "usr",
            "usr/bin", "usr/sbin", "usr/lib", "usr/lib64", "usr/share", "usr/include",
            "etc", "var", "var/lib", "var/cache", "var/log", "var/spool",
            "opt", "srv", "mnt", "tmp",
        ];
        
        for dir in &dirs {
            let dir_path = staging_path.join(dir);
            tokio::fs::create_dir_all(&dir_path)
                .await
                .map_err(|e| AptOstreeError::Io(e))?;
        }
        
        // Create symlinks for atomic filesystem layout
        let symlinks = [
            ("home", "var/home"),
            ("root", "var/roothome"),
            ("usr/local", "var/usrlocal"),
            ("mnt", "var/mnt"),
        ];
        
        for (link, target) in &symlinks {
            let link_path = staging_path.join(link);
            let target_path = staging_path.join(target);
            
            // Create target directory if it doesn't exist
            if let Some(parent) = target_path.parent() {
                tokio::fs::create_dir_all(parent)
                    .await
                    .map_err(|e| AptOstreeError::Io(e))?;
            }
            
            // Create symlink (this will be handled by OSTree during deployment)
            // For now, we'll create the target directory structure
            tokio::fs::create_dir_all(&target_path)
                .await
                .map_err(|e| AptOstreeError::Io(e))?;
        }
        
        info!("Created atomic filesystem layout");
        Ok(())
    }
    
    async fn copy_package_files_to_layout(&self, files_dir: &Path, staging_path: &Path) -> AptOstreeResult<()> {
        info!("Copying package files to atomic layout");
        
        // Walk through extracted files and copy them to appropriate locations
        let mut entries = tokio::fs::read_dir(files_dir)
            .await
            .map_err(|e| AptOstreeError::Io(e))?;
        
        while let Some(entry) = entries.next_entry()
            .await
            .map_err(|e| AptOstreeError::Io(e))? {
            
            let entry_path = entry.path();
            let file_name = entry_path.file_name()
                .ok_or_else(|| AptOstreeError::DebParsing("Invalid file path".to_string()))?
                .to_string_lossy();
            
            // Skip DEBIAN directory (handled separately)
            if file_name == "DEBIAN" {
                continue;
            }
            
            // Determine target path in atomic layout
            let target_path = staging_path.join(&*file_name);
            
            if entry.file_type()
                .await
                .map_err(|e| AptOstreeError::Io(e))?
                .is_dir() {
                // Copy directory recursively
                self.copy_directory_recursive(&entry_path, &target_path)?;
            } else {
                // Copy file
                if let Some(parent) = target_path.parent() {
                    tokio::fs::create_dir_all(parent)
                        .await
                        .map_err(|e| AptOstreeError::Io(e))?;
                }
                tokio::fs::copy(&entry_path, &target_path)
                    .await
                    .map_err(|e| AptOstreeError::Io(e))?;
            }
        }
        
        info!("Copied package files to atomic layout");
        Ok(())
    }
    
    fn copy_directory_recursive(&self, src: &Path, dst: &Path) -> AptOstreeResult<()> {
        std::fs::create_dir_all(dst)
            .map_err(|e| AptOstreeError::Io(e))?;
        
        for entry in std::fs::read_dir(src)
            .map_err(|e| AptOstreeError::Io(e))? {
            
            let entry = entry.map_err(|e| AptOstreeError::Io(e))?;
            let entry_path = entry.path();
            let file_name = entry_path.file_name()
                .ok_or_else(|| AptOstreeError::DebParsing("Invalid file path".to_string()))?
                .to_string_lossy();
            
            let target_path = dst.join(&*file_name);
            
            if entry.file_type()
                .map_err(|e| AptOstreeError::Io(e))?
                .is_dir() {
                self.copy_directory_recursive(&entry_path, &target_path)?;
            } else {
                std::fs::copy(&entry_path, &target_path)
                    .map_err(|e| AptOstreeError::Io(e))?;
            }
        }
        
        Ok(())
    }
}

/// DEB package metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebPackageMetadata {
    pub name: String,
    pub version: String,
    pub architecture: String,
    pub description: String,
    pub depends: Vec<String>,
    pub conflicts: Vec<String>,
    pub provides: Vec<String>,
    pub scripts: HashMap<String, String>,
}

/// OSTree-compatible APT manager
pub struct OstreeAptManager {
    config: OstreeAptConfig,
    package_converter: PackageOstreeConverter,
}

impl OstreeAptManager {
    /// Create a new OSTree-compatible APT manager
    pub fn new(
        config: OstreeAptConfig, 
        apt_manager: &AptManager, 
        ostree_manager: &OstreeManager
    ) -> Self {
        let package_converter = PackageOstreeConverter::new(config.clone());
        
        Self {
            config,
            package_converter,
        }
    }

    /// Configure APT for OSTree compatibility
    pub async fn configure_for_ostree(&self) -> AptOstreeResult<()> {
        info!("Configuring APT for OSTree compatibility");

        // Create OSTree-specific APT configuration
        self.create_ostree_apt_config().await?;

        // Set up package cache directory
        self.setup_package_cache().await?;

        // Configure script execution environment
        self.setup_script_environment().await?;

        info!("APT configured for OSTree compatibility");
        Ok(())
    }

    /// Create OSTree-specific APT configuration
    async fn create_ostree_apt_config(&self) -> AptOstreeResult<()> {
        let apt_conf_dir = self.config.apt_db_path.join("apt.conf.d");
        fs::create_dir_all(&apt_conf_dir)?;

        let ostree_conf = format!(
            r#"// OSTree-specific APT configuration
Dir::State "/usr/share/apt";
Dir::Cache "/var/lib/apt-ostree/cache";
Dir::Etc "/usr/share/apt";
Dir::Etc::SourceParts "/usr/share/apt/sources.list.d";
Dir::Etc::SourceList "/usr/share/apt/sources.list";

// Disable features incompatible with OSTree
APT::Get::AllowUnauthenticated "false";
APT::Get::AllowDowngrade "false";
APT::Get::AllowRemove-Essential "false";
APT::Get::AutomaticRemove "false";
APT::Get::AutomaticRemove-Kernels "false";

// OSTree-specific settings
APT::Get::Assume-Yes "false";
APT::Get::Show-Upgraded "true";
APT::Get::Show-Versions "true";
"#
        );

        let conf_path = apt_conf_dir.join("99ostree");
        fs::write(&conf_path, ostree_conf)?;

        info!("Created OSTree APT configuration: {}", conf_path.display());
        Ok(())
    }

    /// Set up package cache directory
    async fn setup_package_cache(&self) -> AptOstreeResult<()> {
        fs::create_dir_all(&self.config.package_cache_path)?;
        
        // Create subdirectories
        let subdirs = ["archives", "lists", "partial"];
        for subdir in &subdirs {
            fs::create_dir_all(self.config.package_cache_path.join(subdir))?;
        }

        info!("Set up package cache directory: {}", self.config.package_cache_path.display());
        Ok(())
    }

    /// Set up script execution environment
    async fn setup_script_environment(&self) -> AptOstreeResult<()> {
        fs::create_dir_all(&self.config.script_env_path)?;

        // Create script execution directories
        let script_dirs = ["preinst", "postinst", "prerm", "postrm"];
        for dir in &script_dirs {
            fs::create_dir_all(self.config.script_env_path.join(dir))?;
        }

        info!("Set up script execution environment: {}", self.config.script_env_path.display());
        Ok(())
    }

    /// Install packages using "from scratch" philosophy
    pub async fn install_packages_ostree(&self, packages: &[String], ostree_manager: &OstreeManager) -> AptOstreeResult<()> {
        info!("Installing packages using OSTree 'from scratch' approach");

        // Download packages to cache
        let deb_paths = self.download_packages(packages).await?;

        // Convert each package to OSTree commit
        let mut commit_ids = Vec::new();
        for deb_path in deb_paths {
            let commit_id = self.package_converter.deb_to_ostree_commit(&deb_path, ostree_manager).await?;
            commit_ids.push(commit_id);
        }

        // TODO: Implement filesystem assembly from OSTree commits
        // This would involve:
        // 1. Creating a new deployment branch
        // 2. Assembling filesystem from base + package commits
        // 3. Running scripts in sandboxed environment
        // 4. Creating final OSTree commit

        info!("Successfully converted {} packages to OSTree commits", commit_ids.len());
        Ok(())
    }

    /// Download packages to cache
    async fn download_packages(&self, packages: &[String]) -> AptOstreeResult<Vec<PathBuf>> {
        info!("Downloading packages: {:?}", packages);

        let mut deb_paths = Vec::new();
        let archives_dir = self.config.package_cache_path.join("archives");

        for package_name in packages {
            // Use apt-get to download package
            let output = Command::new("apt-get")
                .args(&["download", package_name])
                .current_dir(&archives_dir)
                .output()
                .map_err(|e| AptOstreeError::PackageOperation(format!("Failed to download {}: {}", package_name, e)))?;

            if !output.status.success() {
                return Err(AptOstreeError::PackageOperation(
                    format!("Failed to download package: {}", package_name)
                ));
            }

            // Find the downloaded .deb file
            for entry in fs::read_dir(&archives_dir)? {
                let entry = entry?;
                let path = entry.path();
                if path.extension().and_then(|s| s.to_str()) == Some("deb") {
                    if path.file_name().and_then(|s| s.to_str()).unwrap_or("").contains(package_name) {
                        deb_paths.push(path);
                        break;
                    }
                }
            }
        }

        info!("Downloaded {} packages", deb_paths.len());
        Ok(deb_paths)
    }

    /// Execute DEB scripts in sandboxed environment
    pub async fn execute_deb_script(&self, script_path: &Path, script_type: &str) -> AptOstreeResult<()> {
        info!("Executing DEB script: {} ({})", script_path.display(), script_type);

        // Create sandboxed execution environment
        let sandbox_dir = self.config.script_env_path.join(script_type).join(
            format!("script_{}", chrono::Utc::now().timestamp())
        );
        fs::create_dir_all(&sandbox_dir)?;

        // Copy script to sandbox
        let sandbox_script = sandbox_dir.join("script");
        fs::copy(script_path, &sandbox_script)?;
        fs::set_permissions(&sandbox_script, fs::Permissions::from_mode(0o755))?;

        // TODO: Implement proper sandboxing with bubblewrap
        // For now, execute directly (unsafe)
        let output = Command::new(&sandbox_script)
            .current_dir(&sandbox_dir)
            .env("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
            .env("DEBIAN_FRONTEND", "noninteractive")
            .output()
            .map_err(|e| AptOstreeError::ScriptExecution(format!("Script execution failed: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(AptOstreeError::ScriptExecution(
                format!("Script failed with exit code {}: {}", output.status, stderr)
            ));
        }

        // Clean up sandbox
        fs::remove_dir_all(&sandbox_dir)?;

        info!("Successfully executed DEB script: {}", script_type);
        Ok(())
    }
} 