from __future__ import annotations

import ipaddress
import re
from urllib.parse import urlparse

CVE_RE = re.compile(r"^CVE-\d{4}-\d{4,}$")
SAFE_KEYWORD_RE = re.compile(r"^[a-zA-Z0-9\s\-_.]{1,200}$")
ALLOWED_HOSTS = frozenset(
    {
        "services.nvd.nist.gov",
        "api.osv.dev",
        "api.first.org",
        "www.cisa.gov",
        "raw.githubusercontent.com",
        "api.abuseipdb.com",
        "api.greynoise.io",
        "api.shodan.io",
        "mb-api.abuse.ch",
        "www.virustotal.com",
        "urlscan.io",
        "crt.sh",
        "www.circl.lu",
        "api.msrc.microsoft.com",
        "access.redhat.com",
        "ubuntu.com",
        "api.github.com",
        "threatfox-api.abuse.ch",
        "api.ransomwhe.re",
        "gitlab.com",
        "cveawg.mitre.org",
    }
)


def normalize_cve(cve_id: str) -> str | None:
    s = cve_id.strip().upper()
    return s if CVE_RE.match(s) else None


def sanitize_keyword(query: str) -> str | None:
    q = query.strip()
    return q if SAFE_KEYWORD_RE.match(q) else None


def validate_url_host(url: str) -> None:
    host = urlparse(url).hostname
    if host not in ALLOWED_HOSTS:
        raise ValueError(f"Blocked request to unauthorized host: {host}")


_SENSITIVE_PARAMS = re.compile(
    r"((?:apikey|api_key|key|token|access_token|secret)=)[^&]+",
    re.IGNORECASE,
)


def sanitize_url_for_log(url: str) -> str:
    return _SENSITIVE_PARAMS.sub(r"\1***REDACTED***", url)


def validate_ip_address(ip: str) -> str:
    """Validate and return a normalized IP address.

    Raises ValueError for invalid or private/reserved addresses.
    """
    addr = ipaddress.ip_address(ip.strip())
    if addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_reserved:
        raise ValueError(f"Private/reserved IP not allowed: {ip}")
    return str(addr)


def validate_hash(hash_str: str) -> str | None:
    """Validate MD5 (32 hex), SHA1 (40 hex), or SHA256 (64 hex) hash."""
    h = hash_str.strip().lower()
    if len(h) in (32, 40, 64) and all(c in "0123456789abcdef" for c in h):
        return h
    return None
