use std::mem::{size_of, zeroed};

use crate::modules::enums::MemoryUnit;

#[repr(C)]
#[allow(non_snake_case)]
struct MEMORYSTATUSEX {
    dwLength: u32,
    dwMemoryLoad: u32,
    ullTotalPhys: u64,
    ullAvailPhys: u64,
    ullTotalPageFile: u64,
    ullAvailPageFile: u64,
    ullTotalVirtual: u64,
    ullAvailVirtual: u64,
    ullAvailExtendedVirtual: u64,
}

#[link(name = "kernel32")]
unsafe extern "system" {
    fn GlobalMemoryStatusEx(lpBuffer: *mut MEMORYSTATUSEX) -> i32;
}

pub fn get_memory(memory_percent: bool, memory_unit: MemoryUnit) -> Option<String> {
    unsafe {
        let mut mem_info: MEMORYSTATUSEX = zeroed();
        mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as u32;

        if GlobalMemoryStatusEx(&mut mem_info as *mut _) == 0 {
            return None;
        }

        let total = mem_info.ullTotalPhys;
        let avail = mem_info.ullAvailPhys;
        let used = total.saturating_sub(avail);

        let (used_conv, total_conv, unit_str) = match memory_unit {
            MemoryUnit::GiB => (
                used as f64 / 1024.0 / 1024.0 / 1024.0,
                total as f64 / 1024.0 / 1024.0 / 1024.0,
                "GiB",
            ),
            MemoryUnit::MiB => (
                used as f64 / 1024.0 / 1024.0,
                total as f64 / 1024.0 / 1024.0,
                "MiB",
            ),
            MemoryUnit::KiB => (used as f64 / 1024.0, total as f64 / 1024.0, "KiB"),
        };

        let percent = if memory_percent {
            format!(" ({:.0}%)", used_conv / total_conv * 100.0)
        } else {
            "".to_string()
        };

        Some(format!(
            "{:.1}{} / {:.1}{}{}",
            used_conv, unit_str, total_conv, unit_str, percent
        ))
    }
}

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

    #[test]
    fn test_memory_output_mib() {
        let result = get_memory(false, MemoryUnit::MiB);
        assert!(result.is_some());
        assert!(result.unwrap().contains("MiB"));
    }

    #[test]
    fn test_memory_output_gib_with_percent() {
        let result = get_memory(true, MemoryUnit::GiB);
        let output = result.unwrap();
        assert!(output.contains("GiB"));
        assert!(output.contains('%'));
    }

    #[test]
    fn test_memory_output_kib() {
        let result = get_memory(false, MemoryUnit::KiB);
        assert!(result.unwrap().contains("KiB"));
    }
}
