//! Network latency measurement

use std::process::Command;

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

/// Network latency measurement result
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LatencyResult {
    pub host: String,
    pub min_ms: f64,
    pub max_ms: f64,
    pub avg_ms: f64,
    pub stddev_ms: f64,
    pub packet_loss_percent: f64,
    pub samples: u32,
}

impl Default for LatencyResult {
    fn default() -> Self {
        Self {
            host: "unknown".to_string(),
            min_ms: 0.0,
            max_ms: 0.0,
            avg_ms: 0.0,
            stddev_ms: 0.0,
            packet_loss_percent: 0.0,
            samples: 0,
        }
    }
}

/// Measure network latency to host using ping
pub fn measure_latency(host: &str, count: u32) -> Result<LatencyResult, ProfilingError> {
    // Validate inputs
    let sanitized_host = validation::validate_server_address(host)?;
    let validated_count = std::cmp::min(count.max(1), 100); // Limit to 1-100 pings

    super::super::logging::log_info(&format!(
        "Measuring latency to {} ({} pings)",
        sanitized_host, validated_count
    ));

    // Use safe ping command with limited options
    let output = Command::new("ping")
        .arg("-c")
        .arg(validated_count.to_string())
        .arg("-W") // Timeout
        .arg("5") // 5 second timeout
        .arg("-i") // Interval
        .arg("0.2") // 200ms minimum interval
        .arg(&sanitized_host)
        .output()
        .map_err(|e| ProfilingError::NetworkError(format!("Failed to run ping: {}", e)))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        if stderr.contains("Name or service not known") {
            return Err(ProfilingError::NetworkError(format!(
                "Host not found: {}",
                sanitized_host
            )));
        } else if stderr.contains("Network is unreachable") {
            return Err(ProfilingError::NetworkError(
                "Network unreachable".to_string(),
            ));
        }
        return Err(ProfilingError::NetworkError(stderr.to_string()));
    }

    parse_ping_output(
        &String::from_utf8_lossy(&output.stdout),
        &sanitized_host,
        validated_count,
    )
}

fn parse_ping_output(
    output: &str,
    host: &str,
    count: u32,
) -> Result<LatencyResult, ProfilingError> {
    let mut latencies: Vec<f64> = Vec::new();
    let mut packet_loss = 0.0;

    for line in output.lines() {
        if line.contains("time=") {
            if let Some(time_str) = line.split("time=").nth(1) {
                let time_str = time_str.split_whitespace().next().unwrap_or("0");
                if let Ok(latency) = time_str.parse::<f64>() {
                    latencies.push(latency);
                }
            }
        }

        if line.contains("packet loss") {
            if let Some(loss_str) = line.split("packet loss").next() {
                let parts: Vec<&str> = loss_str.split_whitespace().collect();
                if let Some(percent_str) = parts.last() {
                    if let Ok(loss) = percent_str.trim_end_matches('%').parse::<f64>() {
                        packet_loss = loss;
                    }
                }
            }
        }
    }

    if latencies.is_empty() {
        return Err(ProfilingError::ParseError(
            "No latency data found in ping output".to_string(),
        ));
    }

    let min = latencies.iter().cloned().fold(f64::INFINITY, f64::min);
    let max = latencies.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
    let sum: f64 = latencies.iter().sum();
    let avg = sum / latencies.len() as f64;

    let variance: f64 = latencies.iter().map(|&x| (x - avg).powi(2)).sum();
    let stddev = (variance / latencies.len() as f64).sqrt();

    Ok(LatencyResult {
        host: host.to_string(),
        min_ms: min,
        max_ms: max,
        avg_ms: avg,
        stddev_ms: stddev,
        packet_loss_percent: packet_loss,
        samples: count,
    })
}
