// Host networking probes.

use std::net::{IpAddr, Ipv6Addr, SocketAddr, UdpSocket};
use std::sync::OnceLock;

/// Returns true if the host has IPv6 connectivity (in the routable
/// sense — a non-loopback, non-unspecified global v6 source address
/// is chosen when connecting to a known v6 destination).
///
/// Identical logic to the worker-side probe (kept in
/// `bin/worker.rs::probe_host_ipv6_route`) so the worker still has
/// a self-contained fallback when invoked directly without the lib
/// pre-setting `SUPERMACHINE_HOST_IPV6`. The lib should call this
/// helper and pass the result as an env var on every worker spawn
/// to avoid the per-spawn probe round-trip (~1-3 ms).
///
/// Caching: result is host-static for the lib process lifetime.
/// If a long-lived embedder needs to refresh after a network
/// change, restart the process.
pub fn cached_host_ipv6_route() -> bool {
    static CACHE: OnceLock<bool> = OnceLock::new();
    *CACHE.get_or_init(probe_host_ipv6_route_uncached)
}

/// `cached_host_ipv6_route` with the `SUPERMACHINE_HOST_IPV6` env
/// override honored first — the same precedence the worker binary
/// uses. The lib sets that var on every worker spawn, so inside a
/// worker process this returns the parent's probe result without
/// re-probing; in-process backends (KVM) fall through to the cached
/// probe. Read per call (not cached) so tests can flip the env var.
pub fn host_ipv6_route() -> bool {
    match std::env::var("SUPERMACHINE_HOST_IPV6").as_deref() {
        Ok("1") | Ok("true") => true,
        Ok("0") | Ok("false") => false,
        _ => cached_host_ipv6_route(),
    }
}

fn probe_host_ipv6_route_uncached() -> bool {
    let Ok(sock) = UdpSocket::bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0)) else {
        return false;
    };
    // Cloudflare 2606:4700:4700::1111 is anycast, present on every
    // IPv6 transit. `connect` is non-blocking on UDP — just sets
    // up routing so getsockname returns the chosen v6 source.
    let target = SocketAddr::new(
        IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111)),
        53,
    );
    sock.connect(target).is_ok()
        && sock
            .local_addr()
            .map(|a| match a.ip() {
                IpAddr::V6(v6) => !v6.is_unspecified() && !v6.is_loopback(),
                _ => false,
            })
            .unwrap_or(false)
}
