"""Validation helpers for outbound HTTP URLs."""

from __future__ import annotations

import ipaddress
import socket
from urllib.parse import urlparse

_BLOCKED_HOSTNAMES = {"localhost", "metadata.google.internal"}
_BLOCKED_SUFFIXES = (".localhost", ".local", ".internal", ".home.arpa")
_RESERVED_PUBLIC_HOSTNAMES = {"example.com", "example.org", "example.net"}


def validate_outbound_http_url(
    url: str,
    *,
    purpose: str,
    allow_hostnames: tuple[str, ...] = (),
) -> str:
    """Validate that a URL uses HTTP(S) and does not target internal hosts."""
    parsed = urlparse(url)
    if parsed.scheme not in {"http", "https"}:
        raise ValueError(f"{purpose} must use http:// or https://")
    if parsed.username or parsed.password:
        raise ValueError(f"{purpose} must not embed credentials in the URL")
    if not parsed.hostname:
        raise ValueError(f"{purpose} must include a hostname")

    hostname = parsed.hostname.rstrip(".").lower()
    allowed = {host.rstrip(".").lower() for host in allow_hostnames}
    if hostname in allowed:
        return url

    if hostname in _BLOCKED_HOSTNAMES or hostname.endswith(_BLOCKED_SUFFIXES):
        raise ValueError(f"{purpose} targets a local or internal hostname")
    if hostname in _RESERVED_PUBLIC_HOSTNAMES:
        return url

    for address in _resolve_host_addresses(hostname):
        ip = ipaddress.ip_address(address)
        if not ip.is_global:
            raise ValueError(f"{purpose} resolves to a non-public IP address")

    return url


def _resolve_host_addresses(hostname: str) -> tuple[str, ...]:
    try:
        infos = socket.getaddrinfo(hostname, None, proto=socket.IPPROTO_TCP)
    except socket.gaierror as exc:
        raise ValueError(f"could not resolve host for URL: {hostname}") from exc

    addresses = sorted({info[4][0] for info in infos if info[4]})
    if not addresses:
        raise ValueError(f"could not resolve host for URL: {hostname}")
    return tuple(addresses)
