//! Tree composer for apt-ostree compose
//! 
//! This module orchestrates the entire tree composition process:
//! - Coordinates package management, OSTree operations, and container generation
//! - Manages the build workflow and error handling
//! - Provides high-level composition interface

use std::path::PathBuf;
use tokio::fs;
use apt_ostree::lib::error::{AptOstreeError, AptOstreeResult};
use crate::treefile::{Treefile, PackageConfig, Customizations};
use crate::package_manager::PackageManager;
use crate::ostree_integration::OstreeIntegration;
use crate::container::ContainerGenerator;

/// Main tree composer that orchestrates the composition process
pub struct TreeComposer {
    workdir: PathBuf,
    package_manager: PackageManager,
    ostree_integration: OstreeIntegration,
    container_generator: ContainerGenerator,
}

impl TreeComposer {
    /// Create a new tree composer instance
    pub fn new(options: &crate::ComposeOptions) -> AptOstreeResult<Self> {
        let workdir = options.workdir
            .clone()
            .unwrap_or_else(|| PathBuf::from("/tmp/apt-ostree-compose"));
        
        let package_manager = PackageManager::new(options)?;
        let ostree_integration = OstreeIntegration::new(options.repo.as_deref(), &workdir)?;
        let container_generator = ContainerGenerator::new(&workdir, &workdir.join("ostree-repo"));
        
        Ok(Self {
            workdir,
            package_manager,
            ostree_integration,
            container_generator,
        })
    }
    
    /// Compose a complete tree from a treefile
    pub async fn compose_tree(&self, treefile: &Treefile) -> AptOstreeResult<String> {
        println!("Starting tree composition for: {}", treefile.metadata.ref_name);
        
        // Step 1: Set up build environment
        self.setup_build_environment(treefile).await?;
        
        // Step 2: Configure package sources
        self.package_manager.setup_package_sources(&treefile.repositories).await?;
        
        // Step 3: Update package cache
        self.package_manager.update_cache().await?;
        
        // Step 4: Install base packages
        if let Some(packages) = &treefile.packages.base {
            self.install_packages(packages, "base").await?;
        }
        
        // Step 5: Install additional packages
        if let Some(packages) = &treefile.packages.additional {
            self.install_packages(packages, "additional").await?;
        }
        
        // Step 6: Apply customizations
        if let Some(customizations) = &treefile.customizations {
            self.apply_customizations(customizations).await?;
        }
        
        // Step 7: Run post-installation scripts
        self.package_manager.run_post_install_scripts().await?;
        
        // Step 8: Update package database
        self.package_manager.update_package_database().await?;
        
        // Step 9: Initialize OSTree repository
        self.ostree_integration.init_repository().await?;
        
        // Step 10: Create OSTree commit
        let parent_ref = self.get_parent_reference(treefile).await?;
        let commit_hash = self.ostree_integration.create_commit(&treefile.metadata, parent_ref.as_deref()).await?;
        
        // Step 11: Update reference
        self.ostree_integration.update_reference(&treefile.metadata.ref_name, &commit_hash).await?;
        
        // Step 12: Create repository summary
        self.ostree_integration.create_summary().await?;
        
        // Step 13: Generate container image if requested
        if let Some(output_config) = &treefile.output {
            if output_config.generate_container {
                self.container_generator.generate_image(&treefile.metadata.ref_name, output_config).await?;
            }
        }
        
        // Step 14: Clean up build artifacts
        self.cleanup_build_artifacts().await?;
        
        println!("✅ Tree composition completed successfully");
        println!("Commit hash: {}", commit_hash);
        println!("Reference: {}", treefile.metadata.ref_name);
        
        Ok(commit_hash)
    }
    
    /// Set up the build environment
    async fn setup_build_environment(&self, treefile: &Treefile) -> AptOstreeResult<()> {
        println!("Setting up build environment...");
        
        // Create build directory
        let build_dir = self.workdir.join("build");
        fs::create_dir_all(&build_dir).await
            .map_err(|e| AptOstreeError::System(format!("Failed to create build dir: {}", e)))?;
        
        // Create necessary subdirectories
        let dirs = [
            "etc/apt",
            "var/lib/apt/lists",
            "var/cache/apt/archives",
            "var/lib/dpkg",
            "var/log/apt",
            "tmp",
            "dev",
            "proc",
            "sys"
        ];
        
        for dir in &dirs {
            fs::create_dir_all(build_dir.join(dir)).await
                .map_err(|e| AptOstreeError::System(format!("Failed to create dir {}: {}", dir, e)))?;
        }
        
        // Copy base system files if specified
        if let Some(base_image) = &treefile.base_image {
            self.copy_base_image(base_image, &build_dir).await?;
        }
        
        println!("✅ Build environment set up");
        Ok(())
    }
    
    /// Copy base image files to build directory
    async fn copy_base_image(&self, base_image: &str, build_dir: &PathBuf) -> AptOstreeResult<()> {
        println!("Copying base image: {}", base_image);
        
        // This would implement copying from a base image
        // For now, we'll just create a minimal structure
        let debian_version = "13";
        let debian_codename = "trixie";
        
        // Create basic system files
        let os_release = format!(
            r#"PRETTY_NAME="Debian GNU/Linux {} ({})"
NAME="Debian GNU/Linux"
VERSION_ID="{}"
VERSION="{} ({})"
VERSION_CODENAME={}
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
"#,
            debian_version, debian_codename, debian_version, debian_version, debian_codename, debian_codename
        );
        
        fs::write(build_dir.join("etc/os-release"), os_release).await
            .map_err(|e| AptOstreeError::System(format!("Failed to write os-release: {}", e)))?;
        
        println!("✅ Base image copied");
        Ok(())
    }
    
    /// Install packages with dependency resolution
    async fn install_packages(&self, packages: &[String], package_type: &str) -> AptOstreeResult<()> {
        println!("Installing {} packages: {:?}", package_type, packages);
        
        // Resolve dependencies
        let all_packages = self.package_manager.resolve_dependencies(packages).await?;
        
        // Install packages
        for package in &all_packages {
            self.package_manager.install_package(package).await?;
        }
        
        println!("✅ {} packages installed", package_type);
        Ok(())
    }
    
    /// Apply customizations to the build
    async fn apply_customizations(&self, customizations: &Customizations) -> AptOstreeResult<()> {
        println!("Applying customizations...");
        
        // Apply file modifications
        if let Some(file_mods) = &customizations.file_modifications {
            for file_mod in file_mods {
                self.apply_file_modification(file_mod).await?;
            }
        }
        
        // Apply system modifications
        if let Some(sys_mods) = &customizations.system_modifications {
            for sys_mod in sys_mods {
                self.apply_system_modification(sys_mod).await?;
            }
        }
        
        // Run custom scripts
        if let Some(scripts) = &customizations.scripts {
            for script in scripts {
                self.run_custom_script(script).await?;
            }
        }
        
        println!("✅ Customizations applied");
        Ok(())
    }
    
    /// Apply a file modification
    async fn apply_file_modification(&self, file_mod: &crate::treefile::FileModification) -> AptOstreeResult<()> {
        let build_dir = self.workdir.join("build");
        let target_path = build_dir.join(&file_mod.path);
        
        match &file_mod.operation {
            crate::treefile::FileOperation::Create { content } => {
                // Create parent directories
                if let Some(parent) = target_path.parent() {
                    fs::create_dir_all(parent).await
                        .map_err(|e| AptOstreeError::System(format!("Failed to create parent dir: {}", e)))?;
                }
                
                // Write file content
                fs::write(&target_path, content).await
                    .map_err(|e| AptOstreeError::System(format!("Failed to write file: {}", e)))?;
            }
            crate::treefile::FileOperation::Delete => {
                if target_path.exists() {
                    fs::remove_file(&target_path).await
                        .map_err(|e| AptOstreeError::System(format!("Failed to delete file: {}", e)))?;
                }
            }
            crate::treefile::FileOperation::Copy { source } => {
                let source_path = PathBuf::from(source);
                if source_path.exists() {
                    fs::copy(&source_path, &target_path).await
                        .map_err(|e| AptOstreeError::System(format!("Failed to copy file: {}", e)))?;
                }
            }
        }
        
        Ok(())
    }
    
    /// Apply a system modification
    async fn apply_system_modification(&self, sys_mod: &crate::treefile::SystemModification) -> AptOstreeResult<()> {
        let build_dir = self.workdir.join("build");
        
        match &sys_mod.operation {
            crate::treefile::SystemOperation::UserAdd { username, uid, gid } => {
                // Add user to passwd and group files
                let passwd_entry = format!("{}:x:{}:{}::/home/{}:/bin/bash\n", username, uid, gid, username);
                let group_entry = format!("{}:x:{}:{}\n", username, gid, username);
                
                // Append to passwd file
                let passwd_path = build_dir.join("etc/passwd");
                if !passwd_path.exists() {
                    fs::write(&passwd_path, "root:x:0:0::/root:/bin/bash\n").await
                        .map_err(|e| AptOstreeError::System(format!("Failed to create passwd: {}", e)))?;
                }
                
                fs::OpenOptions::new()
                    .append(true)
                    .open(&passwd_path)
                    .await
                    .map_err(|e| AptOstreeError::System(format!("Failed to open passwd: {}", e)))?
                    .write_all(passwd_entry.as_bytes())
                    .await
                    .map_err(|e| AptOstreeError::System(format!("Failed to write passwd: {}", e)))?;
                
                // Append to group file
                let group_path = build_dir.join("etc/group");
                if !group_path.exists() {
                    fs::write(&group_path, "root:x:0:\n").await
                        .map_err(|e| AptOstreeError::System(format!("Failed to create group: {}", e)))?;
                }
                
                fs::OpenOptions::new()
                    .append(true)
                    .open(&group_path)
                    .await
                    .map_err(|e| AptOstreeError::System(format!("Failed to open group: {}", e)))?
                    .write_all(group_entry.as_bytes())
                    .await
                    .map_err(|e| AptOstreeError::System(format!("Failed to write group: {}", e)))?;
            }
            crate::treefile::SystemOperation::ServiceEnable { service_name } => {
                // Create systemd service symlink
                let service_dir = build_dir.join("etc/systemd/system/multi-user.target.wants");
                fs::create_dir_all(&service_dir).await
                    .map_err(|e| AptOstreeError::System(format!("Failed to create service dir: {}", e)))?;
                
                let service_link = service_dir.join(format!("{}.service", service_name));
                let service_file = format!("/lib/systemd/system/{}.service", service_name);
                
                // Create symlink (this is a simplified approach)
                fs::write(&service_link, service_file).await
                    .map_err(|e| AptOstreeError::System(format!("Failed to create service symlink: {}", e)))?;
            }
        }
        
        Ok(())
    }
    
    /// Run a custom script
    async fn run_custom_script(&self, script: &crate::treefile::Script) -> AptOstreeResult<()> {
        println!("Running custom script: {}", script.name);
        
        let build_dir = self.workdir.join("build");
        let script_path = build_dir.join("tmp").join(&script.name);
        
        // Write script content to file
        fs::write(&script_path, &script.content).await
            .map_err(|e| AptOstreeError::System(format!("Failed to write script: {}", e)))?;
        
        // Make script executable
        use std::os::unix::fs::PermissionsExt;
        let mut perms = fs::metadata(&script_path).await
            .map_err(|e| AptOstreeError::System(format!("Failed to get script metadata: {}", e)))?
            .permissions();
        perms.set_mode(0o755);
        fs::set_permissions(&script_path, perms).await
            .map_err(|e| AptOstreeError::System(format!("Failed to set script permissions: {}", e)))?;
        
        // Run script in chroot
        let output = Command::new("chroot")
            .args([
                &build_dir.to_string_lossy(),
                "/tmp/".to_string() + &script.name
            ])
            .output()
            .map_err(|e| AptOstreeError::System(format!("Failed to run script: {}", e)))?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            println!("Warning: script {} had issues: {}", script.name, stderr);
        }
        
        println!("✅ Custom script executed: {}", script.name);
        Ok(())
    }
    
    /// Get parent reference for the tree
    async fn get_parent_reference(&self, treefile: &Treefile) -> AptOstreeResult<Option<String>> {
        if let Some(parent_ref) = &treefile.metadata.parent {
            // Check if parent reference exists
            if self.ostree_integration.reference_exists(parent_ref).await? {
                return Ok(Some(parent_ref.clone()));
            } else {
                println!("Warning: Parent reference {} not found, creating without parent", parent_ref);
            }
        }
        
        Ok(None)
    }
    
    /// Clean up build artifacts
    async fn cleanup_build_artifacts(&self) -> AptOstreeResult<()> {
        println!("Cleaning up build artifacts...");
        
        // Remove temporary files
        let tmp_dir = self.workdir.join("build/tmp");
        if tmp_dir.exists() {
            fs::remove_dir_all(&tmp_dir).await
                .map_err(|e| AptOstreeError::System(format!("Failed to remove tmp dir: {}", e)))?;
        }
        
        // Remove APT cache
        let apt_cache = self.workdir.join("build/var/cache/apt");
        if apt_cache.exists() {
            fs::remove_dir_all(&apt_cache).await
                .map_err(|e| AptOstreeError::System(format!("Failed to remove APT cache: {}", e)))?;
        }
        
        // Remove APT lists
        let apt_lists = self.workdir.join("build/var/lib/apt/lists");
        if apt_lists.exists() {
            fs::remove_dir_all(&apt_lists).await
                .map_err(|e| AptOstreeError::System(format!("Failed to remove APT lists: {}", e)))?;
        }
        
        println!("✅ Build artifacts cleaned up");
        Ok(())
    }
    
    /// Get composition status and information
    pub async fn get_composition_info(&self) -> AptOstreeResult<String> {
        let mut info = String::new();
        
        info.push_str("=== apt-ostree Composition Information ===\n");
        info.push_str(&format!("Working directory: {}\n", self.workdir.display()));
        
        // OSTree repository info
        if let Ok(repo_info) = self.ostree_integration.get_repo_info().await {
            info.push_str("\n--- OSTree Repository ---\n");
            info.push_str(&repo_info);
        }
        
        // Build directory info
        let build_dir = self.workdir.join("build");
        if build_dir.exists() {
            info.push_str("\n--- Build Directory ---\n");
            info.push_str(&format!("Build directory: {}\n", build_dir.display()));
            
            // Count files in build directory
            if let Ok(entries) = fs::read_dir(&build_dir).await {
                let file_count = entries.count();
                info.push_str(&format!("Files in build directory: {}\n", file_count));
            }
        }
        
        Ok(info)
    }
}
