use std::{
    env::consts::{ARCH, OS},
    fs,
    path::Path,
};
mod base;
use base::{Architecture, OperatingSystem};

static SYS_META: [&str; 2] = [OS, ARCH];
static ENV_META: [&str; 3] = ["NAME=", "PRETTY_NAME=", "VERSION_ID="];

// Index Constants
const OS_: u8 = 0;
const ARCH_: u8 = 1;
const NAME: u8 = 0;
const PRETTY_NAME: u8 = 1;
const VERSION_ID: u8 = 2;

/// Returns the name of the host operating system; matches against `std::env::consts::OS`
pub fn detect_os() -> OperatingSystem {
    let os = match SYS_META[OS_ as usize] { 
        "linux" => OperatingSystem::Linux,
        "android" => OperatingSystem::Android,
        "freebsd" => OperatingSystem::FreeBSD,
        "dragonfly" => OperatingSystem::DragonFlyBSD,
        "netbsd" => OperatingSystem::NetBSD,
        "openbsd" => OperatingSystem::OpenBSD,
        "solaris" => OperatingSystem::Solaris,
        "macos" => OperatingSystem::MacOS,
        "windows" => OperatingSystem::Windows,
        _ => OperatingSystem::Unknown,
    };
    os
}

/// Returns the architecture of the host operating system; matches against `std::env::consts::ARCH`
pub fn detect_arch() -> Architecture {
    let arch = match SYS_META[ARCH_ as usize] {
        "x86" => Architecture::X86,
        "x86_64" => Architecture::X86_64,
        "arm" => Architecture::Arm,
        "aarch64" => Architecture::Aarch64,
        "loongarch64" => Architecture::Loongarch64,
        "m68k" => Architecture::M68k,
        "csky" => Architecture::Csky,
        "mips" => Architecture::Mips,
        "mips64" => Architecture::Mips64,
        "powerpc" => Architecture::Powerpc,
        "powerpc64" => Architecture::Powerpc64,
        "riscv64" => Architecture::Riscv64,
        "s390x" => Architecture::S390x,
        "sparc64" => Architecture::Sparc64,
        _ => Architecture::Unknown,
    };
    arch
}

/// Returns the NAME of the Linux distribution via `/etc/os-release`
pub fn detect_distro() -> String {
    let distro = parse_release(ENV_META[NAME as usize]);
    distro
}

/// Returns the PRETTY_NAME of the Linux distribution via `/etc/os-release`
pub fn detect_distro_pretty() -> String {
    let distro = parse_release(ENV_META[PRETTY_NAME as usize]);
    distro
}

/// Returns the VERSION_ID of the Linux distribution via `/etc/os-release`
pub fn detect_distro_version() -> String {
    let id = parse_release(ENV_META[VERSION_ID as usize]);
    id
}

fn parse_release(env_var: &'static str) -> String {
    let release = fs::read_to_string("/etc/os-release").expect("Unable to read file");

    let capture = release
        .lines()
        .find(|line| line.starts_with(env_var))
        .expect("Unable to find the specified environment variable")
        .split('=')
        .nth(1)
        .expect("Unable to parse Environment Variable from /etc/os-release")
        .trim_matches('"')
        .to_string();
    capture
}

/// Checks if the linux environment is a Windows subsystem
pub fn is_subsystem_env() -> bool {
    let interop_path = Path::new("/proc/sys/fs/binfmt_misc/WSLInterop").exists();
    interop_path
}

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

    // Generic test function, keeps tests DRY
    fn test_fn<T: PartialEq + std::fmt::Debug + std::fmt::Display>(f: fn() -> T, expected: T) {
        assert_eq!(f(), expected);
        println!("Result: {}", f());
    }

    #[test]
    fn test_detect_os() {
        test_fn(detect_os, OperatingSystem::Windows);
    }

    #[test]
    fn test_detect_arch() {
        test_fn(detect_arch, Architecture::X86_64);
    }

    #[test]
    fn test_detect_distro() {
        test_fn(detect_distro, "Fedora Linux".to_string());
    }

    #[test]
    fn test_detect_distro_version() {
        test_fn(detect_distro_version, "39".to_string());
    }

    #[test]
    fn test_is_subsystem_env() {
        test_fn(is_subsystem_env, false);
    }
}
