use adw::prelude::*;
use gtk::gio;
use std::path::PathBuf;
use std::rc::Rc;

use crate::core::analysis;
use crate::core::database::Database;
use crate::core::discovery;
use crate::core::inspector;
use crate::i18n::{i18n, ni18n_f};
use super::widgets;

/// Registered file info returned by the fast registration phase.
struct RegisteredFile {
    id: i64,
    path: PathBuf,
    appimage_type: discovery::AppImageType,
}

/// Show a dialog offering to add dropped AppImage files to the library.
///
/// `files` should already be validated as AppImages (magic bytes checked).
/// `toast_overlay` is used to show result toasts.
/// `on_complete` is called after files are registered to refresh the UI.
pub fn show_drop_dialog(
    parent: &impl IsA<gtk::Widget>,
    files: Vec<PathBuf>,
    toast_overlay: &adw::ToastOverlay,
    on_complete: impl Fn() + 'static,
) {
    if files.is_empty() {
        return;
    }

    let count = files.len();

    // Build heading and body
    let heading = if count == 1 {
        let name = files[0]
            .file_name()
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_else(|| "AppImage".to_string());
        crate::i18n::i18n_f("Add {name}?", &[("{name}", &name)])
    } else {
        ni18n_f(
            "Add {} app?",
            "Add {} apps?",
            count as u32,
            &[("{}", &count.to_string())],
        )
    };

    let body = if count == 1 {
        let path_str = files[0].to_string_lossy().to_string();
        let size = std::fs::metadata(&files[0]).map(|m| m.len()).unwrap_or(0);
        if size > 0 {
            format!("{}\n({})", path_str, super::widgets::format_size(size as i64))
        } else {
            path_str
        }
    } else {
        files
            .iter()
            .filter_map(|f| f.file_name().map(|n| n.to_string_lossy().into_owned()))
            .collect::<Vec<_>>()
            .join("\n")
    };

    let dialog = adw::AlertDialog::builder()
        .heading(&heading)
        .body(&body)
        .build();

    // For single file, try fast icon extraction for a preview
    if count == 1 {
        let file_path = files[0].clone();
        let dialog_ref = dialog.clone();
        glib::spawn_future_local(async move {
            let result = gio::spawn_blocking(move || {
                inspector::extract_icon_fast(&file_path)
            })
            .await;

            if let Ok(Some(icon_path)) = result {
                let image = gtk::Image::builder()
                    .file(icon_path.to_string_lossy().as_ref())
                    .pixel_size(64)
                    .halign(gtk::Align::Center)
                    .margin_top(12)
                    .build();
                image.update_property(&[gtk::accessible::Property::Label("App icon preview")]);
                dialog_ref.set_extra_child(Some(&image));
            }
        });
    }

    dialog.add_response("cancel", &i18n("Cancel"));
    dialog.add_response("keep-in-place", &i18n("Run Portable"));
    dialog.add_response("copy-only", &i18n("Copy to Apps"));
    dialog.add_response("copy-and-integrate", &i18n("Copy & Add to Launcher"));

    dialog.set_response_appearance("copy-and-integrate", adw::ResponseAppearance::Suggested);
    dialog.set_default_response(Some("copy-and-integrate"));
    dialog.set_close_response("cancel");

    let toast_ref = toast_overlay.clone();
    let on_complete = Rc::new(on_complete);
    dialog.connect_response(None, move |_dialog, response| {
        if response == "cancel" {
            return;
        }

        let copy = response != "keep-in-place";
        let integrate = response == "copy-and-integrate";
        let files = files.clone();
        let toast_ref = toast_ref.clone();
        let on_complete_ref = on_complete.clone();

        glib::spawn_future_local(async move {
            // Phase 1: Fast registration (copy + DB upsert only)
            let result = gio::spawn_blocking(move || {
                register_dropped_files(&files, copy)
            })
            .await;

            match result {
                Ok(Ok(registered)) => {
                    let added = registered.len();

                    // Refresh UI immediately - apps appear with "Analyzing..." badge
                    on_complete_ref();

                    // Show toast
                    if added == 1 {
                        toast_ref.add_toast(widgets::info_toast(&i18n("Added to your apps")));
                    } else if added > 0 {
                        let msg = ni18n_f(
                            "Added {} app",
                            "Added {} apps",
                            added as u32,
                            &[("{}", &added.to_string())],
                        );
                        toast_ref.add_toast(widgets::info_toast(&msg));
                    }

                    // Phase 2: Background analysis for each file
                    let on_complete_bg = on_complete_ref.clone();
                    for reg in registered {
                        let on_complete_inner = on_complete_bg.clone();
                        glib::spawn_future_local(async move {
                            let _ = gio::spawn_blocking(move || {
                                analysis::run_background_analysis(
                                    reg.id,
                                    reg.path,
                                    reg.appimage_type,
                                    integrate,
                                );
                            })
                            .await;

                            // Refresh UI when each analysis completes
                            on_complete_inner();
                        });
                    }
                }
                Ok(Err(e)) => {
                    log::error!("Drop processing failed: {}", e);
                    toast_ref.add_toast(widgets::error_toast(&i18n("Failed to add app")));
                }
                Err(e) => {
                    log::error!("Drop task failed: {:?}", e);
                    toast_ref.add_toast(widgets::error_toast(&i18n("Failed to add app")));
                }
            }
        });
    });

    dialog.present(Some(parent));
}

/// Fast registration of dropped files - copies to target dir and inserts into DB.
/// Returns a list of registered files for background analysis.
fn register_dropped_files(
    files: &[PathBuf],
    copy_to_target: bool,
) -> Result<Vec<RegisteredFile>, String> {
    let db = Database::open().map_err(|e| format!("Failed to open database: {}", e))?;

    let settings = gio::Settings::new(crate::config::APP_ID);
    let scan_dirs: Vec<String> = settings
        .strv("scan-directories")
        .iter()
        .map(|s| s.to_string())
        .collect();

    // Target directory: first scan directory (default ~/Applications)
    let target_dir = scan_dirs
        .first()
        .map(|d| discovery::expand_tilde(d))
        .unwrap_or_else(|| {
            dirs::home_dir()
                .unwrap_or_else(|| PathBuf::from("/tmp"))
                .join("Applications")
        });

    // Ensure target directory exists
    std::fs::create_dir_all(&target_dir)
        .map_err(|e| format!("Failed to create {}: {}", target_dir.display(), e))?;

    // Expand scan dirs for checking if file is already in a scan location
    let expanded_scan_dirs: Vec<PathBuf> = scan_dirs
        .iter()
        .map(|d| discovery::expand_tilde(d))
        .collect();

    let mut registered = Vec::new();

    for file in files {
        // Determine if the file is already in a scan directory
        let in_scan_dir = expanded_scan_dirs.iter().any(|scan_dir| {
            file.parent()
                .and_then(|p| p.canonicalize().ok())
                .and_then(|parent| {
                    scan_dir.canonicalize().ok().map(|sd| parent == sd)
                })
                .unwrap_or(false)
        });

        let final_path = if in_scan_dir || !copy_to_target {
            // Keep the file where it is; just ensure it's executable
            #[cfg(unix)]
            {
                use std::os::unix::fs::PermissionsExt;
                if let Ok(meta) = std::fs::metadata(file) {
                    if meta.permissions().mode() & 0o111 == 0 {
                        let perms = std::fs::Permissions::from_mode(0o755);
                        if let Err(e) = std::fs::set_permissions(file, perms) {
                            log::warn!("Failed to set executable on {}: {}", file.display(), e);
                        }
                    }
                }
            }
            file.clone()
        } else {
            let filename = file
                .file_name()
                .ok_or_else(|| "No filename".to_string())?;
            let dest = target_dir.join(filename);

            // Don't overwrite existing files - generate a unique name
            let dest = if dest.exists() && dest != *file {
                let stem = dest
                    .file_stem()
                    .and_then(|s| s.to_str())
                    .unwrap_or("app");
                let ext = dest
                    .extension()
                    .and_then(|e| e.to_str())
                    .unwrap_or("AppImage");
                let mut counter = 1;
                loop {
                    let candidate = target_dir.join(format!("{}-{}.{}", stem, counter, ext));
                    if !candidate.exists() {
                        break candidate;
                    }
                    counter += 1;
                }
            } else {
                dest
            };

            std::fs::copy(file, &dest)
                .map_err(|e| format!("Failed to copy {}: {}", file.display(), e))?;

            // Make executable
            #[cfg(unix)]
            {
                use std::os::unix::fs::PermissionsExt;
                let perms = std::fs::Permissions::from_mode(0o755);
                if let Err(e) = std::fs::set_permissions(&dest, perms) {
                    log::warn!("Failed to set executable permissions on {}: {}", dest.display(), e);
                }
            }

            dest
        };

        // Validate it's actually an AppImage
        let appimage_type = match discovery::detect_appimage(&final_path) {
            Some(t) => t,
            None => {
                log::warn!("Not a valid AppImage: {}", final_path.display());
                continue;
            }
        };

        let filename = final_path
            .file_name()
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_default();

        let metadata = std::fs::metadata(&final_path).ok();
        let size_bytes = metadata.as_ref().map(|m| m.len() as i64).unwrap_or(0);
        let modified = metadata
            .as_ref()
            .and_then(|m| m.modified().ok())
            .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 is_executable = {
            #[cfg(unix)]
            {
                use std::os::unix::fs::PermissionsExt;
                metadata
                    .as_ref()
                    .map(|m| m.permissions().mode() & 0o111 != 0)
                    .unwrap_or(false)
            }
            #[cfg(not(unix))]
            {
                true
            }
        };

        // Register in database with pending analysis status
        let id = db
            .upsert_appimage(
                &final_path.to_string_lossy(),
                &filename,
                Some(appimage_type.as_i32()),
                size_bytes,
                is_executable,
                modified.as_deref(),
            )
            .map_err(|e| format!("Database error: {}", e))?;

        if let Err(e) = db.update_analysis_status(id, "pending") {
            log::warn!("Failed to set analysis status to 'pending' for id {}: {}", id, e);
        }

        registered.push(RegisteredFile {
            id,
            path: final_path,
            appimage_type,
        });
    }

    Ok(registered)
}
