"""HTTP client for the EIP API — used by MCP tool handlers.

All methods are synchronous (MCP tool handlers run in sync context).
Timeouts, TLS verification, and base URL are hardcoded for security.
"""

from __future__ import annotations

import os
from typing import Any
from urllib.parse import quote, urljoin, urlparse

import httpx

from eip_mcp import __version__

API_BASE_URL = "https://exploit-intel.com"
USER_AGENT = f"eip-mcp/{__version__}"
TIMEOUT = httpx.Timeout(connect=10.0, read=30.0, write=10.0, pool=10.0)


_BASE = urlparse(API_BASE_URL)
_BASE_HOST = _BASE.hostname or ""
_BASE_PORT = _BASE.port or (443 if _BASE.scheme == "https" else None)

# Allow the canonical host and its www/non-www variant.
_ALLOWED_REDIRECT_HOSTS = {
    _BASE_HOST,
    f"www.{_BASE_HOST}" if _BASE_HOST and not _BASE_HOST.startswith("www.") else "",
    _BASE_HOST[len("www.") :] if _BASE_HOST.startswith("www.") else "",
}
_ALLOWED_REDIRECT_HOSTS.discard("")

_MAX_REDIRECTS = 3


class APIError(Exception):
    """Raised on non-2xx responses."""

    def __init__(self, status_code: int, message: str):
        self.status_code = status_code
        self.message = message
        super().__init__(message)


def _headers() -> dict[str, str]:
    headers = {"User-Agent": USER_AGENT, "Accept": "application/json"}
    api_key = os.environ.get("EIP_API_KEY")
    if api_key:
        headers["X-API-Key"] = api_key
    return headers


def _url(path: str) -> str:
    return f"{API_BASE_URL}{path}"


def _safe_path_segment(segment: str) -> str:
    """Percent-encode a single URL path segment."""
    return quote(segment, safe="")


def _safe_redirect(current_url: str, location: str) -> str:
    """Resolve and validate redirects.

    We only follow redirects to the same trusted host (including www/non-www).
    This prevents leaking the API key via headers to an unexpected destination.
    """
    next_url = urljoin(current_url, location)
    parsed = urlparse(next_url)
    if parsed.scheme != "https":
        raise APIError(302, "Unexpected redirect to non-HTTPS destination")
    if parsed.hostname not in _ALLOWED_REDIRECT_HOSTS:
        raise APIError(302, "Unexpected redirect to untrusted host")
    port = parsed.port or 443
    if _BASE_PORT and port != _BASE_PORT:
        raise APIError(302, "Unexpected redirect to untrusted port")
    return next_url


def _handle(resp: httpx.Response) -> dict[str, Any]:
    # We normally handle redirects ourselves.
    if 300 <= resp.status_code < 400:
        loc = resp.headers.get("Location", "")
        raise APIError(resp.status_code, f"Unexpected redirect. Location='{loc}'")
    if resp.status_code == 404:
        raise APIError(404, "Not found")
    if resp.status_code == 429:
        retry = resp.headers.get("Retry-After", "60")
        raise APIError(429, f"Rate limited. Retry after {retry}s.")
    if resp.status_code == 403:
        raise APIError(403, "Request blocked (likely contains suspicious patterns). Try rephrasing.")
    if resp.status_code >= 400:
        raise APIError(resp.status_code, f"HTTP {resp.status_code}")
    try:
        return resp.json()
    except Exception:
        raise APIError(resp.status_code, "Invalid JSON response from API")


def _request_json(path: str, *, params: dict[str, Any] | None = None) -> dict[str, Any]:
    """Perform a GET request and return decoded JSON.

    Uses a conservative redirect policy:
    - do not automatically follow redirects
    - follow at most _MAX_REDIRECTS redirects to the same trusted host
    """
    url = _url(path)

    with httpx.Client(
        timeout=TIMEOUT,
        headers=_headers(),
        follow_redirects=False,
        trust_env=False,
    ) as c:
        try:
            resp = c.get(url, params=params)
            redirects = 0
            while resp.status_code in (301, 302, 303, 307, 308) and resp.headers.get("Location"):
                if redirects >= _MAX_REDIRECTS:
                    raise APIError(resp.status_code, "Too many redirects")
                url = _safe_redirect(str(resp.request.url), resp.headers["Location"])
                redirects += 1
                resp = c.get(url)
        except httpx.RequestError as exc:
            raise APIError(0, f"Network error contacting API: {exc.__class__.__name__}")

    return _handle(resp)


# ---------------------------------------------------------------------------
# API methods
# ---------------------------------------------------------------------------

def search_vulns(params: dict[str, Any]) -> dict[str, Any]:
    """Search vulnerabilities. Returns raw API response dict."""
    clean = {k: v for k, v in params.items() if v is not None}
    return _request_json("/api/v1/vulns", params=clean)


def get_vuln_detail(cve_id: str) -> dict[str, Any]:
    """Get full vulnerability detail. Returns raw API response dict."""
    cve_seg = _safe_path_segment(cve_id)
    return _request_json(f"/api/v1/vulns/{cve_seg}")


def list_exploit_files(exploit_id: int) -> list[dict[str, Any]]:
    """List files in an exploit archive."""
    data = _request_json(f"/api/v1/exploits/{exploit_id}/files")
    return data.get("files", [])


def get_exploit_code(exploit_id: int, file_path: str) -> str:
    """Get source code content for a file in an exploit."""
    data = _request_json(f"/api/v1/exploits/{exploit_id}/code", params={"file": file_path})
    return data.get("content", "")


def get_stats() -> dict[str, Any]:
    """Get platform-wide statistics."""
    return _request_json("/api/v1/stats")


def get_health() -> dict[str, Any]:
    """Get health check info."""
    return _request_json("/api/v1/health")


def search_exploits(params: dict[str, Any]) -> dict[str, Any]:
    """Search/browse exploits with filters."""
    clean = {k: v for k, v in params.items() if v is not None}
    return _request_json("/api/v1/exploits", params=clean)


def list_authors(params: dict[str, Any]) -> dict[str, Any]:
    """List exploit authors."""
    clean = {k: v for k, v in params.items() if v is not None}
    return _request_json("/api/v1/authors", params=clean)


def get_author(name: str) -> dict[str, Any]:
    """Get author profile with their exploits."""
    name_seg = _safe_path_segment(name)
    return _request_json(f"/api/v1/authors/{name_seg}")


def list_cwes() -> dict[str, Any]:
    """List CWE categories with vuln counts."""
    return _request_json("/api/v1/cwe")


def get_cwe(cwe_id: str) -> dict[str, Any]:
    """Get CWE detail."""
    cwe_seg = _safe_path_segment(cwe_id)
    return _request_json(f"/api/v1/cwe/{cwe_seg}")


def list_vendors() -> dict[str, Any]:
    """List vendors with vuln counts."""
    return _request_json("/api/v1/vendors")


def list_vendor_products(vendor: str) -> dict[str, Any]:
    """List products for a specific vendor."""
    vendor_seg = _safe_path_segment(vendor)
    return _request_json(f"/api/v1/vendors/{vendor_seg}/products")


def get_exploit_analysis(exploit_id: int) -> dict[str, Any]:
    """Get a single exploit with its LLM analysis and CVE context."""
    return _request_json(f"/api/v1/exploits/{exploit_id}")


def lookup_alt_id(alt_id: str) -> dict[str, Any]:
    """Look up a vulnerability by alternate ID (EDB-XXXXX, GHSA-XXXXX)."""
    return _request_json("/api/v1/lookup", params={"alt_id": alt_id})
