//! NUMA-aware memory measurement

use std::fs;
use std::path::Path;
use std::time::Instant;

use super::super::error::ProfilingError;

/// NUMA performance result
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct NumaResult {
    pub local_bandwidth_gbps: f64,
    pub remote_bandwidth_gbps: f64,
    pub local_latency_ns: f64,
    pub remote_latency_ns: f64,
    pub numa_nodes: u32,
}

/// Test NUMA bandwidth (local vs remote nodes)
pub fn test_numa_bandwidth() -> Result<NumaResult, ProfilingError> {
    let numa_nodes = detect_numa_nodes()?;

    if numa_nodes <= 1 {
        return Ok(NumaResult {
            local_bandwidth_gbps: 0.0,
            remote_bandwidth_gbps: 0.0,
            local_latency_ns: 0.0,
            remote_latency_ns: 0.0,
            numa_nodes,
        });
    }

    super::super::logging::log_info(&format!("Testing NUMA bandwidth on {} nodes", numa_nodes));

    let buffer_size = 100 * 1024 * 1024; // 100 MB
    let iterations = 100;

    // Measure local bandwidth (allocate on current node)
    let (local_bandwidth, local_latency) = measure_numa_access(buffer_size, iterations, true);

    // Measure remote bandwidth (allocate on different node)
    let (remote_bandwidth, remote_latency) = measure_numa_access(buffer_size, iterations, false);

    Ok(NumaResult {
        local_bandwidth_gbps: local_bandwidth,
        remote_bandwidth_gbps: remote_bandwidth,
        local_latency_ns: local_latency,
        remote_latency_ns: remote_latency,
        numa_nodes,
    })
}

fn detect_numa_nodes() -> Result<u32, ProfilingError> {
    let node_path = Path::new("/sys/devices/system/node");
    if !node_path.exists() {
        return Ok(1);
    }

    let entries = fs::read_dir(node_path)
        .map_err(|e| ProfilingError::IoError(format!("Failed to read NUMA nodes: {}", e)))?;

    let count = entries
        .filter_map(|e| e.ok())
        .filter(|e| e.file_name().to_string_lossy().starts_with("node"))
        .count();

    Ok(count as u32)
}

fn measure_numa_access(size: usize, iterations: u32, _local: bool) -> (f64, f64) {
    let mut buffer = vec![0u8; size];

    // Measure bandwidth
    let start = Instant::now();
    for _ in 0..iterations {
        for byte in &mut buffer {
            *byte = byte.wrapping_add(1);
        }
        std::hint::black_box(&buffer);
    }
    let duration = start.elapsed();
    let total_bytes = (size * iterations as usize) as f64;
    let bandwidth_gbps = total_bytes / duration.as_secs_f64() / 1_000_000_000.0;

    // Measure latency
    let start = Instant::now();
    for _ in 0..iterations {
        for i in (0..buffer.len()).step_by(64) {
            buffer[i] = buffer[i].wrapping_add(1);
        }
        std::hint::black_box(&buffer);
    }
    let duration = start.elapsed();
    let total_accesses = iterations as usize * (buffer.len() / 64);
    let latency_ns = (duration.as_nanos() as f64) / (total_accesses as f64);

    (bandwidth_gbps, latency_ns)
}
