//! iperf3 integration for network performance testing

use std::process::{Command, Stdio};
use std::time::Duration;

use super::super::error::ProfilingError;
use super::super::logging;
use super::super::validation::{validate_port, validate_server_address};
use super::protocol::Protocol;

/// Check if iperf3 is available on the system
pub fn is_iperf3_available() -> bool {
    Command::new("iperf3")
        .arg("--version")
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .is_ok()
}

/// Configuration for iperf3 tests
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct IperfConfig {
    pub server: String,
    pub port: u16,
    pub duration: Duration,
    pub parallel_streams: u32,
    pub window_size: Option<u32>,
    pub protocol: Protocol,
    pub bandwidth_limit: Option<u64>,
    pub reverse: bool,
}

impl IperfConfig {
    /// Create new iperf configuration
    pub fn new() -> Self {
        Self {
            server: String::new(),
            port: 5201,
            duration: Duration::from_secs(10),
            parallel_streams: 1,
            window_size: None,
            protocol: Protocol::Tcp,
            bandwidth_limit: None,
            reverse: false,
        }
    }

    /// Set test duration
    pub fn with_duration(mut self, duration: Duration) -> Self {
        self.duration = duration;
        self
    }

    /// Set parallel stream count
    pub fn with_parallel_streams(mut self, streams: u32) -> Self {
        self.parallel_streams = streams;
        self
    }

    /// Set server address
    pub fn with_server(mut self, server: &str) -> Self {
        self.server = server.to_string();
        self
    }

    /// Set TCP window size
    pub fn with_window_size(mut self, size: u32) -> Self {
        self.window_size = Some(size);
        self
    }

    /// Set protocol
    pub fn with_protocol(mut self, protocol: Protocol) -> Self {
        self.protocol = protocol;
        self
    }

    /// Set bandwidth limit (UDP)
    pub fn with_bandwidth_limit(mut self, limit: u64) -> Self {
        self.bandwidth_limit = Some(limit);
        self
    }

    /// Set reverse direction
    pub fn with_reverse(mut self, reverse: bool) -> Self {
        self.reverse = reverse;
        self
    }
}

impl Default for IperfConfig {
    fn default() -> Self {
        Self {
            server: String::new(),
            port: 5201,
            duration: Duration::from_secs(10),
            parallel_streams: 1,
            window_size: None,
            protocol: Protocol::Tcp,
            bandwidth_limit: None,
            reverse: false,
        }
    }
}

/// Result from iperf3 test
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct IperfResult {
    pub config: IperfConfig,
    pub bandwidth_bps: f64,
    pub retransmits: Option<u32>,
    pub jitter_ms: Option<f64>,
    pub packet_loss_percent: Option<f64>,
    pub duration: Duration,
}

// Add From implementations for type conversions
impl From<super::latency::LatencyResult> for IperfResult {
    fn from(latency: super::latency::LatencyResult) -> Self {
        Self {
            config: IperfConfig {
                server: latency.host,
                port: 5201,
                duration: std::time::Duration::from_millis(100),
                parallel_streams: 1,
                window_size: None,
                protocol: super::protocol::Protocol::Tcp,
                bandwidth_limit: None,
                reverse: false,
            },
            bandwidth_bps: 0.0, // No bandwidth for latency test
            retransmits: None,
            jitter_ms: None,
            packet_loss_percent: Some(latency.packet_loss_percent),
            duration: std::time::Duration::from_millis(100),
        }
    }
}

impl From<super::bandwidth::BandwidthResult> for IperfResult {
    fn from(bandwidth: super::bandwidth::BandwidthResult) -> Self {
        Self {
            config: IperfConfig {
                server: bandwidth.server.clone(),
                port: 5201,
                duration: bandwidth.duration,
                parallel_streams: 1,
                window_size: None,
                protocol: super::protocol::Protocol::Tcp,
                bandwidth_limit: None,
                reverse: false,
            },
            bandwidth_bps: bandwidth.upload_bps.max(bandwidth.download_bps),
            retransmits: None,
            jitter_ms: None,
            packet_loss_percent: None,
            duration: bandwidth.duration,
        }
    }
}

/// Client for iperf3 network performance testing
pub struct IperfClient {
    timeout: Duration,
    retry_count: u32,
}

impl IperfClient {
    /// Create iperf client for server
    pub fn new(server: &str) -> Result<Self, ProfilingError> {
        // Validate server address but don't store it (config is used instead)
        validate_server_address(server)?;
        Ok(Self {
            timeout: Duration::from_secs(30),
            retry_count: 0,
        })
    }

    /// Set timeout for iperf3 operations
    pub fn with_timeout(mut self, timeout: Duration) -> Self {
        self.timeout = timeout;
        self
    }

    /// Set retry count for transient failures
    pub fn with_retry_count(mut self, count: u32) -> Self {
        self.retry_count = count;
        self
    }

    /// Check if iperf3 is available
    pub fn check_iperf3() -> Result<(), ProfilingError> {
        let output = Command::new("iperf3").arg("--version").output();

        match output {
            Ok(output) => {
                if output.status.success() {
                    let version_str = String::from_utf8_lossy(&output.stdout);
                    logging::log_info(&format!("iperf3 found: {}", version_str.trim()));
                    Ok(())
                } else {
                    Err(ProfilingError::Iperf3Failed(
                        "iperf3 command failed".to_string(),
                    ))
                }
            }
            Err(_) => Err(ProfilingError::Iperf3NotFound),
        }
    }

    /// Execute iperf test with configuration
    pub fn run_iperf_test(&self, config: &IperfConfig) -> Result<IperfResult, ProfilingError> {
        Self::check_iperf3()?;

        // Validate configuration
        let sanitized_server = validate_server_address(&config.server)?;
        let validated_port = validate_port(config.port)?;
        let validated_duration = std::cmp::min(
            config.duration.max(Duration::from_secs(1)),
            Duration::from_secs(3600),
        ); // 1s to 1h
        let validated_streams = std::cmp::min(config.parallel_streams.max(1), 128); // Limit to 1-128 streams

        let mut validated_config = config.clone();
        validated_config.server = sanitized_server;
        validated_config.port = validated_port;
        validated_config.duration = validated_duration;
        validated_config.parallel_streams = validated_streams;

        let mut attempts = 0;
        let max_attempts = self.retry_count + 1;

        loop {
            attempts += 1;

            match self.run_iperf_test_internal(&validated_config) {
                Ok(result) => return Ok(result),
                Err(e) if attempts < max_attempts => {
                    logging::log_warn(&format!(
                        "iperf3 attempt {} failed, retrying: {}",
                        attempts, e
                    ));
                    std::thread::sleep(Duration::from_secs(1));
                }
                Err(e) => return Err(e),
            }
        }
    }

    fn run_iperf_test_internal(&self, config: &IperfConfig) -> Result<IperfResult, ProfilingError> {
        let mut cmd = Command::new("iperf3");
        cmd.arg("-c")
            .arg(&config.server)
            .arg("-p")
            .arg(config.port.to_string())
            .arg("-t")
            .arg(config.duration.as_secs().to_string())
            .arg("-P")
            .arg(config.parallel_streams.to_string())
            .arg("-J") // JSON output
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());

        if config.protocol == Protocol::Udp {
            cmd.arg("-u");
            if let Some(limit) = config.bandwidth_limit {
                cmd.arg("-b").arg(format!("{}M", limit / 1_000_000));
            }
        }

        if let Some(window) = config.window_size {
            cmd.arg("-w").arg(format!("{}K", window / 1024));
        }

        if config.reverse {
            cmd.arg("-R");
        }

        logging::log_debug(&format!("Running iperf3: {:?}", cmd));

        let output = cmd.output().map_err(|e| {
            ProfilingError::Iperf3Failed(format!("Failed to execute iperf3: {}", e))
        })?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            if stderr.contains("unable to connect to server") {
                return Err(ProfilingError::Timeout);
            }
            return Err(ProfilingError::Iperf3Failed(stderr.to_string()));
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        self.parse_iperf_json(&stdout, config)
    }

    fn parse_iperf_json(
        &self,
        json: &str,
        config: &IperfConfig,
    ) -> Result<IperfResult, ProfilingError> {
        let value: serde_json::Value = serde_json::from_str(json).map_err(|e| {
            ProfilingError::ParseError(format!(
                "Failed to parse iperf3 JSON output: {}. Raw output (first 500 chars): {}",
                e,
                json.chars().take(500).collect::<String>()
            ))
        })?;

        let end_sum = value.get("end").ok_or_else(|| {
            ProfilingError::ParseError("Missing 'end' in iperf3 output".to_string())
        })?;

        let sum_received = end_sum.get("sum_received");
        let sum_sent = end_sum.get("sum_sent");

        let (bandwidth_bps, retransmits) = if let Some(sum) = sum_received.or(sum_sent) {
            let bits_per_second = sum
                .get("bits_per_second")
                .and_then(|v| v.as_f64())
                .ok_or_else(|| ProfilingError::ParseError("Missing bits_per_second".to_string()))?;

            let retransmits = sum
                .get("retransmits")
                .and_then(|v| v.as_u64())
                .map(|v| v as u32);

            (bits_per_second, retransmits)
        } else {
            return Err(ProfilingError::ParseError(
                "Missing sum data in iperf3 output".to_string(),
            ));
        };

        let (jitter_ms, packet_loss_percent) = if config.protocol == Protocol::Udp {
            let jitter = sum_received
                .and_then(|s| s.get("jitter_ms"))
                .and_then(|v| v.as_f64());

            let lost = sum_received
                .and_then(|s| s.get("lost_packets"))
                .and_then(|v| v.as_u64())
                .unwrap_or(0);

            let total = sum_received
                .and_then(|s| s.get("packets"))
                .and_then(|v| v.as_u64())
                .unwrap_or(1);

            let packet_loss = if total > 0 {
                Some((lost as f64 / total as f64) * 100.0)
            } else {
                None
            };

            (jitter, packet_loss)
        } else {
            (None, None)
        };

        Ok(IperfResult {
            config: config.clone(),
            bandwidth_bps,
            retransmits,
            jitter_ms,
            packet_loss_percent,
            duration: config.duration,
        })
    }
}
