use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;

use crossterm::event::KeyEvent;

use crate::app::{App, Screen};
use crate::event::AppEvent;

pub(super) fn handle_confirm_delete(app: &mut App, key: KeyEvent) {
    // Use the central confirm-key router so the y/n/Esc contract is uniform
    // across all confirm dialogs.
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            if let Screen::ConfirmDelete { ref alias } = app.screen {
                let alias = alias.clone();
                let siblings = app.hosts_state.ssh_config.siblings_of(&alias);

                if !siblings.is_empty() {
                    // Multi-alias block: strip only the selected token.
                    // `delete_host_undoable` refuses this case (returning
                    // None) because re-inserting the whole element via
                    // `insert_host_at` cannot reverse a token strip. We
                    // therefore skip the undo stack and surface the event
                    // via a dedicated toast that names the surviving
                    // siblings, so the user knows what did and did not
                    // change on disk.
                    app.hosts_state.ssh_config.delete_host(&alias);
                    if let Err(e) = app.hosts_state.ssh_config.write() {
                        // Disk write failed: reload from disk to discard
                        // the in-memory strip so view and storage match.
                        app.notify_error(crate::messages::failed_to_save(&e));
                        app.reload_hosts();
                    } else {
                        if let Some(mut tunnel) = app.tunnels.active.remove(&alias) {
                            let _ = tunnel.child.kill();
                            let _ = tunnel.child.wait();
                        }
                        app.update_last_modified();
                        app.reload_hosts();
                        app.notify(crate::messages::siblings_stripped(&alias, siblings.len()));
                    }
                } else if let Some((element, position)) =
                    app.hosts_state.ssh_config.delete_host_undoable(&alias)
                {
                    if let Err(e) = app.hosts_state.ssh_config.write() {
                        // Restore the element on write failure
                        app.hosts_state.ssh_config.insert_host_at(element, position);
                        app.notify_error(crate::messages::failed_to_save(&e));
                    } else {
                        // Stop active tunnel for the deleted host
                        if let Some(mut tunnel) = app.tunnels.active.remove(&alias) {
                            let _ = tunnel.child.kill();
                            let _ = tunnel.child.wait();
                        }
                        // Clean up cert file if it exists. NotFound is the
                        // expected case for hosts that never had a cert. Other
                        // errors are surfaced via the status bar (never via
                        // eprintln, which would corrupt the ratatui screen).
                        let mut cert_cleanup_warning: Option<String> = None;
                        if !crate::demo_flag::is_demo() {
                            if let Ok(cert_path) = crate::vault_ssh::cert_path_for(&alias) {
                                match std::fs::remove_file(&cert_path) {
                                    Ok(()) => {}
                                    Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
                                    Err(e) => {
                                        cert_cleanup_warning =
                                            Some(crate::messages::cert_cleanup_warning(
                                                &cert_path.display(),
                                                &e,
                                            ));
                                    }
                                }
                            }
                        }
                        app.hosts_state
                            .undo_stack
                            .push(crate::app::DeletedHost { element, position });
                        if app.hosts_state.undo_stack.len() > 50 {
                            app.hosts_state.undo_stack.remove(0);
                        }
                        app.update_last_modified();
                        app.reload_hosts();
                        if let Some(warning) = cert_cleanup_warning {
                            app.notify_error(warning);
                        } else {
                            app.notify(crate::messages::goodbye_host(&alias));
                        }
                    }
                } else {
                    app.notify_warning(crate::messages::host_not_found(&alias));
                }
            }
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

pub(super) fn handle_confirm_vault_sign(
    app: &mut App,
    key: KeyEvent,
    events_tx: &mpsc::Sender<AppEvent>,
) {
    // Vault Sign is a destructive/material action: signing N certificates
    // hits Vault, may take time and is hard to reverse. Stray keys must NOT
    // cancel. use `route_confirm_key` so only y/Y/n/N/Esc are honored.
    // History: an earlier `_ => app.screen = Screen::HostList` catch-all
    // could be triggered by any keypress next to `y` (e.g. fat-fingered
    // `t` or `u`), silently aborting a bulk sign.
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            // Extract the precomputed signable list, then transition back to
            // the host list and kick off the background signing loop.
            let signable = if let Screen::ConfirmVaultSign { signable } = &app.screen {
                signable.clone()
            } else {
                return;
            };
            app.set_screen(Screen::HostList);
            start_vault_bulk_sign(app, signable, events_tx);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

/// Start the background vault bulk sign loop with fast-fail, progress, TOCTOU
/// coordination and cancellation. Stores the JoinHandle on App for clean exit.
fn start_vault_bulk_sign(
    app: &mut App,
    signable: Vec<(String, String, String, std::path::PathBuf, Option<String>)>,
    events_tx: &mpsc::Sender<AppEvent>,
) {
    let total = signable.len();
    if total == 0 {
        return;
    }
    app.notify_progress(crate::messages::vault_signing_progress(
        crate::animation::SPINNER_FRAMES[0],
        0,
        total,
        "",
    ));

    let cancel = Arc::new(AtomicBool::new(false));
    app.vault.signing_cancel = Some(cancel.clone());

    let in_flight = app.vault.sign_in_flight.clone();
    let tx = events_tx.clone();
    let spawn_result = std::thread::Builder::new()
        .name("vault-bulk-sign".into())
        .spawn(move || {
            let mut signed = 0u32;
            let mut failed = 0u32;
            let mut skipped = 0u32;
            let mut consecutive_failures = 0usize;
            let mut first_error: Option<String> = None;
            let mut aborted_message: Option<String> = None;

            for (idx, (alias, role, cert_file, pubkey, vault_addr)) in signable.iter().enumerate() {
                if cancel.load(Ordering::Relaxed) {
                    break;
                }
                let done = idx + 1;

                // TOCTOU: skip host if another thread already has it in-flight.
                // Otherwise mark it in-flight for the duration of this iteration.
                {
                    // If the mutex is poisoned a worker thread panicked while holding
                    // the lock. Recover the inner value without clearing. clearing
                    // the whole set would make every in-flight alias simultaneously
                    // eligible for re-signing, risking duplicate cert writes.
                    let mut set = match in_flight.lock() {
                        Ok(g) => g,
                        Err(p) => p.into_inner(),
                    };
                    if !set.insert(alias.clone()) {
                        skipped += 1;
                        let _ = tx.send(AppEvent::VaultSignProgress {
                            alias: alias.clone(),
                            done,
                            total,
                        });
                        continue;
                    }
                }

                let _ = tx.send(AppEvent::VaultSignProgress {
                    alias: alias.clone(),
                    done,
                    total,
                });

                let cert_path = match crate::vault_ssh::resolve_cert_path(alias, cert_file) {
                    Ok(p) => p,
                    Err(e) => {
                        failed += 1;
                        consecutive_failures += 1;
                        let scrubbed = crate::vault_ssh::scrub_vault_stderr(&e.to_string());
                        if first_error.is_none() {
                            first_error = Some(scrubbed);
                        }
                        remove_in_flight(&in_flight, alias);
                        if consecutive_failures >= 3 {
                            aborted_message = Some(crate::messages::vault_signing_aborted(
                                failed,
                                first_error.as_deref(),
                            ));
                            break;
                        }
                        continue;
                    }
                };
                let status = crate::vault_ssh::check_cert_validity(&cert_path);
                if !crate::vault_ssh::needs_renewal(&status) {
                    skipped += 1;
                    consecutive_failures = 0;
                    remove_in_flight(&in_flight, alias);
                    continue;
                }

                let sign_result =
                    crate::vault_ssh::sign_certificate(role, pubkey, alias, vault_addr.as_deref());
                // Always clean up in_flight for this alias before handling the
                // result. Using a single cleanup point (rather than per-arm)
                // prevents orphaned aliases when new control flow is added.
                remove_in_flight(&in_flight, alias);
                match sign_result {
                    Ok(_) => {
                        let _ = tx.send(AppEvent::VaultSignResult {
                            alias: alias.clone(),
                            certificate_file: cert_file.clone(),
                            success: true,
                            message: String::new(),
                        });
                        signed += 1;
                        consecutive_failures = 0;
                    }
                    Err(e) => {
                        let raw = e.to_string();
                        let scrubbed = crate::vault_ssh::scrub_vault_stderr(&raw);
                        if first_error.is_none() {
                            first_error = Some(scrubbed.clone());
                        }
                        let _ = tx.send(AppEvent::VaultSignResult {
                            alias: alias.clone(),
                            certificate_file: cert_file.clone(),
                            success: false,
                            message: scrubbed,
                        });
                        failed += 1;
                        consecutive_failures += 1;
                        if consecutive_failures >= 3 {
                            aborted_message = Some(crate::messages::vault_signing_aborted(
                                failed,
                                first_error.as_deref(),
                            ));
                            break;
                        }
                    }
                }
            }

            let cancelled = cancel.load(Ordering::Relaxed);
            let _ = tx.send(AppEvent::VaultSignAllDone {
                signed,
                failed,
                skipped,
                cancelled,
                aborted_message,
                first_error,
            });
        });
    match spawn_result {
        Ok(handle) => {
            log::info!("[purple] vault sign thread: spawned");
            app.vault.sign_thread = Some(handle);
        }
        Err(e) => {
            // Spawn failed (e.g. OS thread limit). Clear the cancel flag and
            // surface the error. otherwise the status bar is stuck at
            // "Signing 0/N" with no way for the user to recover.
            log::warn!("[purple] vault sign thread: spawn failed: {}", e);
            app.vault.signing_cancel = None;
            app.vault.sign_thread = None;
            app.notify_error(crate::messages::vault_spawn_failed(&e));
        }
    }
}

pub(super) fn remove_in_flight(
    set: &std::sync::Arc<std::sync::Mutex<std::collections::HashSet<String>>>,
    alias: &str,
) {
    // On mutex poison, recover the inner value and remove only the target alias.
    // Do NOT clear the entire set. other in-flight aliases are still owned by
    // live worker iterations and clearing them would allow duplicate signs.
    let mut guard = match set.lock() {
        Ok(g) => g,
        Err(p) => p.into_inner(),
    };
    guard.remove(alias);
}

pub(super) fn handle_confirm_host_key_reset(app: &mut App, key: KeyEvent) {
    // Host key reset wipes the host's known_hosts entry. uniform y/n/Esc
    // contract via the central router so stray keys cannot trigger it.
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            if let Screen::ConfirmHostKeyReset {
                ref alias,
                ref hostname,
                ref known_hosts_path,
                ref askpass,
            } = app.screen
            {
                let alias = alias.clone();
                let hostname = hostname.clone();
                let known_hosts_path = known_hosts_path.clone();
                let askpass = askpass.clone();

                let output = std::process::Command::new("ssh-keygen")
                    .arg("-R")
                    .arg(&hostname)
                    .arg("-f")
                    .arg(&known_hosts_path)
                    .output();

                match output {
                    Ok(result) if result.status.success() => {
                        app.notify(crate::messages::removed_host_key(&hostname));
                        if app.demo_mode {
                            app.notify_warning(crate::messages::DEMO_CONNECTION_DISABLED);
                        } else {
                            app.pending_connect = Some((alias, askpass));
                        }
                    }
                    Ok(result) => {
                        let stderr = String::from_utf8_lossy(&result.stderr);
                        app.notify_error(crate::messages::host_key_remove_failed(stderr.trim()));
                    }
                    Err(e) => {
                        app.notify_error(crate::messages::ssh_keygen_failed(&e));
                    }
                }
            }
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

/// Confirm handler for `K` (kick = restart). On Yes, queues a
/// `ContainerActionKind::Restart` request; the main loop picks it
/// up, fires the SSH command, and emits a result event. On No or
/// Esc, the screen drops without side effects.
pub(super) fn handle_confirm_container_restart(app: &mut App, key: KeyEvent) {
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            if let Screen::ConfirmContainerRestart {
                ref alias,
                ref container_id,
                ref container_name,
                ..
            } = app.screen
            {
                queue_container_action(
                    app,
                    alias.clone(),
                    container_id.clone(),
                    container_name.clone(),
                    crate::containers::ContainerAction::Restart,
                );
            }
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

/// Confirm handler for `S` (stop). Same shape as restart; the action
/// kind differs and so does the destructive wording in the dialog
/// body.
pub(super) fn handle_confirm_container_stop(app: &mut App, key: KeyEvent) {
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            if let Screen::ConfirmContainerStop {
                ref alias,
                ref container_id,
                ref container_name,
                ..
            } = app.screen
            {
                queue_container_action(
                    app,
                    alias.clone(),
                    container_id.clone(),
                    container_name.clone(),
                    crate::containers::ContainerAction::Stop,
                );
            }
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

/// Confirm handler for `Ctrl-K` (stack kick). Iterates the stored
/// member list and queues a Restart for each through the same drain
/// mechanism that powers single-container restart. The drain
/// processes one request per tick, giving a sequential cadence.
pub(super) fn handle_confirm_stack_restart(app: &mut App, key: KeyEvent) {
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            if let Screen::ConfirmStackRestart {
                ref alias,
                ref members,
                ..
            } = app.screen
            {
                let alias = alias.clone();
                let members = members.clone();
                for m in members {
                    queue_container_action(
                        app,
                        alias.clone(),
                        m.container_id,
                        m.container_name,
                        crate::containers::ContainerAction::Restart,
                    );
                }
            }
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

/// Confirm handler for `K` on a host-divider row in the containers
/// overview. Iterates every running container of the host and queues
/// a Restart, regardless of compose project. Mirrors the stack-restart
/// drain. one request per tick keeps remote SSH sane.
pub(super) fn handle_confirm_host_restart_all(app: &mut App, key: KeyEvent) {
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            if let Screen::ConfirmHostRestartAll {
                ref alias,
                ref members,
            } = app.screen
            {
                let alias = alias.clone();
                let members = members.clone();
                for m in members {
                    queue_container_action(
                        app,
                        alias.clone(),
                        m.container_id,
                        m.container_name,
                        crate::containers::ContainerAction::Restart,
                    );
                }
            }
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

/// Confirm handler for `S` on a host-divider row. Stops every running
/// container on the host. Same drain shape as host-restart.
pub(super) fn handle_confirm_host_stop_all(app: &mut App, key: KeyEvent) {
    match super::route_confirm_key(key) {
        super::ConfirmAction::Yes => {
            if let Screen::ConfirmHostStopAll {
                ref alias,
                ref members,
            } = app.screen
            {
                let alias = alias.clone();
                let members = members.clone();
                for m in members {
                    queue_container_action(
                        app,
                        alias.clone(),
                        m.container_id,
                        m.container_name,
                        crate::containers::ContainerAction::Stop,
                    );
                }
            }
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::No => {
            app.set_screen(Screen::HostList);
        }
        super::ConfirmAction::Ignored => {}
    }
}

fn queue_container_action(
    app: &mut App,
    alias: String,
    container_id: String,
    container_name: String,
    action: crate::containers::ContainerAction,
) {
    let Some(entry) = app.container_cache.get(&alias) else {
        log::debug!(
            "[purple] container_action: queue aborted, no cache for alias={}",
            alias
        );
        return;
    };
    let runtime = entry.runtime;
    let askpass = app
        .hosts_state
        .list
        .iter()
        .find(|h| h.alias == alias)
        .and_then(|h| h.askpass.clone());
    log::info!(
        "[purple] container_action queued: alias={} id={} action={:?}",
        alias,
        container_id,
        action
    );
    app.pending_container_actions
        .push_back(crate::app::ContainerActionRequest {
            alias,
            askpass,
            runtime,
            container_id,
            container_name,
            action,
        });
}
