use std::net::{IpAddr, SocketAddr};
use std::time::Duration;

use anyhow::Result;
use tokio::net::TcpStream;
use tokio::time::timeout;

/// TCP Connectivity module for checking TCP connections
pub struct TcpConnector {
    timeout: Duration,
}

impl TcpConnector {
    /// Create a new TCP connector with the default timeout (5 seconds)
    pub fn new() -> Self {
        Self {
            timeout: Duration::from_secs(5),
        }
    }

    /// Create a new TCP connector with a custom timeout
    pub fn with_timeout(timeout: Duration) -> Self {
        Self { timeout }
    }

    /// Check if a TCP port is open on a host
    pub async fn check_port<A: Into<IpAddr>>(&self, host: A, port: u16) -> Result<bool> {
        let addr = SocketAddr::new(host.into(), port);
        let connect_future = TcpStream::connect(addr);

        match timeout(self.timeout, connect_future).await {
            Ok(Ok(_)) => Ok(true),
            Ok(Err(_)) => Ok(false),
            Err(_) => Ok(false), // Timeout occurred
        }
    }

    /// Check if multiple TCP ports are open on a host
    pub async fn check_ports<A: Into<IpAddr> + Clone>(
        &self,
        host: A,
        ports: &[u16],
    ) -> Result<Vec<(u16, bool)>> {
        let mut results = Vec::with_capacity(ports.len());

        for &port in ports {
            let is_open = self.check_port(host.clone(), port).await?;
            results.push((port, is_open));
        }

        Ok(results)
    }

    /// Check if a host is reachable on the network using ICMP ping
    pub async fn ping<S: AsRef<str>>(&self, host: S) -> Result<bool> {
        // Convert to owned strings to avoid borrowing issues
        let host_str = host.as_ref().to_string();
        let timeout_secs = self.timeout.as_secs().to_string();

        // Run the ping command with explicit arguments
        let status = tokio::process::Command::new("ping")
            .arg("-c")
            .arg("1") // Just one ping
            .arg("-W")
            .arg(timeout_secs) // Timeout in seconds
            .arg(host_str) // Host to ping
            .output()
            .await?;

        Ok(status.status.success())
    }
}

impl Default for TcpConnector {
    fn default() -> Self {
        Self::new()
    }
}
