use tracing::{info, warn};
use crate::error::AptOstreeResult;
use crate::ostree::OstreeManager;
use serde_json;
use chrono;

/// Base image reference (e.g., "ubuntu:24.04")
#[derive(Debug, Clone)]
pub struct BaseImageRef {
    pub distribution: String,
    pub version: String,
    pub architecture: Option<String>,
}

/// Resolved base image information
#[derive(Debug, Clone)]
pub struct ResolvedBaseImage {
    pub ref_name: BaseImageRef,
    pub ostree_branch: String,
    pub commit_id: Option<String>,
    pub exists_locally: bool,
}

/// Compose operation options
#[derive(Debug, Clone)]
pub struct ComposeOptions {
    pub base: String,
    pub output: Option<String>,
    pub packages: Vec<String>,
    pub dry_run: bool,
}

/// Compose manager for handling base image resolution and compose operations
pub struct ComposeManager {
    branch: String,
    ostree_manager: OstreeManager,
}

impl ComposeManager {
    /// Create a new compose manager
    pub async fn new(branch: &str) -> AptOstreeResult<Self> {
        // Initialize OSTree manager for real branch operations
        let ostree_manager = OstreeManager::new("/var/lib/apt-ostree/repo")?;
        
        Ok(Self { 
            branch: branch.to_string(),
            ostree_manager,
        })
    }

    /// Resolve base image reference to OSTree branch
    pub async fn resolve_base_image(&self, base_ref: &str) -> AptOstreeResult<ResolvedBaseImage> {
        info!("Resolving base image: {}", base_ref);
        
        // Parse base image reference (e.g., "ubuntu:24.04")
        let base_image = self.parse_base_image_ref(base_ref)?;
        
        // Convert to OSTree branch name
        let ostree_branch = format!("{}/{}/{}", 
            base_image.distribution, 
            base_image.version, 
            base_image.architecture.as_deref().unwrap_or("x86_64")
        );
        
        info!("Checking if OSTree branch exists: {}", ostree_branch);
        
        // Check if branch exists locally
        let exists_locally = self.check_branch_exists(&ostree_branch).await?;
        
        if !exists_locally {
            info!("Base image not found locally, attempting to pull from registry");
            self.pull_base_image_from_registry(&base_image, &ostree_branch).await?;
        }
        
        Ok(ResolvedBaseImage {
            ref_name: base_image,
            ostree_branch,
            commit_id: None, // TODO: Get actual commit ID
            exists_locally: true,
        })
    }
    
    /// Pull base image from OSTree registry
    async fn pull_base_image_from_registry(&self, base_image: &BaseImageRef, branch: &str) -> AptOstreeResult<()> {
        info!("Pulling base image from registry: {:?} -> {}", base_image, branch);
        
        // Determine registry URL based on distribution
        let registry_url = match base_image.distribution.as_str() {
            "ubuntu" => "https://ostree.ubuntu.com/ubuntu",
            "debian" => "https://ostree.debian.org/debian", 
            _ => return Err(crate::error::AptOstreeError::InvalidArgument(
                format!("Unsupported distribution: {}", base_image.distribution)
            )),
        };
        
        let remote_name = format!("{}-{}", base_image.distribution, base_image.version);
        info!("Adding remote: {} -> {}", remote_name, registry_url);
        
        // First, add the remote if it doesn't exist
        let add_remote_output = tokio::process::Command::new("/usr/bin/ostree")
            .args(&["remote", "add", "--repo", "/var/lib/apt-ostree/repo", &remote_name, &registry_url])
            .output()
            .await?;
        
        // Ignore errors if remote already exists
        if !add_remote_output.status.success() {
            let stderr = String::from_utf8_lossy(&add_remote_output.stderr);
            if !stderr.contains("already exists") {
                return Err(crate::error::AptOstreeError::SystemError(
                    format!("Failed to add remote: {}", stderr)
                ));
            }
            info!("Remote {} already exists", remote_name);
        }
        
        // Now pull the branch from the remote
        info!("Pulling branch {} from remote {}", branch, remote_name);
        let pull_output = tokio::process::Command::new("/usr/bin/ostree")
            .args(&["pull", "--repo", "/var/lib/apt-ostree/repo", &remote_name, branch])
            .output()
            .await?;
        
        if !pull_output.status.success() {
            return Err(crate::error::AptOstreeError::SystemError(
                format!("Failed to pull base image: {}", String::from_utf8_lossy(&pull_output.stderr))
            ));
        }
        
        info!("Successfully pulled base image from registry");
        Ok(())
    }

    /// Parse base image reference (e.g., "ubuntu:24.04" -> BaseImageRef)
    fn parse_base_image_ref(&self, base_ref: &str) -> AptOstreeResult<BaseImageRef> {
        // Handle different formats:
        // - ubuntu:24.04
        // - ubuntu/24.04
        // - ubuntu/24.04/x86_64
        
        let parts: Vec<&str> = base_ref.split(':').collect();
        
        match parts.as_slice() {
            [distribution, version] => {
                // Format: ubuntu:24.04
                Ok(BaseImageRef {
                    distribution: distribution.to_string(),
                    version: version.to_string(),
                    architecture: None,
                })
            },
            _ => {
                // Try parsing as path format: ubuntu/24.04/x86_64
                let path_parts: Vec<&str> = base_ref.split('/').collect();
                match path_parts.as_slice() {
                    [distribution, version] => {
                        Ok(BaseImageRef {
                            distribution: distribution.to_string(),
                            version: version.to_string(),
                            architecture: None,
                        })
                    },
                    [distribution, version, arch] => {
                        Ok(BaseImageRef {
                            distribution: distribution.to_string(),
                            version: version.to_string(),
                            architecture: Some(arch.to_string()),
                        })
                    },
                    _ => Err(crate::error::AptOstreeError::InvalidArgument(
                        format!("Invalid base image reference format: {}", base_ref)
                    )),
                }
            }
        }
    }

    /// Map base image reference to OSTree branch
    fn map_to_ostree_branch(&self, base_image: &BaseImageRef) -> AptOstreeResult<String> {
        let arch = base_image.architecture.as_deref().unwrap_or("x86_64");
        
        // Map distribution names to OSTree branch patterns
        let branch = match base_image.distribution.to_lowercase().as_str() {
            "ubuntu" => format!("ubuntu/{}/{}", base_image.version, arch),
            "debian" => format!("debian/{}/{}", base_image.version, arch),
            "fedora" => format!("fedora/{}/{}", base_image.version, arch),
            _ => {
                // For unknown distributions, use the distribution name as-is
                format!("{}/{}/{}", base_image.distribution, base_image.version, arch)
            }
        };
        
        Ok(branch)
    }

    /// Check if an OSTree branch exists locally using real OSTree manager
    async fn check_branch_exists(&self, branch: &str) -> AptOstreeResult<bool> {
        info!("Checking if OSTree branch exists: {}", branch);
        
        // Use the existing OSTree manager to check branch existence
        match self.ostree_manager.list_branches() {
            Ok(branches) => {
                info!("Available branches: {:?}", branches);
                let exists = branches.contains(&branch.to_string());
                info!("Branch {} exists locally: {}", branch, exists);
                Ok(exists)
            },
            Err(e) => {
                warn!("Failed to check branch existence: {}", e);
                // If we can't check, assume it doesn't exist
                Ok(false)
            }
        }
    }

    /// Create a new deployment from a base image
    pub async fn create_deployment(&self, options: &ComposeOptions) -> AptOstreeResult<String> {
        info!("Creating deployment with options: {:?}", options);
        
        if options.dry_run {
            info!("DRY RUN: Would create deployment from base: {}", options.base);
            return Ok("dry-run-deployment-id".to_string());
        }
        
        // Resolve base image
        let resolved_base = self.resolve_base_image(&options.base).await?;
        
        if !resolved_base.exists_locally {
            // TODO: Pull base image from registry
            warn!("Base image not found locally, pulling from registry not yet implemented");
            return Err(crate::error::AptOstreeError::InvalidArgument(
                format!("Base image not found locally: {}", options.base)
            ));
        }
        
        // Create temporary staging directory
        let staging_dir = tempfile::tempdir()?;
        let staging_path = staging_dir.path();
        info!("Created staging directory: {:?}", staging_path);
        
        // Step 1: Checkout base image to staging directory
        info!("Checking out base image: {}", resolved_base.ostree_branch);
        self.ostree_manager.checkout_branch(&resolved_base.ostree_branch, staging_path.to_str().unwrap())?;
        
        // Step 2: Install packages if specified
        if !options.packages.is_empty() {
            info!("Installing packages: {:?}", options.packages);
            self.install_packages_in_staging(staging_path, &options.packages).await?;
        }
        
        // Step 3: Create OSTree commit from staging directory
        let output_branch = options.output.as_deref().unwrap_or(&resolved_base.ostree_branch);
        let commit_message = format!("Compose deployment from {} with packages: {:?}", 
                                   options.base, options.packages);
        
        info!("Creating OSTree commit for branch: {}", output_branch);
        let commit_id = self.ostree_manager.create_commit(
            staging_path,
            &commit_message,
            None,
            &serde_json::json!({
                "compose": {
                    "base": options.base,
                    "packages": options.packages,
                    "timestamp": chrono::Utc::now().timestamp()
                }
            })
        ).await?;
        
        // Step 4: Create or update the output branch
        if output_branch != &resolved_base.ostree_branch {
            info!("Creating new branch: {}", output_branch);
            self.ostree_manager.create_branch(output_branch, Some(&resolved_base.ostree_branch))?;
        }
        
        // Update the branch to point to our new commit
        // Use the existing commit_changes method to update the branch
        let _ = self.ostree_manager.commit_changes(output_branch, &commit_message)?;
        
        info!("Deployment created successfully: {} -> {}", output_branch, commit_id);
        Ok(commit_id)
    }
    
    /// Install packages in staging directory
    async fn install_packages_in_staging(&self, staging_path: &std::path::Path, packages: &[String]) -> AptOstreeResult<()> {
        info!("Installing packages in staging directory: {:?}", packages);
        
        if packages.is_empty() {
            return Ok(());
        }
        
        // Create package installation directory
        let package_dir = staging_path.join("var/lib/apt-ostree/packages");
        std::fs::create_dir_all(&package_dir)?;
        
        // Initialize APT manager for package operations
        let apt_manager = crate::apt::AptManager::new()?;
        
        // Download and install each package
        for package_name in packages {
            info!("Installing package: {}", package_name);
            
            // Get package metadata
            let package_info = apt_manager.get_package_info(package_name).await?;
            info!("Got package info: {} version {}", package_name, package_info.version);
            
            // Download the package
            let deb_path = apt_manager.download_package(package_name).await?;
            info!("Downloaded package to: {:?}", deb_path);
            
            // Create package metadata file
            let package_meta_path = package_dir.join(format!("{}.json", package_name));
            let package_metadata = serde_json::json!({
                "name": package_name,
                "version": package_info.version,
                "architecture": package_info.architecture,
                "description": package_info.description,
                "dependencies": package_info.depends,
                "install_timestamp": chrono::Utc::now().timestamp()
            });
            std::fs::write(&package_meta_path, serde_json::to_string_pretty(&package_metadata)?)?;
            
            // Extract package contents to staging directory
            self.extract_package_to_staging(&deb_path, staging_path).await?;
        }
        
        // Create package list file
        let package_list_path = package_dir.join("installed-packages.txt");
        let package_list = packages.join("\n");
        std::fs::write(&package_list_path, package_list)?;
        
        info!("Package installation completed for {} packages", packages.len());
        Ok(())
    }
    
    /// Extract package contents to staging directory
    async fn extract_package_to_staging(&self, deb_path: &std::path::Path, staging_path: &std::path::Path) -> AptOstreeResult<()> {
        info!("Extracting package {:?} to staging directory", deb_path);
        
        // Create temporary directory for package extraction
        let temp_dir = tempfile::tempdir()?;
        let extract_path = temp_dir.path();
        
        // Extract DEB package
        if !deb_path.exists() {
            return Err(crate::error::AptOstreeError::InvalidArgument(
                format!("DEB package not found: {:?}", deb_path)
            ));
        }
        
        // Extract DEB contents using dpkg-deb
        let output = tokio::process::Command::new("dpkg-deb")
            .args(&["-R", deb_path.to_str().unwrap(), extract_path.to_str().unwrap()])
            .output()
            .await?;
        
        if !output.status.success() {
            return Err(crate::error::AptOstreeError::SystemError(
                format!("Failed to extract DEB package: {}", String::from_utf8_lossy(&output.stderr))
            ));
        }
        
        // Copy extracted files to staging directory
        let data_dir = extract_path.join("data");
        if data_dir.exists() {
            info!("Copying package data from {:?} to staging directory", data_dir);
            self.copy_directory_recursive(&data_dir, staging_path)?;
            
            // Count files copied
            let file_count = self.count_files_recursive(&data_dir)?;
            info!("Copied {} files from package data", file_count);
        } else {
            // dpkg-deb -R extracts files directly to the root directory
            info!("Copying package files from {:?} to staging directory", extract_path);
            
            // Verify extraction directory exists and has content
            if !extract_path.exists() {
                return Err(crate::error::AptOstreeError::InvalidArgument(
                    format!("Extraction directory does not exist: {:?}", extract_path)
                ));
            }
            
            // Copy files from extraction directory to staging directory
            for entry in std::fs::read_dir(extract_path)? {
                let entry = entry?;
                let src_path = entry.path();
                let file_name = entry.file_name();
                
                // Skip DEBIAN directory (handled separately)
                if entry.file_type()?.is_dir() && file_name.to_str() == Some("DEBIAN") {
                    continue;
                }
                
                let dst_path = staging_path.join(&file_name);
                info!("Copying {:?} to {:?}", src_path, dst_path);
                
                if entry.file_type()?.is_dir() {
                    // For directories, merge contents instead of overwriting
                    if dst_path.exists() {
                        // Directory exists, copy contents recursively
                        self.copy_directory_recursive(&src_path, &dst_path)?;
                    } else {
                        // Directory doesn't exist, create it and copy the entire directory
                        std::fs::create_dir_all(&dst_path)?;
                        self.copy_directory_recursive(&src_path, &dst_path)?;
                    }
                } else {
                    // For files, ensure parent directory exists and copy
                    if let Some(parent) = dst_path.parent() {
                        std::fs::create_dir_all(parent)?;
                    }
                    std::fs::copy(&src_path, &dst_path)?;
                }
            }
            
            // Count files copied (excluding DEBIAN directory)
            let mut file_count = 0;
            for entry in std::fs::read_dir(extract_path)? {
                let entry = entry?;
                let path = entry.path();
                if entry.file_type()?.is_dir() && path.file_name().and_then(|s| s.to_str()) != Some("DEBIAN") {
                    file_count += self.count_files_recursive(&path)?;
                } else if entry.file_type()?.is_file() {
                    file_count += 1;
                }
            }
            info!("Copied {} files from package root", file_count);
        }
        
        // Copy control files to package metadata
        let control_dir = extract_path.join("control");
        if control_dir.exists() {
            let package_name = deb_path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown");
            let control_dest = staging_path.join("var/lib/apt-ostree/packages").join(package_name).join("control");
            std::fs::create_dir_all(&control_dest)?;
            self.copy_directory_recursive(&control_dir, &control_dest)?;
            info!("Copied control files for package {}", package_name);
        }
        
        // Extract and copy DEBIAN scripts if they exist
        let debian_dir = extract_path.join("DEBIAN");
        if debian_dir.exists() {
            let package_name = deb_path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown");
            let scripts_dest = staging_path.join("var/lib/apt-ostree/packages").join(package_name).join("scripts");
            std::fs::create_dir_all(&scripts_dest)?;
            self.copy_directory_recursive(&debian_dir, &scripts_dest)?;
            info!("Copied DEBIAN scripts for package {}", package_name);
        }
        
        info!("Package extracted successfully with all files");
        Ok(())
    }
    
    /// Count files recursively in directory
    fn count_files_recursive(&self, dir: &std::path::Path) -> AptOstreeResult<usize> {
        let mut count = 0;
        if dir.is_dir() {
            for entry in std::fs::read_dir(dir)? {
                let entry = entry?;
                let path = entry.path();
                
                if entry.file_type()?.is_dir() {
                    count += self.count_files_recursive(&path)?;
                } else {
                    count += 1;
                }
            }
        }
        Ok(count)
    }
    
    /// Copy directory recursively
    fn copy_directory_recursive(&self, src: &std::path::Path, dst: &std::path::Path) -> AptOstreeResult<()> {
        if src.is_dir() {
            std::fs::create_dir_all(dst)?;
            for entry in std::fs::read_dir(src)? {
                let entry = entry?;
                let src_path = entry.path();
                let dst_path = dst.join(entry.file_name());
                
                if entry.file_type()?.is_dir() {
                    self.copy_directory_recursive(&src_path, &dst_path)?;
                } else {
                    if let Some(parent) = dst_path.parent() {
                        std::fs::create_dir_all(parent)?;
                    }
                    std::fs::copy(&src_path, &dst_path)?;
                }
            }
        } else {
            if let Some(parent) = dst.parent() {
                std::fs::create_dir_all(parent)?;
            }
            std::fs::copy(src, dst)?;
        }
        Ok(())
    }

    /// List available base images
    pub async fn list_base_images(&self) -> AptOstreeResult<Vec<ResolvedBaseImage>> {
        info!("Listing available base images");
        
        // TODO: Implement listing of available base images
        // For now, return a hardcoded list
        let base_images = vec![
            "ubuntu:24.04",
            "ubuntu:22.04", 
            "debian:12",
            "debian:11",
        ];
        
        let mut resolved_images = Vec::new();
        
        for base_ref in base_images {
            match self.resolve_base_image(base_ref).await {
                Ok(resolved) => resolved_images.push(resolved),
                Err(e) => {
                    warn!("Failed to resolve base image {}: {}", base_ref, e);
                }
            }
        }
        
        Ok(resolved_images)
    }
} 