use std::{ops::Deref, sync::Arc};

use crate::rpc::peer::GetIpListResponse;
use pnet::datalink::NetworkInterface;
use tokio::{
    sync::{Mutex, RwLock},
    task::JoinSet,
};

use super::netns::NetNS;

pub const CACHED_IP_LIST_TIMEOUT_SEC: u64 = 60;

struct InterfaceFilter {
    iface: NetworkInterface,
}

#[cfg(target_os = "linux")]
impl InterfaceFilter {
    async fn is_tun_tap_device(&self) -> bool {
        let path = format!("/sys/class/net/{}/tun_flags", self.iface.name);
        tokio::fs::metadata(&path).await.is_ok()
    }

    async fn has_valid_ip(&self) -> bool {
        self.iface
            .ips
            .iter()
            .map(|ip| ip.ip())
            .any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
    }

    async fn filter_iface(&self) -> bool {
        tracing::trace!(
            "filter linux iface: {:?}, is_point_to_point: {}, is_loopback: {}, is_up: {}, is_lower_up: {}, is_tun: {}, has_valid_ip: {}",
            self.iface,
            self.iface.is_point_to_point(),
            self.iface.is_loopback(),
            self.iface.is_up(),
            self.iface.is_lower_up(),
            self.is_tun_tap_device().await,
            self.has_valid_ip().await
        );

        !self.iface.is_point_to_point()
            && !self.iface.is_loopback()
            && self.iface.is_up()
            && self.iface.is_lower_up()
            && !self.is_tun_tap_device().await
            && self.has_valid_ip().await
    }
}

#[cfg(target_os = "macos")]
impl InterfaceFilter {
    async fn is_interface_physical(interface_name: &str) -> bool {
        let output = tokio::process::Command::new("networksetup")
            .args(&["-listallhardwareports"])
            .output()
            .await
            .expect("Failed to execute command");

        let stdout = std::str::from_utf8(&output.stdout).expect("Invalid UTF-8");

        let lines: Vec<&str> = stdout.lines().collect();

        for i in 0..lines.len() {
            let line = lines[i];

            if line.contains("Device:") && line.contains(interface_name) {
                let next_line = lines[i + 1];
                if next_line.contains("Virtual Interface") {
                    return false;
                } else {
                    return true;
                }
            }
        }

        false
    }

    async fn filter_iface(&self) -> bool {
        !self.iface.is_point_to_point()
            && !self.iface.is_loopback()
            && self.iface.is_up()
            && Self::is_interface_physical(&self.iface.name).await
    }
}

#[cfg(target_os = "windows")]
impl InterfaceFilter {
    async fn filter_iface(&self) -> bool {
        tracing::debug!(
            "iface_name: {:?}, p2p: {:?}, is_up: {:?}, iface: {:?}",
            self.iface.name,
            self.iface.is_point_to_point(),
            self.iface.is_up(),
            self.iface
        );
        !self.iface.is_point_to_point()
            && !self.iface.is_loopback()
            && self
                .iface
                .ips
                .iter()
                .map(|ip| ip.ip())
                .any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
            && self.iface.mac.map(|mac| !mac.is_zero()).unwrap_or(false)
    }
}

pub async fn local_ipv4() -> std::io::Result<std::net::Ipv4Addr> {
    let socket = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
    socket.connect("8.8.8.8:80").await?;
    let addr = socket.local_addr()?;
    match addr.ip() {
        std::net::IpAddr::V4(ip) => Ok(ip),
        std::net::IpAddr::V6(_) => Err(std::io::Error::new(
            std::io::ErrorKind::AddrNotAvailable,
            "no ipv4 address",
        )),
    }
}

pub async fn local_ipv6() -> std::io::Result<std::net::Ipv6Addr> {
    let socket = tokio::net::UdpSocket::bind("[::]:0").await?;
    socket
        .connect("[2001:4860:4860:0000:0000:0000:0000:8888]:80")
        .await?;
    let addr = socket.local_addr()?;
    match addr.ip() {
        std::net::IpAddr::V6(ip) => Ok(ip),
        std::net::IpAddr::V4(_) => Err(std::io::Error::new(
            std::io::ErrorKind::AddrNotAvailable,
            "no ipv4 address",
        )),
    }
}

pub struct IPCollector {
    cached_ip_list: Arc<RwLock<GetIpListResponse>>,
    collect_ip_task: Mutex<JoinSet<()>>,
    net_ns: NetNS,
}

impl IPCollector {
    pub fn new(net_ns: NetNS) -> Self {
        Self {
            cached_ip_list: Arc::new(RwLock::new(GetIpListResponse::new())),
            collect_ip_task: Mutex::new(JoinSet::new()),
            net_ns,
        }
    }

    pub async fn collect_ip_addrs(&self) -> GetIpListResponse {
        let mut task = self.collect_ip_task.lock().await;
        if task.is_empty() {
            let cached_ip_list = self.cached_ip_list.clone();
            *cached_ip_list.write().await =
                Self::do_collect_ip_addrs(false, self.net_ns.clone()).await;
            let net_ns = self.net_ns.clone();
            task.spawn(async move {
                loop {
                    let ip_addrs = Self::do_collect_ip_addrs(true, net_ns.clone()).await;
                    *cached_ip_list.write().await = ip_addrs;
                    tokio::time::sleep(std::time::Duration::from_secs(CACHED_IP_LIST_TIMEOUT_SEC))
                        .await;
                }
            });
        }

        return self.cached_ip_list.read().await.deref().clone();
    }

    pub async fn collect_interfaces(net_ns: NetNS) -> Vec<NetworkInterface> {
        let _g = net_ns.guard();
        let ifaces = pnet::datalink::interfaces();
        let mut ret = vec![];
        for iface in ifaces {
            let f = InterfaceFilter {
                iface: iface.clone(),
            };

            if !f.filter_iface().await {
                continue;
            }

            ret.push(iface);
        }

        ret
    }

    #[tracing::instrument(skip(net_ns))]
    async fn do_collect_ip_addrs(with_public: bool, net_ns: NetNS) -> GetIpListResponse {
        let mut ret = crate::rpc::peer::GetIpListResponse::new();

        if with_public {
            if let Some(v4_addr) =
                public_ip::addr_with(public_ip::http::ALL, public_ip::Version::V4).await
            {
                ret.public_ipv4 = v4_addr.to_string();
            }

            if let Some(v6_addr) = public_ip::addr_v6().await {
                ret.public_ipv6 = v6_addr.to_string();
            }
        }

        let ifaces = Self::collect_interfaces(net_ns.clone()).await;
        let _g = net_ns.guard();
        for iface in ifaces {
            for ip in iface.ips {
                let ip: std::net::IpAddr = ip.ip();
                if ip.is_loopback() || ip.is_multicast() {
                    continue;
                }
                if ip.is_ipv4() {
                    ret.interface_ipv4s.push(ip.to_string());
                } else if ip.is_ipv6() {
                    ret.interface_ipv6s.push(ip.to_string());
                }
            }
        }

        if let Ok(v4_addr) = local_ipv4().await {
            tracing::trace!("got local ipv4: {}", v4_addr);
            if !ret.interface_ipv4s.contains(&v4_addr.to_string()) {
                ret.interface_ipv4s.push(v4_addr.to_string());
            }
        }

        if let Ok(v6_addr) = local_ipv6().await {
            tracing::trace!("got local ipv6: {}", v6_addr);
            if !ret.interface_ipv6s.contains(&v6_addr.to_string()) {
                ret.interface_ipv6s.push(v6_addr.to_string());
            }
        }

        ret
    }
}
