use std::path::Path;
use std::process::Command;

#[derive(Debug, Clone, PartialEq)]
pub enum FuseStatus {
    /// libfuse2 available, fusermount present, /dev/fuse exists - fully working
    FullyFunctional,
    /// Only libfuse3 installed - most AppImages won't mount natively
    Fuse3Only,
    /// fusermount binary not found
    NoFusermount,
    /// /dev/fuse device not present (container or WSL)
    NoDevFuse,
    /// libfuse2 not installed
    MissingLibfuse2,
}

impl FuseStatus {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::FullyFunctional => "fully_functional",
            Self::Fuse3Only => "fuse3_only",
            Self::NoFusermount => "no_fusermount",
            Self::NoDevFuse => "no_dev_fuse",
            Self::MissingLibfuse2 => "missing_libfuse2",
        }
    }

    pub fn from_str(s: &str) -> Self {
        match s {
            "fully_functional" => Self::FullyFunctional,
            "fuse3_only" => Self::Fuse3Only,
            "no_fusermount" => Self::NoFusermount,
            "no_dev_fuse" => Self::NoDevFuse,
            _ => Self::MissingLibfuse2,
        }
    }

    pub fn label(&self) -> &'static str {
        match self {
            Self::FullyFunctional => "OK",
            Self::Fuse3Only => "FUSE3 only",
            Self::NoFusermount => "No fusermount",
            Self::NoDevFuse => "No /dev/fuse",
            Self::MissingLibfuse2 => "No libfuse2",
        }
    }

    pub fn badge_class(&self) -> &'static str {
        match self {
            Self::FullyFunctional => "success",
            Self::Fuse3Only => "warning",
            Self::NoFusermount | Self::NoDevFuse | Self::MissingLibfuse2 => "error",
        }
    }

    pub fn is_functional(&self) -> bool {
        matches!(self, Self::FullyFunctional)
    }
}

#[derive(Debug, Clone)]
pub struct FuseSystemInfo {
    pub status: FuseStatus,
    pub has_libfuse2: bool,
    pub has_libfuse3: bool,
    pub has_fusermount: bool,
    pub fusermount_path: Option<String>,
    pub has_dev_fuse: bool,
    pub install_hint: Option<String>,
}

/// Detect the system FUSE status by checking for libraries, binaries, and device nodes.
pub fn detect_system_fuse() -> FuseSystemInfo {
    let has_libfuse2 = check_library("libfuse.so.2");
    let has_libfuse3 = check_library("libfuse3.so.3");
    let fusermount_path = find_fusermount();
    let has_fusermount = fusermount_path.is_some();
    let has_dev_fuse = Path::new("/dev/fuse").exists();

    let status = if has_libfuse2 && has_fusermount && has_dev_fuse {
        FuseStatus::FullyFunctional
    } else if !has_dev_fuse {
        FuseStatus::NoDevFuse
    } else if !has_fusermount {
        FuseStatus::NoFusermount
    } else if has_libfuse3 && !has_libfuse2 {
        FuseStatus::Fuse3Only
    } else {
        FuseStatus::MissingLibfuse2
    };

    let install_hint = if status.is_functional() {
        None
    } else {
        Some(get_install_hint())
    };

    FuseSystemInfo {
        status,
        has_libfuse2,
        has_libfuse3,
        has_fusermount,
        fusermount_path,
        has_dev_fuse,
        install_hint,
    }
}

/// Per-AppImage FUSE launch status
#[derive(Debug, Clone, PartialEq)]
pub enum AppImageFuseStatus {
    /// Will mount natively via FUSE
    NativeFuse,
    /// Uses new type2-runtime with static FUSE
    StaticRuntime,
    /// Will use extract-and-run fallback (slower startup)
    ExtractAndRun,
    /// Cannot launch at all
    CannotLaunch,
}

impl AppImageFuseStatus {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::NativeFuse => "native_fuse",
            Self::StaticRuntime => "static_runtime",
            Self::ExtractAndRun => "extract_and_run",
            Self::CannotLaunch => "cannot_launch",
        }
    }

    pub fn label(&self) -> &'static str {
        match self {
            Self::NativeFuse => "Native FUSE",
            Self::StaticRuntime => "Static runtime",
            Self::ExtractAndRun => "Extract & Run",
            Self::CannotLaunch => "Cannot launch",
        }
    }

    pub fn badge_class(&self) -> &'static str {
        match self {
            Self::NativeFuse | Self::StaticRuntime => "success",
            Self::ExtractAndRun => "warning",
            Self::CannotLaunch => "error",
        }
    }
}

/// Determine launch status for a specific AppImage given system FUSE state.
pub fn determine_app_fuse_status(
    system: &FuseSystemInfo,
    appimage_path: &Path,
) -> AppImageFuseStatus {
    // Check if the AppImage uses the new static runtime
    if has_static_runtime(appimage_path) {
        return AppImageFuseStatus::StaticRuntime;
    }

    if system.status.is_functional() {
        return AppImageFuseStatus::NativeFuse;
    }

    // FUSE not fully functional - check if extract-and-run works
    if supports_extract_and_run(appimage_path) {
        AppImageFuseStatus::ExtractAndRun
    } else {
        AppImageFuseStatus::CannotLaunch
    }
}

/// Check if the AppImage uses the new type2-runtime with statically linked FUSE.
/// Scans the first 256KB of the binary for runtime signatures instead of executing
/// the AppImage (which can hang for apps with custom AppRun scripts like Affinity).
fn has_static_runtime(appimage_path: &Path) -> bool {
    use std::io::Read;
    let mut file = match std::fs::File::open(appimage_path) {
        Ok(f) => f,
        Err(_) => return false,
    };
    // The runtime signature is in the ELF binary header area, well within first 256KB
    let mut buf = vec![0u8; 256 * 1024];
    let n = match file.read(&mut buf) {
        Ok(n) => n,
        Err(_) => return false,
    };
    let data = &buf[..n];
    // Search raw bytes directly - avoids allocating a UTF-8 string from binary data.
    // Case-insensitive matching for the two known signatures.
    bytes_contains_ci(data, b"type2-runtime")
        || bytes_contains_ci(data, b"libfuse3")
}

/// Case-insensitive byte-level substring search (ASCII only).
fn bytes_contains_ci(haystack: &[u8], needle: &[u8]) -> bool {
    if needle.is_empty() || haystack.len() < needle.len() {
        return false;
    }
    haystack.windows(needle.len()).any(|window| {
        window.iter().zip(needle).all(|(h, n)| h.to_ascii_lowercase() == n.to_ascii_lowercase())
    })
}

/// Check if --appimage-extract-and-run is supported.
fn supports_extract_and_run(appimage_path: &Path) -> bool {
    // Check for AppImage Type 2 magic at offset 8 - only need to read 12 bytes
    use std::io::Read;
    let mut file = match std::fs::File::open(appimage_path) {
        Ok(f) => f,
        Err(_) => return false,
    };
    let mut header = [0u8; 12];
    if file.read_exact(&mut header).is_ok() {
        return header[8] == 0x41 && header[9] == 0x49 && header[10] == 0x02;
    }
    false
}

/// Check if a shared library is available on the system via ldconfig.
fn check_library(soname: &str) -> bool {
    let output = Command::new("ldconfig")
        .arg("-p")
        .output();

    if let Ok(output) = output {
        let stdout = String::from_utf8_lossy(&output.stdout);
        return stdout.contains(soname);
    }
    false
}

/// Find fusermount or fusermount3 binary.
fn find_fusermount() -> Option<String> {
    for name in &["fusermount", "fusermount3"] {
        let output = Command::new("which")
            .arg(name)
            .output();
        if let Ok(output) = output {
            if output.status.success() {
                let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
                if !path.is_empty() {
                    return Some(path);
                }
            }
        }
    }
    None
}

/// Detect distro and return the appropriate libfuse2 install command.
fn get_install_hint() -> String {
    if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
        let id = extract_os_field(&content, "ID");
        let version_id = extract_os_field(&content, "VERSION_ID");
        let id_like = extract_os_field(&content, "ID_LIKE");

        return match id.as_deref() {
            Some("ubuntu") => {
                let ver: f64 = version_id
                    .as_deref()
                    .and_then(|v| v.parse().ok())
                    .unwrap_or(0.0);
                if ver >= 24.04 {
                    "sudo apt install libfuse2t64".to_string()
                } else {
                    "sudo apt install libfuse2".to_string()
                }
            }
            Some("debian") => "sudo apt install libfuse2".to_string(),
            Some("fedora") => "sudo dnf install fuse-libs".to_string(),
            Some("arch") | Some("manjaro") | Some("endeavouros") => {
                "sudo pacman -S fuse2".to_string()
            }
            Some("opensuse-tumbleweed") | Some("opensuse-leap") => {
                "sudo zypper install libfuse2".to_string()
            }
            _ => {
                // Check ID_LIKE for derivatives
                if let Some(like) = id_like.as_deref() {
                    if like.contains("ubuntu") || like.contains("debian") {
                        return "sudo apt install libfuse2".to_string();
                    }
                    if like.contains("fedora") {
                        return "sudo dnf install fuse-libs".to_string();
                    }
                    if like.contains("arch") {
                        return "sudo pacman -S fuse2".to_string();
                    }
                    if like.contains("suse") {
                        return "sudo zypper install libfuse2".to_string();
                    }
                }
                "Install libfuse2 using your distribution's package manager".to_string()
            }
        };
    }
    "Install libfuse2 using your distribution's package manager".to_string()
}

fn extract_os_field(content: &str, key: &str) -> Option<String> {
    for line in content.lines() {
        if let Some(rest) = line.strip_prefix(&format!("{}=", key)) {
            return Some(rest.trim_matches('"').to_string());
        }
    }
    None
}

/// Get the package install command suitable for running via pkexec (no sudo prefix).
pub fn get_fuse_install_command() -> Option<String> {
    let content = std::fs::read_to_string("/etc/os-release").ok()?;
    let id = extract_os_field(&content, "ID");
    let version_id = extract_os_field(&content, "VERSION_ID");
    let id_like = extract_os_field(&content, "ID_LIKE");

    match id.as_deref() {
        Some("ubuntu") => {
            let ver: f64 = version_id
                .as_deref()
                .and_then(|v| v.parse().ok())
                .unwrap_or(0.0);
            if ver >= 24.04 {
                Some("apt install -y libfuse2t64".to_string())
            } else {
                Some("apt install -y libfuse2".to_string())
            }
        }
        Some("debian") => Some("apt install -y libfuse2".to_string()),
        Some("fedora") => Some("dnf install -y fuse-libs".to_string()),
        Some("arch") | Some("manjaro") | Some("endeavouros") => {
            Some("pacman -S --noconfirm fuse2".to_string())
        }
        Some("opensuse-tumbleweed") | Some("opensuse-leap") => {
            Some("zypper install -y libfuse2".to_string())
        }
        _ => {
            if let Some(like) = id_like.as_deref() {
                if like.contains("ubuntu") || like.contains("debian") {
                    return Some("apt install -y libfuse2".to_string());
                }
                if like.contains("fedora") {
                    return Some("dnf install -y fuse-libs".to_string());
                }
                if like.contains("arch") {
                    return Some("pacman -S --noconfirm fuse2".to_string());
                }
                if like.contains("suse") {
                    return Some("zypper install -y libfuse2".to_string());
                }
            }
            None
        }
    }
}

/// Check if AppImageLauncher is installed (known conflicts with new runtime).
pub fn detect_appimagelauncher() -> Option<String> {
    let output = Command::new("dpkg")
        .args(["-s", "appimagelauncher"])
        .output();

    if let Ok(output) = output {
        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            for line in stdout.lines() {
                if let Some(ver) = line.strip_prefix("Version: ") {
                    return Some(ver.trim().to_string());
                }
            }
            return Some("unknown".to_string());
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fuse_status_roundtrip() {
        let statuses = [
            FuseStatus::FullyFunctional,
            FuseStatus::Fuse3Only,
            FuseStatus::NoFusermount,
            FuseStatus::NoDevFuse,
            FuseStatus::MissingLibfuse2,
        ];
        for status in &statuses {
            assert_eq!(&FuseStatus::from_str(status.as_str()), status);
        }
    }

    #[test]
    fn test_extract_os_field() {
        let content = r#"NAME="Ubuntu"
VERSION_ID="24.04"
ID=ubuntu
ID_LIKE=debian
"#;
        assert_eq!(extract_os_field(content, "ID"), Some("ubuntu".to_string()));
        assert_eq!(extract_os_field(content, "VERSION_ID"), Some("24.04".to_string()));
        assert_eq!(extract_os_field(content, "ID_LIKE"), Some("debian".to_string()));
        assert_eq!(extract_os_field(content, "MISSING"), None);
    }

    #[test]
    fn test_fuse_status_badges() {
        assert_eq!(FuseStatus::FullyFunctional.badge_class(), "success");
        assert_eq!(FuseStatus::Fuse3Only.badge_class(), "warning");
        assert_eq!(FuseStatus::MissingLibfuse2.badge_class(), "error");
    }
}
