//! Unified hardware detection: GPUs + CPU + RAM.

use crate::types::GpuTopology;
use crate::nvidia;

/// Detect all GPUs and system resources, return topology.
pub fn detect_all() -> GpuTopology {
    let mut gpus = Vec::new();

    // NVIDIA
    gpus.extend(nvidia::detect_nvidia());

    // AMD (ROCm) — placeholder for F2
    // Intel (Level Zero) — placeholder for F2
    // Apple (Metal) — placeholder for F2

    let total_vram_mb: u64 = gpus.iter().map(|g| g.vram_total_mb).sum();
    let total_free_vram_mb: u64 = gpus.iter().map(|g| g.vram_free_mb).sum();
    let strategy = GpuTopology::recommend_strategy(&gpus);

    let cpu_cores = detect_cpu_cores();
    let (ram_total_mb, ram_free_mb) = detect_ram();

    GpuTopology {
        gpus,
        total_vram_mb,
        total_free_vram_mb,
        recommended_strategy: strategy,
        cpu_cores,
        ram_total_mb,
        ram_free_mb,
    }
}

fn detect_cpu_cores() -> usize {
    num_cpus::get()
}

fn detect_ram() -> (u64, u64) {
    // Linux: parse /proc/meminfo
    if let Ok(contents) = std::fs::read_to_string("/proc/meminfo") {
        let mut total: u64 = 0;
        let mut free: u64 = 0;
        for line in contents.lines() {
            if line.starts_with("MemTotal:") {
                total = line
                    .split_whitespace()
                    .nth(1)
                    .and_then(|v| v.parse::<u64>().ok())
                    .unwrap_or(0)
                    / 1024; // kB → MB
            } else if line.starts_with("MemAvailable:") {
                free = line
                    .split_whitespace()
                    .nth(1)
                    .and_then(|v| v.parse::<u64>().ok())
                    .unwrap_or(0)
                    / 1024; // kB → MB
            }
        }
        return (total, free);
    }
    // Fallback for non-Linux: return 0 (unknown)
    (0, 0)
}

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

    #[test]
    fn test_detect_all_returns_valid_topology() {
        let topo = detect_all();
        // Strategy should match GPU count
        if topo.gpus.is_empty() {
            assert_eq!(topo.recommended_strategy, crate::types::InferenceStrategy::CpuOnly);
        }
        // CPU cores should be at least 1
        assert!(topo.cpu_cores >= 1, "cpu_cores: {}", topo.cpu_cores);
    }

    #[test]
    fn test_detect_cpu_ram_on_linux() {
        let (total, free) = detect_ram();
        // On Linux, we should get non-zero values
        if cfg!(target_os = "linux") {
            assert!(total > 0, "ram_total_mb: {}", total);
            assert!(free > 0, "ram_free_mb: {}", free);
        }
    }
}
