"""Built-in tools that exercise the agent loop (build plan Phase 2).

- ``kb.search``: knowledge-base retrieval. A safe stub here; backed by Qdrant in
  Phase 3.
- ``http.request``: read-only HTTP. Only GET is permitted — writes arrive with
  the connector framework (Phase 4) and are gated by the policy engine.
"""

from __future__ import annotations

import httpx

from legate.agent.tooling import ToolContext, ToolRegistry, ToolResult, ToolSpec
from legate.security.ssrf import SSRFError, validate_public_url

_MAX_BODY_CHARS = 8_000

KB_SEARCH_SPEC = ToolSpec(
    name="kb.search",
    description="Search the workspace knowledge base for relevant passages.",
    parameters={
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "What to search for."},
            "top_k": {"type": "integer", "description": "Max results (default 5)."},
        },
        "required": ["query"],
    },
    is_sensitive=False,
    scopes=[],
)

HTTP_REQUEST_SPEC = ToolSpec(
    name="http.request",
    description="Perform a read-only HTTP GET request and return the response.",
    parameters={
        "type": "object",
        "properties": {
            "url": {"type": "string", "description": "Absolute http(s) URL."},
            "method": {"type": "string", "description": "Only GET is allowed."},
        },
        "required": ["url"],
    },
    is_sensitive=False,
    scopes=[],
)


async def kb_search(args: dict, ctx: ToolContext) -> ToolResult:
    query = str(args.get("query", "")).strip()
    if not query:
        return ToolResult(ok=False, error="query must not be empty")
    top_k = int(args.get("top_k", 5) or 5)

    # Retrieval needs a database session, an embedder, and a vector store. When
    # any are absent (e.g. a unit-test runner without RAG wired in), return an
    # empty result rather than failing the run.
    if ctx.db is None or ctx.embedder is None or ctx.vector_store is None:
        return ToolResult(
            ok=True,
            data={"query": query, "results": [], "note": "no knowledge base is configured"},
        )

    from legate.rag.retrieval import search as kb_retrieval_search

    citations = kb_retrieval_search(
        db=ctx.db,
        workspace_id=ctx.workspace_id,
        query=query,
        embedder=ctx.embedder,
        vector_store=ctx.vector_store,
        top_k=top_k,
    )
    return ToolResult(ok=True, data={"query": query, "results": [c.as_dict() for c in citations]})


async def http_request(args: dict, ctx: ToolContext) -> ToolResult:
    url = str(args.get("url", "")).strip()
    method = str(args.get("method", "GET")).upper()
    if method != "GET":
        return ToolResult(ok=False, error="only read-only GET requests are permitted")
    if not (url.startswith("http://") or url.startswith("https://")):
        return ToolResult(ok=False, error="url must be an absolute http(s) URL")
    try:
        validate_public_url(url)
    except SSRFError as exc:
        return ToolResult(ok=False, error=str(exc))

    try:
        if ctx.http_client is not None:
            resp = await ctx.http_client.get(url)
        else:
            async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client:
                resp = await client.get(url)
    except httpx.HTTPError as exc:
        return ToolResult(ok=False, error=f"request failed: {exc}")

    body = resp.text
    truncated = len(body) > _MAX_BODY_CHARS
    return ToolResult(
        ok=True,
        data={
            "status_code": resp.status_code,
            "content_type": resp.headers.get("content-type", ""),
            "body": body[:_MAX_BODY_CHARS],
            "truncated": truncated,
        },
    )


def build_builtin_registry() -> ToolRegistry:
    registry = ToolRegistry()
    registry.register(KB_SEARCH_SPEC, kb_search)
    registry.register(HTTP_REQUEST_SPEC, http_request)
    return registry


#: Process-wide registry of built-in tools.
BUILTIN_REGISTRY = build_builtin_registry()
