"""Model discovery endpoints."""

import ipaddress
import socket
from urllib.parse import urlparse

import httpx
from fastapi import APIRouter, Query
from pydantic import BaseModel

router = APIRouter()

# Set of private/dangerous networks to block in SSRF validation.
# Loopback is allowed conditionally (when the server itself runs on localhost).
_BLOCKED_NETWORKS = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("169.254.0.0/16"),  # link-local
    ipaddress.ip_network("fc00::/7"),  # IPv6 unique-local
    ipaddress.ip_network("fe80::/10"),  # IPv6 link-local
]

_LOOPBACK_NETWORKS = [
    ipaddress.ip_network("127.0.0.0/8"),
    ipaddress.ip_network("::1/128"),
]


def _is_server_on_localhost() -> bool:
    """Check whether the server is configured to bind to localhost."""
    from ieum.server.config import default_config
    return default_config.host in {"127.0.0.1", "localhost", "::1"}


def _validate_base_url(base_url: str) -> str | None:
    """Validate a base_url for SSRF safety.

    Returns an error message string if the URL is blocked, or None if safe.
    """
    try:
        parsed = urlparse(base_url)
    except Exception:
        return "Invalid URL format"

    # Only allow http/https schemes
    if parsed.scheme not in ("http", "https"):
        return f"Disallowed URL scheme: {parsed.scheme}. Only http and https are allowed."

    hostname = parsed.hostname
    if not hostname:
        return "URL must include a hostname"

    # Resolve hostname to IP addresses
    try:
        addr_infos = socket.getaddrinfo(hostname, parsed.port or 80, proto=socket.IPPROTO_TCP)
    except socket.gaierror:
        return f"Cannot resolve hostname: {hostname}"

    server_is_localhost = _is_server_on_localhost()

    for family, _type, _proto, _canonname, sockaddr in addr_infos:
        ip = ipaddress.ip_address(sockaddr[0])

        # Check loopback -- allowed only if server itself is on localhost
        for net in _LOOPBACK_NETWORKS:
            if ip in net and not server_is_localhost:
                return f"Loopback addresses are not allowed when server is not on localhost"

        # Block private/link-local ranges unconditionally
        for net in _BLOCKED_NETWORKS:
            if ip in net:
                return f"Access to private/internal network addresses is not allowed"

    return None


class ModelInfo(BaseModel):
    """Model information."""
    id: str
    name: str


class ModelsResponse(BaseModel):
    """Models list response."""
    models: list[ModelInfo]
    error: str | None = None


@router.get("/models", response_model=ModelsResponse)
async def list_models(
    provider: str = Query(..., description="Provider type: ollama, vllm, lmstudio"),
    base_url: str = Query(..., description="Base URL of the provider"),
) -> ModelsResponse:
    """Fetch available models from a provider.

    Args:
        provider: Provider type (ollama, vllm, lmstudio).
        base_url: Base URL of the provider server.

    Returns:
        List of available models.
    """
    # SSRF validation: block requests to private/internal networks
    url_error = _validate_base_url(base_url)
    if url_error:
        return ModelsResponse(models=[], error=url_error)

    try:
        async with httpx.AsyncClient(timeout=10.0) as client:
            if provider == "ollama":
                # Ollama API: GET /api/tags
                response = await client.get(f"{base_url.rstrip('/')}/api/tags")
                response.raise_for_status()
                data = response.json()
                models = [
                    ModelInfo(id=m["name"], name=m["name"])
                    for m in data.get("models", [])
                ]

            elif provider == "vllm":
                # vLLM uses OpenAI-compatible API: GET /v1/models
                response = await client.get(f"{base_url.rstrip('/')}/v1/models")
                response.raise_for_status()
                data = response.json()
                models = [
                    ModelInfo(id=m["id"], name=m["id"])
                    for m in data.get("data", [])
                ]

            elif provider == "lmstudio":
                # LM Studio uses OpenAI-compatible API: GET /v1/models
                response = await client.get(f"{base_url.rstrip('/')}/v1/models")
                response.raise_for_status()
                data = response.json()
                models = [
                    ModelInfo(id=m["id"], name=m["id"])
                    for m in data.get("data", [])
                ]

            else:
                return ModelsResponse(models=[], error=f"Unknown provider: {provider}")

            return ModelsResponse(models=models)

    except httpx.ConnectError:
        return ModelsResponse(models=[], error=f"Cannot connect to {base_url}")
    except httpx.TimeoutException:
        return ModelsResponse(models=[], error="Connection timed out")
    except Exception as e:
        return ModelsResponse(models=[], error=str(e))
