"""Generic authenticated HTTP connector — the universal escape hatch.

Unlike the built-in read-only ``http.request`` tool, this connector can make
authenticated requests with any method, so its tool is marked sensitive (it can
write) and is gated by the policy engine.
"""

from __future__ import annotations

import httpx

from legate.agent.tooling import ToolContext, ToolResult, ToolSpec
from legate.connectors.base import BaseConnector, ConnectionResult, ConnectorConfigError
from legate.connectors.runtime import new_async_client
from legate.security.ssrf import SSRFError, validate_public_url

_MAX_BODY_CHARS = 8_000
_METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE"}


class HttpConnector(BaseConnector):
    type = "http"
    title = "HTTP / REST"
    config_schema = {
        "type": "object",
        "properties": {
            "base_url": {"type": "string"},
            "auth_type": {"type": "string", "description": "none | bearer | header"},
            "header_name": {"type": "string"},
            "test_url": {"type": "string"},
        },
    }
    secret_schema = {
        "type": "object",
        "properties": {"token": {"type": "string"}},
    }

    def validate_config(self, config: dict) -> None:
        super().validate_config(config)
        auth_type = config.get("auth_type", "none")
        if auth_type not in {"none", "bearer", "header"}:
            raise ConnectorConfigError("auth_type must be one of: none, bearer, header")
        if auth_type == "header" and not config.get("header_name"):
            raise ConnectorConfigError("header_name is required when auth_type is 'header'")

    def list_tools(self) -> list[ToolSpec]:
        return [
            ToolSpec(
                name="http.call",
                description="Make an authenticated HTTP request (any method).",
                parameters={
                    "type": "object",
                    "properties": {
                        "method": {"type": "string", "description": "GET/POST/PUT/PATCH/DELETE."},
                        "url": {"type": "string", "description": "Absolute or base-relative URL."},
                        "headers": {"type": "object"},
                        "params": {"type": "object"},
                        "json": {"type": "object"},
                    },
                    "required": ["url"],
                },
                is_sensitive=True,
                scopes=["http:write"],
            )
        ]

    def _auth_headers(self, config: dict, secrets: dict) -> dict:
        auth_type = config.get("auth_type", "none")
        token = secrets.get("token", "")
        if auth_type == "bearer" and token:
            return {"Authorization": f"Bearer {token}"}
        if auth_type == "header" and token:
            return {config["header_name"]: token}
        return {}

    def _resolve_url(self, config: dict, url: str) -> str:
        base = (config.get("base_url") or "").rstrip("/")
        if url.startswith("http://") or url.startswith("https://"):
            return url
        return f"{base}/{url.lstrip('/')}" if base else url

    async def test_connection(self, config: dict, secrets: dict) -> ConnectionResult:
        target = config.get("test_url") or config.get("base_url")
        if not target:
            return ConnectionResult(ok=True, message="no test_url/base_url configured to probe")
        try:
            validate_public_url(target)
        except SSRFError as exc:
            return ConnectionResult(ok=False, message=str(exc))
        try:
            async with new_async_client() as client:
                resp = await client.get(target, headers=self._auth_headers(config, secrets))
        except httpx.HTTPError as exc:
            return ConnectionResult(ok=False, message=f"connection failed: {exc}")
        ok = resp.status_code < 500
        return ConnectionResult(ok=ok, message=f"HTTP {resp.status_code}")

    async def execute(
        self, tool: str, args: dict, config: dict, secrets: dict, ctx: ToolContext
    ) -> ToolResult:
        if tool != "http.call":
            return ToolResult(ok=False, error=f"unknown http tool: {tool}")
        method = str(args.get("method", "GET")).upper()
        if method not in _METHODS:
            return ToolResult(ok=False, error=f"unsupported method: {method}")
        url = self._resolve_url(config, str(args.get("url", "")))
        if not url:
            return ToolResult(ok=False, error="url is required")
        try:
            validate_public_url(url)
        except SSRFError as exc:
            return ToolResult(ok=False, error=str(exc))

        headers = {**self._auth_headers(config, secrets), **(args.get("headers") or {})}
        try:
            async with new_async_client() as client:
                resp = await client.request(
                    method,
                    url,
                    headers=headers,
                    params=args.get("params"),
                    json=args.get("json"),
                )
        except httpx.HTTPError as exc:
            return ToolResult(ok=False, error=f"request failed: {exc}")

        body = resp.text
        return ToolResult(
            ok=resp.status_code < 400,
            data={
                "status_code": resp.status_code,
                "content_type": resp.headers.get("content-type", ""),
                "body": body[:_MAX_BODY_CHARS],
                "truncated": len(body) > _MAX_BODY_CHARS,
            },
            error=None if resp.status_code < 400 else f"HTTP {resp.status_code}",
        )
