use std::net::{IpAddr, SocketAddr, TcpStream, ToSocketAddrs};
use std::process::Command;
use std::time::Duration;

use anyhow::Result;

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

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

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

    /// Checks if a TCP port is open on a host using an IP address.
    pub fn check_port<A: Into<IpAddr>>(&self, host: A, port: u16) -> Result<bool> {
        let addr = SocketAddr::new(host.into(), port);
        match TcpStream::connect_timeout(&addr, self.timeout) {
            Ok(_) => Ok(true),
            Err(_) => Ok(false),
        }
    }

    /// Checks if a TCP port is open on a host using a hostname string.
    pub fn check_port_by_name(&self, host: &str, port: u16) -> Result<bool> {
        let addr_str = format!("{}:{}", host, port);

        // Try to resolve and connect
        match addr_str.to_socket_addrs() {
            Ok(mut addrs) => {
                if let Some(addr) = addrs.next() {
                    match TcpStream::connect_timeout(&addr, self.timeout) {
                        Ok(_) => Ok(true),
                        Err(_) => Ok(false),
                    }
                } else {
                    Ok(false)
                }
            }
            Err(_) => Ok(false),
        }
    }

    /// Checks if multiple TCP ports are open on a host.
    pub 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)?;
            results.push((port, is_open));
        }

        Ok(results)
    }

    /// Checks if a host is reachable on the network using ICMP ping.
    pub fn ping(&self, host: &str) -> Result<bool> {
        let timeout_secs = self.timeout.as_secs().to_string();

        let mut cmd = Command::new("ping");

        #[cfg(target_os = "windows")]
        {
            cmd.arg("-n").arg("1").arg("-w").arg(&timeout_secs);
        }

        #[cfg(not(target_os = "windows"))]
        {
            cmd.arg("-c").arg("1").arg("-W").arg(&timeout_secs);
        }

        cmd.arg(host);

        match cmd.output() {
            Ok(output) => Ok(output.status.success()),
            Err(_) => Ok(false),
        }
    }
}

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