use std::path::Path;
use std::process::{Child, Command, Stdio};

use super::database::Database;
use super::fuse::{detect_system_fuse, determine_app_fuse_status, AppImageFuseStatus};

/// Sandbox mode for running AppImages.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SandboxMode {
    None,
    Firejail,
}

impl SandboxMode {
    pub fn from_str(s: &str) -> Self {
        match s {
            "firejail" => Self::Firejail,
            _ => Self::None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::None => "none",
            Self::Firejail => "firejail",
        }
    }

    pub fn display_label(&self) -> &'static str {
        match self {
            Self::None => "None",
            Self::Firejail => "Firejail",
        }
    }
}

/// Launch method used for the AppImage.
#[derive(Debug, Clone, PartialEq)]
pub enum LaunchMethod {
    /// Direct execution via FUSE mount
    Direct,
    /// Extract-and-run fallback (APPIMAGE_EXTRACT_AND_RUN=1)
    ExtractAndRun,
    /// Via firejail sandbox
    Sandboxed,
}

impl LaunchMethod {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Direct => "direct",
            Self::ExtractAndRun => "extract_and_run",
            Self::Sandboxed => "sandboxed",
        }
    }
}

/// Result of a launch attempt.
#[derive(Debug)]
pub enum LaunchResult {
    /// Process spawned and survived the startup crash-check window.
    Started {
        pid: u32,
        method: LaunchMethod,
    },
    /// Process spawned but exited during the startup crash-check window.
    Crashed {
        exit_code: Option<i32>,
        stderr: String,
        method: LaunchMethod,
    },
    /// Failed to launch (binary not found, permission denied, etc.).
    Failed(String),
}

/// Launch an AppImage, recording the event in the database.
/// Automatically selects the best launch method based on FUSE status.
pub fn launch_appimage(
    db: &Database,
    record_id: i64,
    appimage_path: &Path,
    source: &str,
    extra_args: &[String],
    extra_env: &[(&str, &str)],
) -> LaunchResult {
    // Determine launch method based on FUSE status
    let fuse_info = detect_system_fuse();
    let fuse_status = determine_app_fuse_status(&fuse_info, appimage_path);

    let method = match fuse_status {
        AppImageFuseStatus::NativeFuse | AppImageFuseStatus::StaticRuntime => LaunchMethod::Direct,
        AppImageFuseStatus::ExtractAndRun => LaunchMethod::ExtractAndRun,
        AppImageFuseStatus::CannotLaunch => {
            return LaunchResult::Failed(
                "Cannot launch: FUSE is not available and extract-and-run is not supported".into(),
            );
        }
    };

    // Override with sandboxed launch if the user enabled firejail for this app
    let method = if has_firejail() {
        let sandbox = db
            .get_appimage_by_id(record_id)
            .ok()
            .flatten()
            .and_then(|r| r.sandbox_mode);
        if sandbox.as_deref() == Some("firejail") {
            LaunchMethod::Sandboxed
        } else {
            method
        }
    } else {
        method
    };

    // When sandboxed, ensure a profile exists and resolve its path
    let profile_path = if method == LaunchMethod::Sandboxed {
        let app_name = appimage_path
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown")
            .to_string();
        // Auto-generate a default profile if none exists yet
        if super::sandbox::profile_path_for_app(&app_name).is_none() {
            let profile = super::sandbox::generate_default_profile(&app_name);
            if let Err(e) = super::sandbox::save_profile(db, &profile) {
                log::warn!("Failed to create default sandbox profile: {}", e);
            }
        }
        super::sandbox::profile_path_for_app(&app_name)
    } else {
        None
    };

    let result = execute_appimage(appimage_path, &method, extra_args, extra_env, profile_path.as_deref());

    // Record the launch event regardless of success
    if let Err(e) = db.record_launch(record_id, source) {
        log::warn!("Failed to record launch event: {}", e);
    }

    result
}

/// Launch an AppImage without database tracking (for standalone use).
pub fn launch_appimage_simple(
    appimage_path: &Path,
    extra_args: &[String],
) -> LaunchResult {
    let fuse_info = detect_system_fuse();
    let fuse_status = determine_app_fuse_status(&fuse_info, appimage_path);

    let method = match fuse_status {
        AppImageFuseStatus::NativeFuse | AppImageFuseStatus::StaticRuntime => LaunchMethod::Direct,
        AppImageFuseStatus::ExtractAndRun => LaunchMethod::ExtractAndRun,
        AppImageFuseStatus::CannotLaunch => {
            return LaunchResult::Failed(
                "Cannot launch: FUSE is not available and this AppImage doesn't support extract-and-run".into(),
            );
        }
    };

    execute_appimage(appimage_path, &method, extra_args, &[], None)
}

/// Execute the AppImage process with the given method.
fn execute_appimage(
    appimage_path: &Path,
    method: &LaunchMethod,
    args: &[String],
    extra_env: &[(&str, &str)],
    sandbox_profile: Option<&Path>,
) -> LaunchResult {
    let mut cmd = match method {
        LaunchMethod::Direct => {
            let mut c = Command::new(appimage_path);
            c.args(args);
            c
        }
        LaunchMethod::ExtractAndRun => {
            let mut c = Command::new(appimage_path);
            c.env("APPIMAGE_EXTRACT_AND_RUN", "1");
            c.args(args);
            c
        }
        LaunchMethod::Sandboxed => {
            let mut c = Command::new("firejail");
            if let Some(profile) = sandbox_profile {
                c.arg(format!("--profile={}", profile.display()));
            }
            c.arg("--appimage");
            c.arg(appimage_path);
            c.args(args);
            c
        }
    };

    // Apply extra environment variables
    for (key, value) in extra_env {
        cmd.env(key, value);
    }

    // Detach stdin, pipe stderr so we can capture crash messages
    cmd.stdin(Stdio::null());
    cmd.stderr(Stdio::piped());

    match cmd.spawn() {
        Ok(mut child) => {
            let pid = child.id();

            // Monitor for early crash (2s window). This blocks the current
            // thread, so callers should run this inside gio::spawn_blocking.
            match check_early_crash(&mut child, std::time::Duration::from_secs(2)) {
                Some((exit_code, stderr)) => {
                    LaunchResult::Crashed {
                        exit_code,
                        stderr,
                        method: method.clone(),
                    }
                }
                None => {
                    LaunchResult::Started {
                        pid,
                        method: method.clone(),
                    }
                }
            }
        }
        Err(e) => LaunchResult::Failed(format!("Failed to start: {}", e)),
    }
}

/// Check if a recently-launched child process crashed during startup.
/// Waits up to `timeout` for the process to exit. If it exits within that window,
/// reads stderr and returns a Crashed result. If still running, drops the stderr
/// pipe (to prevent pipe buffer deadlock) and returns None.
///
/// Call this from a background thread after spawning the process.
pub fn check_early_crash(
    child: &mut Child,
    timeout: std::time::Duration,
) -> Option<(Option<i32>, String)> {
    let start = std::time::Instant::now();
    loop {
        match child.try_wait() {
            Ok(Some(status)) => {
                // Process exited - read stderr for crash details
                let stderr_text = child.stderr.take().map(|mut pipe| {
                    let mut buf = String::new();
                    use std::io::Read;
                    let mut limited = (&mut pipe).take(64 * 1024);
                    let _ = limited.read_to_string(&mut buf);
                    buf
                }).unwrap_or_default();

                return Some((status.code(), stderr_text));
            }
            Ok(None) => {
                if start.elapsed() >= timeout {
                    // Still running - drop stderr pipe to avoid deadlock
                    drop(child.stderr.take());
                    return None;
                }
                std::thread::sleep(std::time::Duration::from_millis(50));
            }
            Err(_) => return None,
        }
    }
}

/// Parse launch arguments with basic quote support.
/// Splits on whitespace, respecting double-quoted strings.
/// Returns an empty Vec if the input is None or empty.
pub fn parse_launch_args(args: Option<&str>) -> Vec<String> {
    let Some(s) = args else {
        return Vec::new();
    };
    let s = s.trim();
    if s.is_empty() {
        return Vec::new();
    }

    let mut result = Vec::new();
    let mut current = String::new();
    let mut in_quotes = false;

    for c in s.chars() {
        match c {
            '"' => in_quotes = !in_quotes,
            ' ' | '\t' if !in_quotes => {
                if !current.is_empty() {
                    result.push(std::mem::take(&mut current));
                }
            }
            _ => current.push(c),
        }
    }
    if !current.is_empty() {
        result.push(current);
    }

    result
}

/// Check if firejail is available for sandboxed launches.
pub fn has_firejail() -> bool {
    Command::new("firejail")
        .arg("--version")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

/// Get launch statistics for an AppImage from the database.
#[derive(Debug, Clone)]
pub struct LaunchStats {
    pub total_launches: u64,
    pub last_launched: Option<String>,
}

pub fn get_launch_stats(db: &Database, record_id: i64) -> LaunchStats {
    let total_launches = db.get_launch_count(record_id).unwrap_or(0) as u64;
    let last_launched = db.get_last_launched(record_id).unwrap_or(None);

    LaunchStats {
        total_launches,
        last_launched,
    }
}
