use adw::prelude::*;
use std::path::Path;
use std::rc::Rc;

use gtk::gio;

use crate::config::APP_ID;
use crate::core::database::Database;
use crate::core::updater;
use crate::i18n::{i18n, ni18n_f};
use crate::ui::update_dialog;
use crate::ui::widgets;

/// Build the Updates top-level view.
/// Returns a ToolbarView that can be added to the ViewStack.
pub fn build_updates_view(db: &Rc<Database>) -> adw::ToolbarView {
    let toast_overlay = adw::ToastOverlay::new();

    let header = adw::HeaderBar::new();
    let title = adw::WindowTitle::builder()
        .title(&i18n("Updates"))
        .build();
    header.set_title_widget(Some(&title));

    // Check Now button
    let check_btn = widgets::accessible_icon_button(
        "view-refresh-symbolic",
        "Check for updates",
        &i18n("Check for updates (Ctrl+U)"),
    );
    header.pack_end(&check_btn);

    // Update All button (only visible when updates exist)
    let update_all_btn = gtk::Button::builder()
        .label(&i18n("Update All"))
        .css_classes(["suggested-action", "pill"])
        .visible(false)
        .build();
    header.pack_start(&update_all_btn);

    // Content stack: empty state vs checking vs update list
    let stack = gtk::Stack::new();

    // Empty state - all up to date
    let empty_page = adw::StatusPage::builder()
        .icon_name("emblem-ok-symbolic")
        .title(&i18n("Everything is Up to Date"))
        .description(&i18n("All your AppImages are running the latest version"))
        .build();
    stack.add_named(&empty_page, Some("empty"));

    // Checking state
    let checking_page = adw::StatusPage::builder()
        .icon_name("emblem-synchronizing-symbolic")
        .title(&i18n("Checking for Updates"))
        .description(&i18n("Looking for new versions of your AppImages..."))
        .build();
    let spinner = gtk::Spinner::builder()
        .spinning(true)
        .width_request(32)
        .height_request(32)
        .halign(gtk::Align::Center)
        .build();
    spinner.update_property(&[gtk::accessible::Property::Label("Checking for updates")]);
    checking_page.set_child(Some(&spinner));
    stack.add_named(&checking_page, Some("checking"));

    // Update list
    let list_box = gtk::ListBox::builder()
        .selection_mode(gtk::SelectionMode::None)
        .css_classes(["boxed-list"])
        .margin_start(18)
        .margin_end(18)
        .margin_top(12)
        .margin_bottom(24)
        .build();

    let last_checked_label = gtk::Label::builder()
        .css_classes(["dim-label", "caption"])
        .halign(gtk::Align::Start)
        .margin_start(18)
        .margin_top(6)
        .build();

    let updates_content = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(6)
        .build();
    updates_content.append(&last_checked_label);

    // "What will happen" explanation
    let explanation = gtk::Label::builder()
        .label(&i18n("Each app will be downloaded fresh. Your settings and data are kept. You can choose to keep old versions as backup in Preferences."))
        .css_classes(["caption", "dim-label"])
        .wrap(true)
        .xalign(0.0)
        .margin_start(18)
        .margin_end(18)
        .margin_top(6)
        .build();
    updates_content.append(&explanation);

    let clamp = adw::Clamp::builder()
        .maximum_size(800)
        .tightening_threshold(600)
        .child(&list_box)
        .build();
    updates_content.append(&clamp);

    let scrolled = gtk::ScrolledWindow::builder()
        .child(&updates_content)
        .vexpand(true)
        .build();
    stack.add_named(&scrolled, Some("updates"));

    toast_overlay.set_child(Some(&stack));

    // Shared state for refresh
    let state = Rc::new(UpdatesState {
        db: db.clone(),
        list_box: list_box.clone(),
        stack: stack.clone(),
        update_all_btn: update_all_btn.clone(),
        title: title.clone(),
        last_checked_label: last_checked_label.clone(),
        toast_overlay: toast_overlay.clone(),
    });

    // Initial population
    populate_update_list(&state);

    // Check Now handler
    {
        let state_ref = state.clone();
        check_btn.connect_clicked(move |btn| {
            btn.set_sensitive(false);
            state_ref.stack.set_visible_child_name("checking");

            let state_c = state_ref.clone();
            let btn_c = btn.clone();

            glib::spawn_future_local(async move {
                let db_bg = Database::open().ok();
                let _result = gio::spawn_blocking(move || {
                    if let Some(ref db) = db_bg {
                        update_dialog::batch_check_updates(db);
                    }
                }).await;

                btn_c.set_sensitive(true);

                // Update last-checked timestamp
                let settings = gio::Settings::new(APP_ID);
                let now = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
                settings.set_string("last-update-check", &now).ok();

                // Reload from main DB
                let fresh_db = state_c.db.clone();
                // Re-read records from the shared db so UI picks up changes from the bg thread
                drop(fresh_db);
                populate_update_list(&state_c);
                state_c.toast_overlay.add_toast(widgets::info_toast(&i18n("Update check complete")));
            });
        });
    }

    // Update All handler
    {
        let state_ref = state.clone();
        update_all_btn.connect_clicked(move |btn| {
            btn.set_sensitive(false);
            let state_c = state_ref.clone();
            let btn_c = btn.clone();

            glib::spawn_future_local(async move {
                let db_bg = Database::open().ok();
                let result = gio::spawn_blocking(move || {
                    let mut updated = 0u32;
                    if let Some(ref db) = db_bg {
                        let updatable = db.get_appimages_with_updates().unwrap_or_default();
                        for rec in &updatable {
                            let path = Path::new(&rec.path);
                            if path.exists() {
                                if updater::perform_update(
                                    path,
                                    rec.update_url.as_deref(),
                                    true,
                                    None,
                                ).is_ok() {
                                    db.clear_update_available(rec.id).ok();
                                    updated += 1;
                                }
                            }
                        }
                    }
                    updated
                }).await;

                btn_c.set_sensitive(true);
                let count = result.unwrap_or(0);
                if count > 0 {
                    state_c.toast_overlay.add_toast(
                        widgets::info_toast(&ni18n_f("{} app updated", "{} apps updated", count, &[("{}", &count.to_string())])),
                    );
                }
                populate_update_list(&state_c);
            });
        });
    }

    let toolbar_view = adw::ToolbarView::new();
    toolbar_view.add_top_bar(&header);
    toolbar_view.set_content(Some(&toast_overlay));
    widgets::apply_pointer_cursors(&toolbar_view);

    toolbar_view
}

struct UpdatesState {
    db: Rc<Database>,
    list_box: gtk::ListBox,
    stack: gtk::Stack,
    update_all_btn: gtk::Button,
    title: adw::WindowTitle,
    last_checked_label: gtk::Label,
    toast_overlay: adw::ToastOverlay,
}

fn populate_update_list(state: &Rc<UpdatesState>) {
    // Clear existing rows
    while let Some(row) = state.list_box.row_at_index(0) {
        state.list_box.remove(&row);
    }

    // Re-read from database to pick up changes from background threads
    let db_fresh = Database::open().ok();
    let updatable = db_fresh
        .as_ref()
        .and_then(|db| db.get_appimages_with_updates().ok())
        .unwrap_or_default();

    // Update last checked label
    let settings = gio::Settings::new(APP_ID);
    let last_check = settings.string("last-update-check");
    if last_check.is_empty() || last_check.as_str() == "never" {
        state.last_checked_label.set_label(&i18n("Never checked"));
    } else {
        state.last_checked_label.set_label(
            &format!("Last checked: {}", widgets::relative_time(&last_check)),
        );
    }

    if updatable.is_empty() {
        state.stack.set_visible_child_name("empty");
        state.update_all_btn.set_visible(false);
        state.title.set_subtitle(&i18n("All up to date"));
        return;
    }

    state.stack.set_visible_child_name("updates");
    state.update_all_btn.set_visible(true);
    let count = updatable.len();
    state.title.set_subtitle(&format!("{} updates available", count));
    widgets::announce(state.list_box.upcast_ref::<gtk::Widget>(), &format!("{} updates available", count));

    for record in &updatable {
        let name = record.app_name.as_deref().unwrap_or(&record.filename);

        // Show version info: current -> latest (with size if available)
        let current = record.app_version.as_deref().unwrap_or("unknown");
        let latest = record.latest_version.as_deref().unwrap_or("unknown");
        let subtitle = if record.size_bytes > 0 {
            format!("{} -> {} ({})", current, latest, widgets::format_size(record.size_bytes))
        } else {
            format!("{} -> {}", current, latest)
        };

        // Try to find changelog from the app's own release_history or from catalog
        let changelog = find_changelog_for_version(
            &state.db, record, latest,
        );

        // Use ExpanderRow if we have changelog, otherwise plain ActionRow
        let row: adw::ExpanderRow = adw::ExpanderRow::builder()
            .title(name)
            .subtitle(&subtitle)
            .show_enable_switch(false)
            .expanded(false)
            .build();

        // App icon (decorative - row title already names the app)
        let icon = widgets::app_icon(record.icon_path.as_deref(), name, 32);
        icon.set_accessible_role(gtk::AccessibleRole::Presentation);
        row.add_prefix(&icon);

        // "What's new" content inside the expander
        let changelog_text = match &changelog {
            Some(text) => text.clone(),
            None => i18n("Release notes not available"),
        };
        let changelog_label = gtk::Label::builder()
            .label(&changelog_text)
            .wrap(true)
            .xalign(0.0)
            .css_classes(if changelog.is_some() { vec!["body"] } else { vec!["dim-label", "caption"] })
            .margin_start(12)
            .margin_end(12)
            .margin_top(8)
            .margin_bottom(8)
            .selectable(true)
            .build();
        let changelog_row = adw::ActionRow::builder()
            .activatable(false)
            .child(&changelog_label)
            .build();
        row.add_row(&changelog_row);

        // Individual update button
        let update_btn = gtk::Button::builder()
            .icon_name("software-update-available-symbolic")
            .valign(gtk::Align::Center)
            .tooltip_text(&i18n("Update this app"))
            .css_classes(["flat"])
            .build();
        update_btn.update_property(&[gtk::accessible::Property::Label(&format!("Update {}", name))]);

        let app_id = record.id;
        let update_url = record.update_url.clone();
        let app_path = record.path.clone();
        let state_ref = state.clone();
        update_btn.connect_clicked(move |btn| {
            btn.set_sensitive(false);
            let state_c = state_ref.clone();
            let path = app_path.clone();
            let url = update_url.clone();
            let btn_c = btn.clone();
            let aid = app_id;

            glib::spawn_future_local(async move {
                let db_bg = Database::open().ok();
                let result = gio::spawn_blocking(move || {
                    let p = Path::new(&path);
                    if p.exists() {
                        let ok = updater::perform_update(
                            p,
                            url.as_deref(),
                            true,
                            None,
                        ).is_ok();
                        if ok {
                            if let Some(ref db) = db_bg {
                                db.clear_update_available(aid).ok();
                            }
                        }
                        ok
                    } else {
                        false
                    }
                }).await;

                btn_c.set_sensitive(true);
                if result.unwrap_or(false) {
                    state_c.toast_overlay.add_toast(
                        widgets::info_toast(&i18n("Update complete")),
                    );
                } else {
                    state_c.toast_overlay.add_toast(
                        widgets::error_toast(&i18n("Update failed")),
                    );
                }
                populate_update_list(&state_c);
            });
        });

        row.add_suffix(&update_btn);
        state.list_box.append(&row);
    }
}

/// Try to find changelog/release notes for a specific version.
/// Checks the installed app's release_history first, then falls back
/// to the catalog app's release_history (populated by GitHub enrichment).
fn find_changelog_for_version(
    db: &Database,
    record: &crate::core::database::AppImageRecord,
    target_version: &str,
) -> Option<String> {
    // First check the installed app's own release_history
    if let Some(ref history_json) = record.release_history {
        if let Some(text) = extract_version_notes(history_json, target_version) {
            return Some(text);
        }
    }

    // Fall back to catalog app's release_history
    let app_name = record.app_name.as_deref().unwrap_or(&record.filename);
    if let Ok(Some(ref history_json)) = db.get_catalog_release_history_by_name(app_name) {
        if let Some(text) = extract_version_notes(history_json, target_version) {
            return Some(text);
        }
    }

    None
}

/// Parse release_history JSON and extract the description for a given version.
/// Format: [{"version": "1.0.0", "date": "2026-01-01", "description": "..."}]
fn extract_version_notes(history_json: &str, target_version: &str) -> Option<String> {
    let releases: Vec<serde_json::Value> = serde_json::from_str(history_json).ok()?;

    // Try exact match first
    for release in &releases {
        if let Some(ver) = release.get("version").and_then(|v| v.as_str()) {
            if ver == target_version {
                return release.get("description")
                    .and_then(|d| d.as_str())
                    .map(|s| truncate_changelog(s));
            }
        }
    }

    // If no exact match, try matching without "v" prefix on both sides
    let clean_target = target_version.strip_prefix('v').unwrap_or(target_version);
    for release in &releases {
        if let Some(ver) = release.get("version").and_then(|v| v.as_str()) {
            let clean_ver = ver.strip_prefix('v').unwrap_or(ver);
            if clean_ver == clean_target {
                return release.get("description")
                    .and_then(|d| d.as_str())
                    .map(|s| truncate_changelog(s));
            }
        }
    }

    // No matching version found - show the latest entry's notes as a fallback
    // (the first entry is typically the newest release)
    if let Some(first) = releases.first() {
        if let Some(desc) = first.get("description").and_then(|d| d.as_str()) {
            let ver = first.get("version").and_then(|v| v.as_str()).unwrap_or("?");
            return Some(format!("(v{}) {}", ver, truncate_changelog(desc)));
        }
    }

    None
}

/// Truncate long changelog text to keep the UI compact.
fn truncate_changelog(text: &str) -> String {
    let max_len = 500;
    let trimmed = text.trim();
    if trimmed.len() <= max_len {
        trimmed.to_string()
    } else {
        format!("{}...", &trimmed[..max_len])
    }
}
