use clap::{Parser, Subcommand};
use glib::ExitCode;
use gtk::prelude::*;
use std::time::Instant;

use crate::core::backup;
use crate::core::database::Database;
use crate::core::discovery;
use crate::core::duplicates;
use crate::core::fuse;
use crate::core::inspector;
use crate::core::integrator;
use crate::core::launcher;
use crate::core::orphan;
use crate::core::updater;
use crate::core::verification;
use crate::core::wayland;

#[derive(Parser)]
#[command(name = "driftwood", version, about = "Modern AppImage manager for GNOME desktops")]
pub struct Cli {
    #[command(subcommand)]
    pub command: Option<Commands>,
}

#[derive(Subcommand)]
pub enum Commands {
    /// List all known AppImages
    List {
        /// Output format: table or json
        #[arg(long, default_value = "table")]
        format: String,
    },
    /// Scan for AppImages in configured directories
    Scan,
    /// Integrate an AppImage (create .desktop and install icon)
    Integrate {
        /// Path to the AppImage
        path: String,
    },
    /// Remove integration for an AppImage
    Remove {
        /// Path to the AppImage
        path: String,
    },
    /// Clean orphaned desktop entries
    Clean,
    /// Inspect an AppImage and show its metadata
    Inspect {
        /// Path to the AppImage
        path: String,
    },
    /// Show system status (FUSE, Wayland, desktop environment)
    Status,
    /// Check all AppImages for updates
    CheckUpdates,
    /// Find duplicate and multi-version AppImages
    Duplicates,
    /// Launch an AppImage (with tracking and FUSE detection)
    Launch {
        /// Path to the AppImage
        path: String,
    },
    /// Update all AppImages that have available updates
    UpdateAll,
    /// Enable or disable autostart for an AppImage
    Autostart {
        /// Path to the AppImage
        path: String,
        /// Enable autostart
        #[arg(long, conflicts_with = "disable")]
        enable: bool,
        /// Disable autostart
        #[arg(long)]
        disable: bool,
    },
    /// Remove all system modifications made by Driftwood
    Purge,
    /// Verify an AppImage's integrity (signature or SHA256)
    Verify {
        /// Path to the AppImage
        path: String,
        /// Expected SHA256 hash (if not provided, checks embedded signature)
        #[arg(long)]
        sha256: Option<String>,
    },
    /// Export app library to a JSON file
    Export {
        /// Output file path (default: stdout)
        #[arg(long)]
        output: Option<String>,
    },
    /// Import app library from a JSON file
    Import {
        /// Path to the JSON file to import
        file: String,
    },
}

pub fn run_command(command: Commands) -> ExitCode {
    let db = match Database::open() {
        Ok(db) => db,
        Err(e) => {
            eprintln!("Error: Failed to open database: {}", e);
            return ExitCode::FAILURE;
        }
    };

    match command {
        Commands::List { format } => cmd_list(&db, &format),
        Commands::Scan => cmd_scan(&db),
        Commands::Integrate { path } => cmd_integrate(&db, &path),
        Commands::Remove { path } => cmd_remove(&db, &path),
        Commands::Clean => cmd_clean(),
        Commands::Inspect { path } => cmd_inspect(&path),
        Commands::Status => cmd_status(),
        Commands::CheckUpdates => cmd_check_updates(&db),
        Commands::Duplicates => cmd_duplicates(&db),
        Commands::Launch { path } => cmd_launch(&db, &path),
        Commands::UpdateAll => cmd_update_all(&db),
        Commands::Autostart { path, enable, disable } => cmd_autostart(&db, &path, enable, disable),
        Commands::Purge => cmd_purge(&db),
        Commands::Verify { path, sha256 } => cmd_verify(&path, sha256.as_deref()),
        Commands::Export { output } => cmd_export(&db, output.as_deref()),
        Commands::Import { file } => cmd_import(&db, &file),
    }
}

fn cmd_list(db: &Database, format: &str) -> ExitCode {
    let records = match db.get_all_appimages() {
        Ok(r) => r,
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    if records.is_empty() {
        println!("No AppImages found. Run 'driftwood scan' first.");
        return ExitCode::SUCCESS;
    }

    if format == "json" {
        let items: Vec<serde_json::Value> = records
            .iter()
            .map(|r| {
                serde_json::json!({
                    "name": r.app_name.as_deref().unwrap_or(&r.filename),
                    "version": r.app_version.as_deref().unwrap_or(""),
                    "path": r.path,
                    "size": r.size_bytes,
                    "integrated": r.integrated,
                })
            })
            .collect();
        println!("{}", serde_json::to_string_pretty(&items).unwrap_or_else(|_| "[]".into()));
        return ExitCode::SUCCESS;
    }

    // Table output
    let name_width = records
        .iter()
        .map(|r| r.app_name.as_deref().unwrap_or(&r.filename).len())
        .max()
        .unwrap_or(4)
        .max(4)
        .min(30);

    let ver_width = records
        .iter()
        .map(|r| r.app_version.as_deref().unwrap_or("").len())
        .max()
        .unwrap_or(7)
        .max(7)
        .min(15);

    println!(
        " {:<name_w$}  {:<ver_w$}  {:>10}  {}",
        "Name", "Version", "Size", "Integrated",
        name_w = name_width,
        ver_w = ver_width,
    );
    println!(
        " {:-<name_w$}  {:-<ver_w$}  {:->10}  ----------",
        "", "", "",
        name_w = name_width,
        ver_w = ver_width,
    );

    let mut integrated_count = 0;
    for r in &records {
        let name = r.app_name.as_deref().unwrap_or(&r.filename);
        let display_name = if name.len() > name_width {
            &name[..name_width]
        } else {
            name
        };
        let version = r.app_version.as_deref().unwrap_or("");
        let size = humansize::format_size(r.size_bytes as u64, humansize::BINARY);
        let status = if r.integrated { "Yes" } else { "No" };
        if r.integrated {
            integrated_count += 1;
        }
        println!(
            " {:<name_w$}  {:<ver_w$}  {:>10}  {}",
            display_name, version, size, status,
            name_w = name_width,
            ver_w = ver_width,
        );
    }

    println!();
    println!(
        " {} AppImages found, {} integrated",
        records.len(),
        integrated_count,
    );

    ExitCode::SUCCESS
}

fn cmd_scan(db: &Database) -> ExitCode {
    let settings = gtk::gio::Settings::new(crate::config::APP_ID);
    let dirs: Vec<String> = settings
        .strv("scan-directories")
        .iter()
        .map(|s| s.to_string())
        .collect();

    println!("Scanning directories:");
    for d in &dirs {
        let expanded = discovery::expand_tilde(d);
        println!("  {}", expanded.display());
    }

    let start = Instant::now();
    let discovered = discovery::scan_directories(&dirs);
    let total = discovered.len();
    let mut new_count = 0;

    for d in &discovered {
        let existing = db
            .get_appimage_by_path(&d.path.to_string_lossy())
            .ok()
            .flatten();

        let modified = d.modified_time
            .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
            .and_then(|dur| {
                chrono::DateTime::from_timestamp(dur.as_secs() as i64, 0)
                    .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
            });

        let id = db.upsert_appimage(
            &d.path.to_string_lossy(),
            &d.filename,
            Some(d.appimage_type.as_i32()),
            d.size_bytes as i64,
            d.is_executable,
            modified.as_deref(),
        ).unwrap_or(0);

        if existing.is_none() {
            new_count += 1;
            println!("  [NEW] {}", d.filename);
        }

        let needs_metadata = existing
            .as_ref()
            .map(|r| r.app_name.is_none())
            .unwrap_or(true);

        if needs_metadata {
            print!("  Inspecting {}... ", d.filename);
            match inspector::inspect_appimage(&d.path, &d.appimage_type) {
                Ok(metadata) => {
                    let categories = if metadata.categories.is_empty() {
                        None
                    } else {
                        Some(metadata.categories.join(";"))
                    };
                    db.update_metadata(
                        id,
                        metadata.app_name.as_deref(),
                        metadata.app_version.as_deref(),
                        metadata.description.as_deref(),
                        metadata.developer.as_deref(),
                        categories.as_deref(),
                        metadata.architecture.as_deref(),
                        metadata.cached_icon_path.as_ref().map(|p| p.to_string_lossy()).as_deref(),
                        Some(&metadata.desktop_entry_content),
                    ).ok();
                    println!(
                        "{}",
                        metadata.app_name.as_deref().unwrap_or("(no name)")
                    );
                }
                Err(e) => {
                    println!("failed: {}", e);
                }
            }
        }
    }

    let duration = start.elapsed();
    db.log_scan(
        "cli",
        &dirs,
        total as i32,
        new_count,
        0,
        duration.as_millis() as i64,
    ).ok();

    println!();
    println!(
        "Scan complete: {} found, {} new ({:.1}s)",
        total,
        new_count,
        duration.as_secs_f64(),
    );

    ExitCode::SUCCESS
}

fn cmd_integrate(db: &Database, path: &str) -> ExitCode {
    let record = match db.get_appimage_by_path(path) {
        Ok(Some(r)) => r,
        Ok(None) => {
            eprintln!("Error: '{}' is not in the database. Run 'driftwood scan' first.", path);
            return ExitCode::FAILURE;
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    if record.integrated {
        println!("{} is already integrated.", record.app_name.as_deref().unwrap_or(&record.filename));
        return ExitCode::SUCCESS;
    }

    match integrator::integrate_tracked(&record, &db) {
        Ok(result) => {
            db.set_integrated(
                record.id,
                true,
                Some(&result.desktop_file_path.to_string_lossy()),
            ).ok();
            println!(
                "Integrated {} -> {}",
                record.app_name.as_deref().unwrap_or(&record.filename),
                result.desktop_file_path.display(),
            );
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            ExitCode::FAILURE
        }
    }
}

fn cmd_remove(db: &Database, path: &str) -> ExitCode {
    let record = match db.get_appimage_by_path(path) {
        Ok(Some(r)) => r,
        Ok(None) => {
            eprintln!("Error: '{}' is not in the database.", path);
            return ExitCode::FAILURE;
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    if !record.integrated {
        println!("{} is not integrated.", record.app_name.as_deref().unwrap_or(&record.filename));
        return ExitCode::SUCCESS;
    }

    integrator::undo_all_modifications(&db, record.id).ok();
    match integrator::remove_integration(&record) {
        Ok(()) => {
            db.set_integrated(record.id, false, None).ok();
            println!(
                "Removed integration for {}",
                record.app_name.as_deref().unwrap_or(&record.filename),
            );
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            ExitCode::FAILURE
        }
    }
}

fn cmd_clean() -> ExitCode {
    let orphans = orphan::detect_orphans();

    if orphans.is_empty() {
        println!("No orphaned desktop entries found.");
        return ExitCode::SUCCESS;
    }

    println!("Found {} orphaned entries:", orphans.len());
    for entry in &orphans {
        println!(
            "  {} (was: {})",
            entry.app_name.as_deref().unwrap_or("Unknown"),
            entry.original_appimage_path,
        );
    }

    match orphan::clean_all_orphans() {
        Ok(summary) => {
            println!(
                "Cleaned {} desktop entries, {} icons",
                summary.entries_removed,
                summary.icons_removed,
            );
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error during cleanup: {}", e);
            ExitCode::FAILURE
        }
    }
}

fn cmd_inspect(path: &str) -> ExitCode {
    let file_path = std::path::Path::new(path);
    if !file_path.exists() {
        eprintln!("Error: file not found: {}", path);
        return ExitCode::FAILURE;
    }

    // Detect AppImage type
    let discovered = discovery::scan_directories(&[path.to_string()]);
    if discovered.is_empty() {
        // Try scanning the parent directory and finding by path
        let parent = file_path.parent().unwrap_or(std::path::Path::new("."));
        let all = discovery::scan_directories(&[parent.to_string_lossy().to_string()]);
        let found = all.iter().find(|d| d.path == file_path);
        if let Some(d) = found {
            return do_inspect(file_path, &d.appimage_type);
        }
        eprintln!("Error: '{}' does not appear to be an AppImage", path);
        return ExitCode::FAILURE;
    }

    let d = &discovered[0];
    do_inspect(file_path, &d.appimage_type)
}

fn cmd_status() -> ExitCode {
    println!("System Status");
    println!("=============");
    println!();

    // Display server
    let session = wayland::detect_session_type();
    println!("  Display server: {}", session.label());

    // Desktop environment
    let de = wayland::detect_desktop_environment();
    println!("  Desktop: {}", de);

    // XWayland
    println!(
        "  XWayland: {}",
        if wayland::has_xwayland() {
            "running"
        } else {
            "not detected"
        }
    );
    println!();

    // FUSE
    let fuse_info = fuse::detect_system_fuse();
    println!("  FUSE status: {}", fuse_info.status.label());
    println!("    libfuse2: {}", if fuse_info.has_libfuse2 { "yes" } else { "no" });
    println!("    libfuse3: {}", if fuse_info.has_libfuse3 { "yes" } else { "no" });
    println!("    fusermount: {}", fuse_info.fusermount_path.as_deref().unwrap_or("not found"));
    println!("    /dev/fuse: {}", if fuse_info.has_dev_fuse { "present" } else { "missing" });

    if let Some(ref hint) = fuse_info.install_hint {
        println!();
        println!("  Fix: {}", hint);
    }

    // AppImageLauncher
    if let Some(version) = fuse::detect_appimagelauncher() {
        println!();
        println!("  WARNING: AppImageLauncher v{} detected (may conflict)", version);
    }

    println!();

    // AppImageUpdate tool
    println!(
        "  AppImageUpdate: {}",
        if updater::has_appimage_update_tool() {
            "available (delta updates enabled)"
        } else {
            "not found (full downloads only)"
        }
    );

    ExitCode::SUCCESS
}

fn cmd_check_updates(db: &Database) -> ExitCode {
    let records = match db.get_all_appimages() {
        Ok(r) => r,
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    if records.is_empty() {
        println!("No AppImages found. Run 'driftwood scan' first.");
        return ExitCode::SUCCESS;
    }

    println!("Checking {} AppImages for updates...", records.len());
    println!();

    let mut updates_found = 0;

    for record in &records {
        let name = record.app_name.as_deref().unwrap_or(&record.filename);
        let appimage_path = std::path::Path::new(&record.path);

        if !appimage_path.exists() {
            continue;
        }

        print!("  {} ... ", name);

        let (type_label, raw_info, check_result) = updater::check_appimage_for_update(
            appimage_path,
            record.app_version.as_deref(),
        );

        // Store update info
        if raw_info.is_some() || type_label.is_some() {
            db.update_update_info(record.id, raw_info.as_deref(), type_label.as_deref()).ok();
        }

        match check_result {
            Some(result) if result.update_available => {
                let latest = result.latest_version.as_deref().unwrap_or("unknown");
                println!(
                    "UPDATE AVAILABLE ({} -> {})",
                    record.app_version.as_deref().unwrap_or("?"),
                    latest,
                );
                db.set_update_available(record.id, Some(latest), result.download_url.as_deref()).ok();
                updates_found += 1;
            }
            Some(_) => {
                println!("up to date");
                db.clear_update_available(record.id).ok();
            }
            None => {
                if raw_info.is_none() {
                    println!("no update info");
                } else {
                    println!("check failed");
                }
            }
        }
    }

    println!();
    if updates_found == 0 {
        println!("All AppImages are up to date.");
    } else {
        println!("{} update{} available.", updates_found, if updates_found == 1 { "" } else { "s" });
    }

    ExitCode::SUCCESS
}

fn cmd_duplicates(db: &Database) -> ExitCode {
    let groups = duplicates::detect_duplicates(db);

    if groups.is_empty() {
        println!("No duplicate or multi-version AppImages found.");
        return ExitCode::SUCCESS;
    }

    let summary = duplicates::summarize_duplicates(&groups);
    println!(
        "Found {} duplicate groups ({} exact, {} multi-version)",
        summary.total_groups,
        summary.exact_duplicates,
        summary.multi_version,
    );
    println!(
        "Potential savings: {}",
        humansize::format_size(summary.total_potential_savings, humansize::BINARY),
    );
    println!();

    for group in &groups {
        println!("  {} ({})", group.app_name, group.match_reason.label());
        for member in &group.members {
            let r = &member.record;
            let version = r.app_version.as_deref().unwrap_or("?");
            let size = humansize::format_size(r.size_bytes as u64, humansize::BINARY);
            let rec = member.recommendation.label();
            println!("    {} v{} ({}) - {}", r.path, version, size, rec);
        }
        if group.potential_savings > 0 {
            println!(
                "    Savings: {}",
                humansize::format_size(group.potential_savings, humansize::BINARY),
            );
        }
        println!();
    }

    ExitCode::SUCCESS
}

fn cmd_launch(db: &Database, path: &str) -> ExitCode {
    let file_path = std::path::Path::new(path);
    if !file_path.exists() {
        eprintln!("Error: file not found: {}", path);
        return ExitCode::FAILURE;
    }

    // Try to find in database for tracking
    let record = db.get_appimage_by_path(path).ok().flatten();

    let launch_result = if let Some(ref record) = record {
        launcher::launch_appimage(db, record.id, file_path, "cli", &[], &[])
    } else {
        launcher::launch_appimage_simple(file_path, &[])
    };

    match launch_result {
        launcher::LaunchResult::Started { pid, method } => {
            let name = record.as_ref()
                .and_then(|r| r.app_name.as_deref())
                .unwrap_or(path);
            println!("Launched {} (PID: {}, {})", name, pid, method.as_str());
            ExitCode::SUCCESS
        }
        launcher::LaunchResult::Crashed { exit_code, stderr, method } => {
            let name = record.as_ref()
                .and_then(|r| r.app_name.as_deref())
                .unwrap_or(path);
            eprintln!(
                "{} crashed on launch (exit code: {}, method: {})\n{}",
                name,
                exit_code.map(|c: i32| c.to_string()).unwrap_or_else(|| "unknown".into()),
                method.as_str(),
                stderr,
            );
            ExitCode::FAILURE
        }
        launcher::LaunchResult::Failed(msg) => {
            eprintln!("Error: {}", msg);
            ExitCode::FAILURE
        }
    }
}

fn do_inspect(path: &std::path::Path, appimage_type: &discovery::AppImageType) -> ExitCode {
    println!("Inspecting: {}", path.display());
    println!("Type: {:?}", appimage_type);

    match inspector::inspect_appimage(path, appimage_type) {
        Ok(metadata) => {
            println!("Name: {}", metadata.app_name.as_deref().unwrap_or("(unknown)"));
            println!("Version: {}", metadata.app_version.as_deref().unwrap_or("(unknown)"));
            if let Some(ref desc) = metadata.description {
                println!("Description: {}", desc);
            }
            if let Some(ref arch) = metadata.architecture {
                println!("Architecture: {}", arch);
            }
            if !metadata.categories.is_empty() {
                println!("Categories: {}", metadata.categories.join(", "));
            }
            if let Some(ref icon) = metadata.cached_icon_path {
                println!("Icon: {}", icon.display());
            }
            if !metadata.desktop_entry_content.is_empty() {
                println!();
                println!("--- Desktop Entry ---");
                println!("{}", metadata.desktop_entry_content);
            }
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Inspection failed: {}", e);
            ExitCode::FAILURE
        }
    }
}

fn cmd_update_all(db: &Database) -> ExitCode {
    let records = match db.get_all_appimages() {
        Ok(r) => r,
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    let updatable: Vec<_> = records
        .iter()
        .filter(|r| r.latest_version.is_some())
        .collect();

    if updatable.is_empty() {
        println!("No updates available. Run 'driftwood check-updates' first.");
        return ExitCode::SUCCESS;
    }

    println!("Updating {} AppImages...", updatable.len());
    let mut success_count = 0u32;
    let mut fail_count = 0u32;

    for record in &updatable {
        let name = record.app_name.as_deref().unwrap_or(&record.filename);
        let latest = record.latest_version.as_deref().unwrap_or("?");
        print!("  {} -> {} ... ", name, latest);

        let appimage_path = std::path::Path::new(&record.path);
        match updater::perform_update(appimage_path, record.update_url.as_deref(), true, None) {
            Ok(result) => {
                println!("done ({})", result.new_path.display());
                // Store rollback path
                if let Some(ref backup) = result.old_path_backup {
                    db.set_previous_version(record.id, Some(&backup.to_string_lossy())).ok();
                }
                db.clear_update_available(record.id).ok();
                // Record in update history
                db.record_update(
                    record.id,
                    record.app_version.as_deref(),
                    Some(latest),
                    Some("cli"),
                    None,
                    true,
                ).ok();
                success_count += 1;
            }
            Err(e) => {
                println!("FAILED: {}", e);
                fail_count += 1;
            }
        }
    }

    println!();
    println!(
        "Updated {} of {} AppImages ({} failed)",
        success_count,
        updatable.len(),
        fail_count,
    );

    ExitCode::SUCCESS
}

fn cmd_autostart(db: &Database, path: &str, enable: bool, disable: bool) -> ExitCode {
    let record = match db.get_appimage_by_path(path) {
        Ok(Some(r)) => r,
        Ok(None) => {
            eprintln!("Error: '{}' is not in the database. Run 'driftwood scan' first.", path);
            return ExitCode::FAILURE;
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    let name = record.app_name.as_deref().unwrap_or(&record.filename);

    if enable {
        match integrator::enable_autostart(db, &record) {
            Ok(desktop_path) => {
                println!("Autostart enabled for {} ({})", name, desktop_path.display());
                ExitCode::SUCCESS
            }
            Err(e) => {
                eprintln!("Error: {}", e);
                ExitCode::FAILURE
            }
        }
    } else if disable {
        match integrator::disable_autostart(db, record.id) {
            Ok(()) => {
                println!("Autostart disabled for {}", name);
                ExitCode::SUCCESS
            }
            Err(e) => {
                eprintln!("Error: {}", e);
                ExitCode::FAILURE
            }
        }
    } else {
        // Show current status
        println!("{}: autostart {}", name, if record.autostart { "enabled" } else { "disabled" });
        ExitCode::SUCCESS
    }
}

fn cmd_purge(db: &Database) -> ExitCode {
    let records = match db.get_all_appimages() {
        Ok(r) => r,
        Err(e) => {
            eprintln!("Error: {}", e);
            return ExitCode::FAILURE;
        }
    };

    let mut total_mods = 0u32;
    for record in &records {
        let mods = db.get_modifications(record.id).unwrap_or_default();
        if !mods.is_empty() {
            let name = record.app_name.as_deref().unwrap_or(&record.filename);
            println!("  Undoing {} modifications for {}", mods.len(), name);
            total_mods += mods.len() as u32;
            integrator::undo_all_modifications(db, record.id).ok();
        }
    }

    if total_mods == 0 {
        println!("No system modifications to undo.");
    } else {
        println!("Removed {} system modifications.", total_mods);
    }

    ExitCode::SUCCESS
}

fn cmd_verify(path: &str, expected_sha256: Option<&str>) -> ExitCode {
    let file_path = std::path::Path::new(path);
    if !file_path.exists() {
        eprintln!("Error: file not found: {}", path);
        return ExitCode::FAILURE;
    }

    if let Some(expected) = expected_sha256 {
        // SHA256 verification
        println!("Verifying SHA256 for {}...", path);
        let status = verification::verify_sha256(file_path, expected);
        println!("  {}", status.label());
        match status {
            verification::VerificationStatus::ChecksumMatch => ExitCode::SUCCESS,
            _ => ExitCode::FAILURE,
        }
    } else {
        // Embedded signature check
        println!("Checking embedded signature for {}...", path);
        let status = verification::check_embedded_signature(file_path);
        println!("  {}", status.label());

        // Also compute and display SHA256
        println!();
        match verification::compute_sha256(file_path) {
            Ok(hash) => {
                println!("  SHA256: {}", hash);
                ExitCode::SUCCESS
            }
            Err(e) => {
                eprintln!("  SHA256 computation failed: {}", e);
                ExitCode::FAILURE
            }
        }
    }
}

// --- Export/Import library ---

fn cmd_export(db: &Database, output: Option<&str>) -> ExitCode {
    let path = output.unwrap_or("driftwood-apps.json");
    let export_path = std::path::Path::new(path);

    match backup::export_app_list(db, export_path) {
        Ok(count) => {
            eprintln!("Exported {} AppImages to {}", count, path);
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            ExitCode::FAILURE
        }
    }
}

fn cmd_import(db: &Database, file: &str) -> ExitCode {
    let import_path = std::path::Path::new(file);

    match backup::import_app_list(db, import_path) {
        Ok(result) => {
            eprintln!("Matched and merged metadata for {} apps", result.matched);
            if !result.missing.is_empty() {
                eprintln!(
                    "{} apps not found in library: {}",
                    result.missing.len(),
                    result.missing.join(", ")
                );
            }
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            ExitCode::FAILURE
        }
    }
}
