use adw::prelude::*;
use gtk::gio;
use std::path::PathBuf;
use std::rc::Rc;
use crate::config::APP_ID;
use crate::core::database::{AppImageRecord, Database};
use crate::core::updater;
use crate::i18n::{i18n, i18n_f};

/// Show an update check + apply dialog for a single AppImage.
pub fn show_update_dialog(
    parent: &impl IsA<gtk::Widget>,
    record: &AppImageRecord,
    db: &Rc<Database>,
) {
    let dialog = adw::AlertDialog::builder()
        .heading(&i18n("Check for Updates"))
        .body(&i18n_f(
            "Checking for updates for {name}...",
            &[("{name}", record.app_name.as_deref().unwrap_or(&record.filename))],
        ))
        .build();
    dialog.add_response("close", &i18n("Close"));
    dialog.set_default_response(Some("close"));
    dialog.set_close_response("close");

    let record_clone = record.clone();
    let db_ref = db.clone();
    let dialog_ref = dialog.clone();

    // Start the update check in the background
    let record_id = record.id;
    let path = record.path.clone();
    let current_version = record.app_version.clone();

    glib::spawn_future_local(async move {
        let result = gio::spawn_blocking(move || {
            let appimage_path = std::path::Path::new(&path);
            updater::check_appimage_for_update(
                appimage_path,
                current_version.as_deref(),
            )
        })
        .await;

        match result {
            Ok((type_label, raw_info, Some(check_result))) => {
                // Store update info in DB
                db_ref
                    .update_update_info(
                        record_id,
                        raw_info.as_deref(),
                        type_label.as_deref(),
                    )
                    .ok();

                if check_result.update_available {
                    if let Some(ref version) = check_result.latest_version {
                        db_ref.set_update_available(record_id, Some(version), check_result.download_url.as_deref()).ok();
                    }

                    let mut body = i18n_f(
                        "{current} -> {latest}",
                        &[
                            ("{current}", record_clone.app_version.as_deref().unwrap_or("unknown")),
                            ("{latest}", check_result.latest_version.as_deref().unwrap_or("unknown")),
                        ],
                    );
                    if let Some(size) = check_result.file_size {
                        body.push_str(&format!(" ({})", humansize::format_size(size, humansize::BINARY)));
                    }
                    body.push_str(&format!("\n\n{}", i18n("A new version is available.")));
                    if let Some(ref notes) = check_result.release_notes {
                        if !notes.is_empty() {
                            // Truncate long release notes for the dialog
                            let truncated: String = notes.chars().take(300).collect();
                            let suffix = if truncated.len() < notes.len() { "..." } else { "" };
                            body.push_str(&format!("\n\n{}{}", truncated, suffix));
                        }
                    }
                    dialog_ref.set_heading(Some(&i18n("Update Available")));
                    dialog_ref.set_body(&body);

                    // Add "Update Now" button if we have a download URL
                    if let Some(download_url) = check_result.download_url {
                        dialog_ref.add_response("update", &i18n("Update Now"));
                        dialog_ref.set_response_appearance("update", adw::ResponseAppearance::Suggested);
                        dialog_ref.set_default_response(Some("update"));

                        let db_update = db_ref.clone();
                        let record_path = record_clone.path.clone();
                        let new_version = check_result.latest_version.clone();
                        dialog_ref.connect_response(Some("update"), move |dlg, _response| {
                            start_update(
                                dlg,
                                &record_path,
                                &download_url,
                                record_id,
                                new_version.as_deref(),
                                &db_update,
                            );
                        });
                    }
                } else {
                    dialog_ref.set_heading(Some(&i18n("Up to Date")));
                    dialog_ref.set_body(&i18n_f(
                        "{name} is already at the latest version ({version}).",
                        &[
                            ("{name}", record_clone.app_name.as_deref().unwrap_or(&record_clone.filename)),
                            ("{version}", record_clone.app_version.as_deref().unwrap_or("unknown")),
                        ],
                    ));
                    db_ref.clear_update_available(record_id).ok();
                }
            }
            Ok((type_label, raw_info, None)) => {
                if raw_info.is_some() {
                    db_ref.update_update_info(record_id, raw_info.as_deref(), type_label.as_deref()).ok();
                    dialog_ref.set_heading(Some(&i18n("Check Failed")));
                    dialog_ref.set_body(&i18n("Could not reach the update server. Try again later."));
                } else {
                    dialog_ref.set_heading(Some(&i18n("No Update Info")));
                    dialog_ref.set_body(
                        &i18n("This app does not support automatic updates. Check the developer's website for newer versions."),
                    );
                }
            }
            Err(_) => {
                dialog_ref.set_heading(Some(&i18n("Error")));
                dialog_ref.set_body(&i18n("An error occurred while checking for updates."));
            }
        }
    });

    dialog.present(Some(parent));
}

/// Start the actual update download + apply process.
fn start_update(
    dialog: &adw::AlertDialog,
    appimage_path: &str,
    download_url: &str,
    record_id: i64,
    new_version: Option<&str>,
    db: &Rc<Database>,
) {
    dialog.set_heading(Some(&i18n("Updating...")));
    dialog.set_body(&i18n("Downloading update. This may take a moment."));
    dialog.set_response_enabled("update", false);

    let path = appimage_path.to_string();
    let url = download_url.to_string();
    let version = new_version.map(|s| s.to_string());
    let db_ref = db.clone();
    let dialog_weak = dialog.downgrade();

    glib::spawn_future_local(async move {
        let result = gio::spawn_blocking(move || {
            let p = std::path::Path::new(&path);
            // Always keep old version initially - cleanup decision happens after
            updater::perform_update(p, Some(&url), true, None)
        })
        .await;

        let Some(dialog) = dialog_weak.upgrade() else { return };

        match result {
            Ok(Ok(applied)) => {
                // Record the update in history using the actual new version
                let actual_version = applied.new_version.as_deref().or(version.as_deref());
                if let Some(ver) = actual_version {
                    db_ref.record_update(record_id, None, Some(ver), Some("download"), None, true).ok();
                }
                db_ref.clear_update_available(record_id).ok();

                let success_body = i18n_f(
                    "Updated to {version}\nPath: {path}",
                    &[
                        ("{version}", applied.new_version.as_deref().unwrap_or("latest")),
                        ("{path}", &applied.new_path.display().to_string()),
                    ],
                );
                dialog.set_heading(Some(&i18n("Update Complete")));
                dialog.set_body(&success_body);
                dialog.set_response_enabled("update", false);

                // Save previous version path for rollback
                if let Some(ref old_path) = applied.old_path_backup {
                    db_ref.set_previous_version(
                        record_id,
                        Some(&old_path.to_string_lossy()),
                    ).ok();
                }

                // Handle old version cleanup
                if let Some(old_path) = applied.old_path_backup {
                    handle_old_version_cleanup(&dialog, old_path);
                }
            }
            Ok(Err(e)) => {
                dialog.set_heading(Some(&i18n("Update Failed")));
                dialog.set_body(&i18n_f(
                    "The update could not be applied: {error}",
                    &[("{error}", &e.to_string())],
                ));
            }
            Err(_) => {
                dialog.set_heading(Some(&i18n("Update Failed")));
                dialog.set_body(&i18n("An unexpected error occurred during the update."));
            }
        }
    });
}

/// After a successful update, handle cleanup of the old version based on user preference.
fn handle_old_version_cleanup(dialog: &adw::AlertDialog, old_path: PathBuf) {
    let settings = gio::Settings::new(APP_ID);
    let policy = settings.string("update-cleanup");

    match policy.as_str() {
        "always" => {
            // Auto-remove without asking
            if old_path.exists() {
                if let Err(e) = std::fs::remove_file(&old_path) {
                    log::warn!("Failed to remove old version {}: {}", old_path.display(), e);
                } else {
                    log::info!("Auto-removed old version: {}", old_path.display());
                }
            }
        }
        "never" => {
            // Keep the backup, just inform
            dialog.set_body(&i18n_f(
                "Update complete. The old version is saved at:\n{path}",
                &[("{path}", &old_path.display().to_string())],
            ));
        }
        _ => {
            // "ask" - prompt the user
            dialog.set_body(&i18n_f(
                "Update complete.\n\nRemove the old version?\n{path}",
                &[("{path}", &old_path.display().to_string())],
            ));
            dialog.add_response("remove-old", &i18n("Remove Old Version"));
            dialog.set_response_appearance("remove-old", adw::ResponseAppearance::Destructive);

            let path = old_path.clone();
            dialog.connect_response(Some("remove-old"), move |_dlg, _response| {
                if path.exists() {
                    std::fs::remove_file(&path).ok();
                }
            });
        }
    }
}

/// Batch check all AppImages for updates. Returns count of updates found.
/// Check all apps for updates, returns (count, list of app names with updates).
pub fn batch_check_updates_detailed(db: &Database) -> (u32, Vec<String>) {
    let records = match db.get_all_appimages() {
        Ok(r) => r,
        Err(e) => {
            log::error!("Failed to get appimages for update check: {}", e);
            return (0, vec![]);
        }
    };

    let mut updates_found = 0u32;
    let mut updated_names = Vec::new();

    for record in &records {
        if record.pinned {
            continue;
        }
        let appimage_path = std::path::Path::new(&record.path);
        if !appimage_path.exists() {
            continue;
        }

        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();
        }

        if let Some(result) = check_result {
            if result.update_available {
                if let Some(ref version) = result.latest_version {
                    db.set_update_available(record.id, Some(version), result.download_url.as_deref()).ok();
                    updates_found += 1;
                    let name = record.app_name.as_deref().unwrap_or(&record.filename);
                    updated_names.push(name.to_string());
                }
            } else {
                db.clear_update_available(record.id).ok();
            }
        }
    }

    (updates_found, updated_names)
}

pub fn batch_check_updates(db: &Database) -> u32 {
    batch_check_updates_detailed(db).0
}
