"""Outbound SSRF prevention for REMOTE_HTTP tool execution.

IP allowlisting of executor connections is enforced **server-side** by
the Executor Gateway — it reads the real source IP from the HTTP/WebSocket
connection headers (X-Forwarded-For, remote address), not from any
client-reported field.

This module handles one client-side concern:
  SSRF prevention — block outbound HTTP calls from REMOTE_HTTP tool
  execution to cloud metadata / internal endpoints.

Configuration:
  QRAPTOR_BLOCKED_OUTBOUND_CIDRS=169.254.0.0/16,10.0.0.0/8
"""

from __future__ import annotations

import ipaddress
import logging
import os
import socket
from urllib.parse import urlparse

logger = logging.getLogger("qremotex.ip_allowlist")


class OutboundBlocklistConfig:
    """SSRF prevention config for outbound HTTP tool calls."""

    def __init__(self) -> None:
        # Always block cloud metadata + link-local by default
        default_blocked = "169.254.169.254/32,fd00::/8"
        raw_blocked = os.environ.get("QRAPTOR_BLOCKED_OUTBOUND_CIDRS", default_blocked)
        self.blocked_networks: list[ipaddress.IPv4Network | ipaddress.IPv6Network] = (
            _parse_networks(raw_blocked) if raw_blocked.strip() else []
        )

    @property
    def enabled(self) -> bool:
        return len(self.blocked_networks) > 0


def _parse_networks(raw: str) -> list[ipaddress.IPv4Network | ipaddress.IPv6Network]:
    """Parse comma-separated IPs/CIDRs into network objects."""
    networks = []
    for entry in raw.split(","):
        entry = entry.strip()
        if not entry:
            continue
        try:
            networks.append(ipaddress.ip_network(entry, strict=False))
        except ValueError:
            logger.warning("Invalid IP/CIDR in blocklist config: %s (skipped)", entry)
    return networks


def validate_outbound_target(url: str, config: OutboundBlocklistConfig) -> bool:
    """Check that an outbound HTTP target is not in the blocklist.

    Used by REMOTE_HTTP tool executor to prevent SSRF attacks against
    cloud metadata endpoints, internal services, etc.

    Returns True if the target is safe (not blocked).
    Returns False if the target resolves to a blocked IP.
    """
    if not config.enabled:
        return True

    host = urlparse(url).hostname
    if not host:
        return False

    try:
        addr_info = socket.getaddrinfo(host, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
        resolved_ips = {info[4][0] for info in addr_info}
    except socket.gaierror:
        logger.warning("DNS resolution failed for outbound target '%s': allowing", host)
        return True

    for ip_str in resolved_ips:
        ip = ipaddress.ip_address(ip_str)
        for network in config.blocked_networks:
            if ip in network:
                logger.warning(
                    "Outbound target %s resolves to blocked IP %s (in %s). SSRF prevention.",
                    url,
                    ip_str,
                    network,
                )
                return False

    return True
