use std::net::{IpAddr, Ipv6Addr};

use anyhow::{Result, bail};

pub fn ensure_safe_ip(ip: IpAddr) -> Result<()> {
    if is_blocked_ip(ip) {
        bail!("blocked potentially unsafe IP address `{ip}`");
    }
    Ok(())
}

pub fn is_blocked_ip(ip: IpAddr) -> bool {
    match ip {
        IpAddr::V4(v4) => {
            v4.is_private()
                || v4.is_loopback()
                || v4.is_link_local()
                || v4.is_multicast()
                || v4.is_broadcast()
                || v4.is_unspecified()
                || is_ipv4_reserved_range(v4)
                || v4.is_documentation()
                || is_ipv4_shared_space(v4)
                || is_ipv4_benchmarking(v4)
                || is_ipv4_ietf_protocol_assignments(v4)
                || v4.octets() == [169, 254, 169, 254]
                || v4.octets() == [100, 100, 100, 200]
        }
        IpAddr::V6(v6) => {
            if let Some(mapped) = v6.to_ipv4_mapped() {
                return is_blocked_ip(IpAddr::V4(mapped));
            }

            v6.is_loopback()
                || v6.is_unspecified()
                || v6.is_multicast()
                || v6.is_unicast_link_local()
                || v6.is_unique_local()
                || is_ipv6_site_local(v6)
                || is_ipv6_documentation(v6)
                || is_ipv6_metadata(v6)
        }
    }
}

fn is_ipv4_shared_space(ip: std::net::Ipv4Addr) -> bool {
    let octets = ip.octets();
    octets[0] == 100 && (octets[1] & 0b1100_0000) == 0b0100_0000
}

fn is_ipv4_reserved_range(ip: std::net::Ipv4Addr) -> bool {
    ip.octets()[0] >= 240
}

fn is_ipv4_benchmarking(ip: std::net::Ipv4Addr) -> bool {
    let octets = ip.octets();
    octets[0] == 198 && (octets[1] == 18 || octets[1] == 19)
}

fn is_ipv4_ietf_protocol_assignments(ip: std::net::Ipv4Addr) -> bool {
    let octets = ip.octets();
    octets[0] == 192 && octets[1] == 0 && octets[2] == 0
}

fn is_ipv6_documentation(ip: Ipv6Addr) -> bool {
    ip.segments()[0] == 0x2001 && ip.segments()[1] == 0x0db8
}

fn is_ipv6_site_local(ip: Ipv6Addr) -> bool {
    (ip.segments()[0] & 0xffc0) == 0xfec0
}

fn is_ipv6_metadata(ip: Ipv6Addr) -> bool {
    // Common metadata aliases used by cloud providers.
    ip == Ipv6Addr::new(0xfd00, 0xec2, 0, 0, 0, 0, 0, 0x254)
        || ip == Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xa9fe, 0xa9fe)
}
