mod cli;
mod tui;
mod output;
mod spinner;

use libarc::{Package, PackageManager};
use cli::{Cli, Commands};
use std::process::Command;
use std::env;
use std::os::unix::process::CommandExt;

fn main() {
    let args = Cli::parse();

    // Check if install/uninstall commands need elevation
    match &args.command {
        Some(Commands::Install { .. }) | Some(Commands::Uninstall { .. }) => {
            if !is_root() {
                elevate_with_pkexec();
            }
        }
        _ => {}
    }

    // Show help if no command is provided
    if args.should_show_help() {
        cli::print_help();
        return;
    }

    // Create package manager (loads config from /etc/arc.json)
    let pm = match PackageManager::new() {
        Ok(pm) => pm,
        Err(e) => {
            if args.verbose {
                eprintln!("Warning: Could not load config: {}", e);
                eprintln!("Using default configuration");
            }
            PackageManager::default()
        }
    };

    if args.verbose {
        println!("Configuration loaded:");
        println!("  - Flatpak to Pacman mappings: {}", pm.config().flatpak_to_pacman.len());
        println!("  - Blacklisted flatpaks: {}", pm.config().flatpak_blacklist.len());
        println!("  - Whitelisted pacman packages: {}", pm.config().pacman_whitelist.len());
        println!("  - Pacman aliases: {}", pm.config().pacman_aliases.len());
        println!("  - Companion packages: {}", pm.config().pacman_companions.len());
        println!();
    }

    match &args.command {
        Some(Commands::Search { query, pacman_only, flatpak_only }) => {
            handle_search(&pm, query, *pacman_only, *flatpak_only, args.verbose);
        }
        Some(Commands::Install { package, force: _ }) => {
            handle_install(&pm, package, args.verbose);
        }
        Some(Commands::Uninstall { package, force: _ }) => {
            handle_uninstall(&pm, package, args.verbose);
        }
        Some(Commands::Info { package }) => {
            handle_info(&pm, package, args.verbose);
        }
        Some(Commands::List { pacman_only, flatpak_only }) => {
            handle_list(&pm, *pacman_only, *flatpak_only, args.verbose);
        }
        Some(Commands::Config { all }) => {
            handle_config(&pm, *all, args.verbose);
        }
        None => {
            // Already handled above
        }
    }
}

/// Check if running as root (UID 0)
fn is_root() -> bool {
    unsafe { libc::geteuid() == 0 }
}

/// Elevate privileges using pkexec and re-execute the current command
fn elevate_with_pkexec() -> ! {
    let mut cmd = Command::new("pkexec");
    
    // Get the path to the current executable
    let exe = env::current_exe().expect("Failed to get current executable path");
    cmd.arg(exe);
    
    // Pass all command line arguments
    for arg in env::args().skip(1) {
        cmd.arg(arg);
    }
    
    // Execute pkexec, replacing current process
    match cmd.exec() {
        _ => {
            // exec() never returns on success, only on error
            eprintln!("Error: Failed to elevate privileges with pkexec");
            eprintln!("Make sure pkexec is installed and you have permission to use it");
            std::process::exit(1);
        }
    }
}

fn handle_search(pm: &PackageManager, query: &str, pacman_only: bool, flatpak_only: bool, verbose: bool) {
    if verbose {
        output::info(&format!("Searching for '{}'...", query));
        println!();
    }

    match pm.search(query) {
        Ok(packages) => {
            if packages.is_empty() {
                output::warning(&format!("No packages found matching '{}'", query));
                return;
            }

            let filtered: Vec<_> = packages
                .iter()
                .filter(|pkg| {
                    if pacman_only {
                        pkg.is_pacman()
                    } else if flatpak_only {
                        pkg.is_flatpak()
                    } else {
                        true
                    }
                })
                .collect();

            if filtered.is_empty() {
                output::warning("No packages found matching filters");
                return;
            }

            println!("{}Found {} package(s):{}\n", output::colors::BOLD, filtered.len(), output::colors::RESET);
            for pkg in filtered {
                match pkg {
                    Package::Pacman(name) => {
                        print!("  ");
                        output::package_pacman(name);
                        println!();
                    }
                    Package::Flatpak(bundle_id) => {
                        print!("  ");
                        output::package_flatpak(bundle_id);
                        println!();
                    }
                }
            }
        }
        Err(e) => output::error(&format!("Failed to search for packages: {}", e)),
    }
}

fn handle_install(pm: &PackageManager, package: &str, verbose: bool) {
    if verbose {
        output::info(&format!("Installing '{}'...", package));
        println!();
    }

    // Try as pacman package first (enforces whitelist via pm.install)
    let pacman_pkg = Package::Pacman(package.to_string());
    if let Ok((success, logs)) = install_with_spinner(&pm, &pacman_pkg, package, verbose) {
        if success {
            output::success(&format!("Successfully installed {}", package));
            if !logs.is_empty() && !logs.contains("Überspringe") {
                println!("\n{}Installation logs:{}", output::colors::BOLD, output::colors::RESET);
                println!("{}", logs);
            }
            return;
        }
        if verbose {
            output::warning(&format!("Could not install as pacman package"));
        }
    }

    // Try to find best matching flatpak
    if verbose {
        output::info("Searching for flatpak matches...");
    }
    
    match pm.search(package) {
        Ok(results) => {
            let flatpak_match = results.iter()
                .filter_map(|pkg| {
                    if let Package::Flatpak(bundle_id) = pkg {
                        let lower_bundle = bundle_id.to_lowercase();
                        let lower_query = package.to_lowercase();
                        
                        let score = if lower_bundle == lower_query {
                            3
                        } else if lower_bundle.contains(&lower_query) {
                            2
                        } else {
                            0
                        };
                        
                        Some((score, bundle_id))
                    } else {
                        None
                    }
                })
                .max_by_key(|(score, _)| *score)
                .map(|(_, bundle_id)| bundle_id);
            
            if let Some(bundle_id) = flatpak_match {
                if verbose {
                    output::info(&format!("Found flatpak match: {}", bundle_id));
                    println!();
                }
                
                let flatpak_pkg = Package::Flatpak(bundle_id.clone());
                if let Ok((success, logs)) = install_with_spinner(&pm, &flatpak_pkg, package, verbose) {
                    if success {
                        output::success(&format!("Successfully installed {}", package));
                        if !logs.is_empty() && !logs.contains("Überspringe") {
                            println!("\n{}Installation logs:{}", output::colors::BOLD, output::colors::RESET);
                            println!("{}", logs);
                        }
                        return;
                    } else {
                        output::error(&format!("Failed to install package '{}'", package));
                        println!("\n{}Logs:{}", output::colors::BOLD, output::colors::RESET);
                        println!("{}", logs);
                    }
                }
            } else {
                output::error(&format!("Package '{}' not found", package));
            }
        }
        Err(e) => {
            output::error(&format!("Error searching for package '{}': {}", package, e));
        }
    }
}

/// Helper function to install with spinner and log capturing
/// Returns (success, logs) without displaying errors - errors handled by caller
/// Uses PackageManager methods to respect all policies (whitelist, aliases, companions)
fn install_with_spinner(
    pm: &PackageManager, 
    package: &Package,
    package_name: &str,
    verbose: bool,
) -> Result<(bool, String), Box<dyn std::error::Error>> {
    let spinner = spinner::Spinner::new(&format!("Installing {}...", package_name));
    
    // Use pm.install() which enforces all policies: whitelist, blacklist, aliases, companions
    match pm.install(package) {
        Ok(()) => {
            spinner.stop();
            // Installation succeeded
            Ok((true, String::new()))
        }
        Err(e) => {
            spinner.stop();
            // Installation failed - return error message as "logs"
            if verbose {
                eprintln!("Installation failed: {}", e);
            }
            Ok((false, format!("Installation failed: {}", e)))
        }
    }
}

fn handle_uninstall(pm: &PackageManager, package: &str, verbose: bool) {
    if verbose {
        output::info(&format!("Uninstalling '{}'...", package));
        println!();
    }

    // Try to find and uninstall pacman package (enforces whitelist via pm.uninstall)
    let pacman_pkg = Package::Pacman(package.to_string());
    if let Ok((success, logs)) = uninstall_with_spinner(&pm, &pacman_pkg, package, verbose) {
        if success {
            output::success(&format!("Successfully uninstalled {}", package));
            if !logs.is_empty() {
                println!("\n{}Uninstall logs:{}", output::colors::BOLD, output::colors::RESET);
                println!("{}", logs);
            }
            return;
        }
        if verbose {
            output::warning(&format!("Could not uninstall as pacman package"));
        }
    }

    // Try to find and uninstall flatpak
    match pm.search(package) {
        Ok(results) => {
            let flatpak_match = results.iter()
                .filter_map(|pkg| {
                    if let Package::Flatpak(bundle_id) = pkg {
                        let lower_bundle = bundle_id.to_lowercase();
                        let lower_query = package.to_lowercase();
                        
                        let score = if lower_bundle == lower_query {
                            3
                        } else if lower_bundle.contains(&lower_query) {
                            2
                        } else {
                            0
                        };
                        
                        Some((score, bundle_id))
                    } else {
                        None
                    }
                })
                .max_by_key(|(score, _)| *score)
                .map(|(_, bundle_id)| bundle_id);
            
            if let Some(bundle_id) = flatpak_match {
                if verbose {
                    output::info(&format!("Found flatpak match: {}", bundle_id));
                    println!();
                }
                
                let flatpak_pkg = Package::Flatpak(bundle_id.clone());
                if let Ok((success, logs)) = uninstall_with_spinner(&pm, &flatpak_pkg, package, verbose) {
                    if success {
                        output::success(&format!("Successfully uninstalled {}", package));
                        if !logs.is_empty() {
                            println!("\n{}Uninstall logs:{}", output::colors::BOLD, output::colors::RESET);
                            println!("{}", logs);
                        }
                        return;
                    } else {
                        output::error(&format!("Failed to uninstall package '{}'", package));
                        println!("\n{}Logs:{}", output::colors::BOLD, output::colors::RESET);
                        println!("{}", logs);
                    }
                }
            } else {
                output::error(&format!("Package '{}' not found", package));
            }
        }
        Err(e) => {
            output::error(&format!("Error searching for package '{}': {}", package, e));
        }
    }
}

/// Helper function to uninstall with spinner and log capturing
/// Returns (success, logs) without displaying errors - errors handled by caller
/// Uses PackageManager methods to respect all policies (whitelist, blacklist, companions)
fn uninstall_with_spinner(
    pm: &PackageManager, 
    package: &Package,
    package_name: &str,
    verbose: bool,
) -> Result<(bool, String), Box<dyn std::error::Error>> {
    let spinner = spinner::Spinner::new(&format!("Uninstalling {}...", package_name));
    
    // Use pm.uninstall() which enforces all policies: whitelist, blacklist, companions
    match pm.uninstall(package) {
        Ok(()) => {
            spinner.stop();
            // Uninstallation succeeded
            Ok((true, String::new()))
        }
        Err(e) => {
            spinner.stop();
            // Uninstallation failed - return error message as "logs"
            if verbose {
                eprintln!("Uninstallation failed: {}", e);
            }
            Ok((false, format!("Uninstallation failed: {}", e)))
        }
    }
}

fn handle_info(pm: &PackageManager, package: &str, verbose: bool) {
    if verbose {
        output::info(&format!("Checking if '{}' is installed...", package));
        println!();
    }

    let pkg = Package::Pacman(package.to_string());
    match pm.is_installed(&pkg) {
        Ok(installed) => {
            if installed {
                output::success(&format!("Package '{}' is installed", package));
            } else {
                output::warning(&format!("Package '{}' is not installed", package));
            }
        }
        Err(e) => output::error(&format!("Failed to check package: {}", e)),
    }
}

fn handle_list(pm: &PackageManager, pacman_only: bool, flatpak_only: bool, verbose: bool) {
    if verbose {
        output::info("Listing installed packages...");
        println!();
    }

    match pm.list_installed() {
        Ok(packages) => {
            let filtered: Vec<_> = packages
                .iter()
                .filter(|pkg| {
                    if pacman_only {
                        pkg.is_pacman()
                    } else if flatpak_only {
                        pkg.is_flatpak()
                    } else {
                        true
                    }
                })
                .collect();

            if filtered.is_empty() {
                output::warning("No installed packages found");
                return;
            }

            let pacman_count = filtered.iter().filter(|p| p.is_pacman()).count();
            let flatpak_count = filtered.iter().filter(|p| p.is_flatpak()).count();

            println!("{}Total: {} packages{}", output::colors::BOLD, filtered.len(), output::colors::RESET);
            output::count(pacman_count, "Pacman");
            output::count(flatpak_count, "Flatpak");
            println!();

            for pkg in filtered {
                match pkg {
                    Package::Pacman(name) => {
                        print!("  ");
                        output::package_pacman(name);
                        println!();
                    }
                    Package::Flatpak(bundle_id) => {
                        print!("  ");
                        output::package_flatpak(bundle_id);
                        println!();
                    }
                }
            }
        }
        Err(e) => output::error(&format!("Failed to list packages: {}", e)),
    }
}

fn handle_config(pm: &PackageManager, all: bool, verbose: bool) {
    if verbose {
        output::header("Configuration Information");
        println!();
    }

    let config = pm.config();

    output::count(config.flatpak_to_pacman.len(), "Flatpak to Pacman Mappings");
    if all && !config.flatpak_to_pacman.is_empty() {
        for (flatpak, pacman) in &config.flatpak_to_pacman {
            println!("  {} → {}", flatpak, pacman);
        }
        println!();
    }

    output::count(config.flatpak_blacklist.len(), "Flatpak Blacklist");
    if all && !config.flatpak_blacklist.is_empty() {
        for pkg in &config.flatpak_blacklist {
            println!("  - {}", pkg);
        }
        println!();
    }

    output::count(config.pacman_whitelist.len(), "Pacman Whitelist");
    if all && !config.pacman_whitelist.is_empty() {
        for pkg in &config.pacman_whitelist {
            println!("  - {}", pkg);
        }
        println!();
    }

    output::count(config.pacman_aliases.len(), "Pacman Aliases");
    if all && !config.pacman_aliases.is_empty() {
        for (alias, actual) in &config.pacman_aliases {
            println!("  {} → {}", alias, actual);
        }
        println!();
    }

    output::count(config.pacman_companions.len(), "Companion Packages");
    if all && !config.pacman_companions.is_empty() {
        for (pkg, companions) in &config.pacman_companions {
            println!("  {}:", pkg);
            for companion in companions {
                println!("    - {}", companion);
            }
        }
    }
}
