use std::fs;

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

pub fn get_memory(memory_percent: bool, memory_unit: MemoryUnit) -> Option<String> {
    let contents = fs::read_to_string("/proc/meminfo").ok()?;

    let mut mem_total_kb = 0;
    let mut mem_available_kb = None;
    let mut shmem = 0;
    let mut memfree = 0;
    let mut buffers = 0;
    let mut cached = 0;
    let mut sreclaimable = 0;

    for line in contents.lines() {
        let mut parts = line.split_whitespace();
        match parts.next()? {
            "MemTotal:" => mem_total_kb = parts.next()?.parse::<u64>().ok()?,
            "MemAvailable:" => mem_available_kb = parts.next()?.parse::<u64>().ok(),
            "Shmem:" => shmem = parts.next()?.parse::<u64>().ok()?,
            "MemFree:" => memfree = parts.next()?.parse::<u64>().ok()?,
            "Buffers:" => buffers = parts.next()?.parse::<u64>().ok()?,
            "Cached:" => cached = parts.next()?.parse::<u64>().ok()?,
            "SReclaimable:" => sreclaimable = parts.next()?.parse::<u64>().ok()?,
            _ => {}
        }
    }

    let used_kb = if let Some(avail) = mem_available_kb {
        mem_total_kb.saturating_sub(avail)
    } else {
        mem_total_kb + shmem - memfree - buffers - cached - sreclaimable
    };

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

    let percent = if memory_percent && total > 0.0 {
        format!(" ({:.0}%)", used / total * 100.0)
    } else {
        "".to_string()
    };

    Some(format!(
        "{:.1}{} / {:.1}{}{}",
        used, unit_str, total, 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"));
    }
}
