//! System requirements checking
//!
//! Verifies that the target system meets all requirements for OSTP Client.

use serde::{Deserialize, Serialize};

/// System check result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemCheckResult {
    pub wintun_installed: bool,
    pub aes_ni_supported: bool,
    pub admin_privileges: bool,
    pub os_version: String,
}
use anyhow::Result;

/// Run all system checks
pub async fn run_checks() -> Result<SystemCheckResult> {
    Ok(SystemCheckResult {
        wintun_installed: check_wintun_installed(),
        aes_ni_supported: check_aes_ni_support(),
        admin_privileges: check_admin_privileges(),
        os_version: get_os_version(),
    })
}

/// Check if wintun.dll is already installed
fn check_wintun_installed() -> bool {
    crate::wintun::is_installed()
}

/// Check if CPU supports AES-NI instructions
fn check_aes_ni_support() -> bool {
    #[cfg(target_arch = "x86_64")]
    {
        // Check CPUID for AES-NI support
        return is_x86_feature_detected!("aes");
    }
    
    #[cfg(target_arch = "aarch64")]
    {
        // ARM64 typically has crypto extensions
        // Check for ARMv8 Crypto extensions
        #[cfg(target_feature = "aes")]
        return true;
        
        #[cfg(not(target_feature = "aes"))]
        return false;
    }
    
    // Fallback: assume software AES is acceptable
    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
    true
}

/// Check if running with administrator privileges
fn check_admin_privileges() -> bool {
    #[cfg(windows)]
    {
        use std::ptr;
        
        // Use Windows API to check elevation
        unsafe {
            let mut token_handle: *mut std::ffi::c_void = ptr::null_mut();
            let current_process = GetCurrentProcess();
            
            if OpenProcessToken(
                current_process,
                TOKEN_QUERY,
                &mut token_handle,
            ) == 0 {
                return false;
            }
            
            let mut elevation = TOKEN_ELEVATION { TokenIsElevated: 0 };
            let mut size: u32 = std::mem::size_of::<TOKEN_ELEVATION>() as u32;
            
            let result = GetTokenInformation(
                token_handle,
                TokenElevation,
                &mut elevation as *mut _ as *mut _,
                size,
                &mut size,
            );
            
            CloseHandle(token_handle);
            
            result != 0 && elevation.TokenIsElevated != 0
        }
    }
    
    #[cfg(not(windows))]
    {
        // On non-Windows, check if running as root
        unsafe { libc::geteuid() == 0 }
    }
}

/// Get Windows version string
fn get_os_version() -> String {
    #[cfg(windows)]
    {
        use winreg::{enums::*, RegKey};
        
        if let Ok(hklm) = RegKey::predef(HKEY_LOCAL_MACHINE)
            .open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion")
        {
            let product_name: String = hklm.get_value("ProductName").unwrap_or_default();
            let build: String = hklm.get_value("CurrentBuild").unwrap_or_default();
            
            if !product_name.is_empty() {
                return format!("{} (Build {})", product_name, build);
            }
        }
        
        "Windows (Unknown Version)".to_string()
    }
    
    #[cfg(not(windows))]
    {
        "Non-Windows OS".to_string()
    }
}

/// Check minimum Windows version (Windows 10 1607+)
#[allow(dead_code)]
pub fn check_minimum_version() -> bool {
    #[cfg(windows)]
    {
        use winreg::{enums::*, RegKey};
        
        if let Ok(hklm) = RegKey::predef(HKEY_LOCAL_MACHINE)
            .open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion")
        {
            let build: String = hklm.get_value("CurrentBuild").unwrap_or_default();
            if let Ok(build_num) = build.parse::<u32>() {
                // Windows 10 1607 is build 14393
                return build_num >= 14393;
            }
        }
        
        false
    }
    
    #[cfg(not(windows))]
    true
}

/// Check available disk space (need at least 50MB)
#[allow(dead_code)]
pub fn check_disk_space(path: &std::path::Path) -> bool {
    #[cfg(windows)]
    {
        use std::os::windows::ffi::OsStrExt;
        use std::ffi::OsStr;
        
        let path_wide: Vec<u16> = OsStr::new(path)
            .encode_wide()
            .chain(std::iter::once(0))
            .collect();
        
        let mut free_bytes: u64 = 0;
        let mut total_bytes: u64 = 0;
        let mut total_free_bytes: u64 = 0;
        
        unsafe {
            if GetDiskFreeSpaceExW(
                path_wide.as_ptr(),
                &mut free_bytes,
                &mut total_bytes,
                &mut total_free_bytes,
            ) != 0 {
                // Need at least 50MB
                return free_bytes >= 50 * 1024 * 1024;
            }
        }
        
        true // Assume OK if check fails
    }
    
    #[cfg(not(windows))]
    {
        let _ = path;
        true
    }
}

// Windows API bindings
#[cfg(windows)]
unsafe extern "system" {
    fn GetCurrentProcess() -> *mut std::ffi::c_void;
    fn OpenProcessToken(
        process: *mut std::ffi::c_void,
        access: u32,
        token: *mut *mut std::ffi::c_void,
    ) -> i32;
    fn GetTokenInformation(
        token: *mut std::ffi::c_void,
        info_class: u32,
        info: *mut std::ffi::c_void,
        info_len: u32,
        return_len: *mut u32,
    ) -> i32;
    fn CloseHandle(handle: *mut std::ffi::c_void) -> i32;
    #[allow(dead_code)]
    fn GetDiskFreeSpaceExW(
        directory: *const u16,
        free_bytes: *mut u64,
        total_bytes: *mut u64,
        total_free_bytes: *mut u64,
    ) -> i32;
}

#[cfg(windows)]
const TOKEN_QUERY: u32 = 0x0008;
#[cfg(windows)]
#[allow(non_upper_case_globals)]
const TokenElevation: u32 = 20;

#[cfg(windows)]
#[repr(C)]
#[allow(non_snake_case)]
struct TOKEN_ELEVATION {
    TokenIsElevated: u32,
}
