"""Built-in tools, argument validation, and memory/action helpers."""

from __future__ import annotations

import uuid

import httpx
from legate.agent.actions import ActionParseError, parse_action
from legate.agent.builtin_tools import http_request, kb_search
from legate.agent.memory import estimate_tokens, transcript_tokens, trim_transcript
from legate.agent.tooling import ToolContext, validate_args
from legate.config import get_settings
from legate.llm.base import LLMMessage

SCHEMA = {
    "type": "object",
    "properties": {"query": {"type": "string"}, "top_k": {"type": "integer"}},
    "required": ["query"],
}


def _ctx(http_client=None) -> ToolContext:
    return ToolContext(workspace_id=uuid.uuid4(), settings=get_settings(), http_client=http_client)


def test_validate_args_required_and_types():
    assert validate_args(SCHEMA, {"query": "x"}) == []
    assert any("required" in e for e in validate_args(SCHEMA, {}))
    assert any("integer" in e for e in validate_args(SCHEMA, {"query": "x", "top_k": "no"}))
    # bool must not satisfy integer
    assert any("integer" in e for e in validate_args(SCHEMA, {"query": "x", "top_k": True}))


async def test_kb_search_stub_returns_empty_results():
    res = await kb_search({"query": "invoices"}, _ctx())
    assert res.ok
    assert res.data["results"] == []
    bad = await kb_search({"query": "  "}, _ctx())
    assert not bad.ok


async def test_http_request_get_returns_body():
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(200, text="hello world", headers={"content-type": "text/plain"})

    async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http:
        res = await http_request({"url": "http://example.com"}, _ctx(http))
    assert res.ok
    assert res.data["status_code"] == 200
    assert res.data["body"] == "hello world"


async def test_http_request_rejects_writes_and_bad_urls():
    res = await http_request({"url": "http://example.com", "method": "POST"}, _ctx())
    assert not res.ok and "GET" in res.error
    res2 = await http_request({"url": "ftp://example.com"}, _ctx())
    assert not res2.ok


def test_parse_action_handles_fences_and_rejects_garbage():
    action = parse_action('```json\n{"thought":"t","action":"final","args":{"output":"hi"}}\n```')
    assert action.is_final
    assert action.args["output"] == "hi"
    for bad in ["not json", "[]", '{"thought":"t"}']:
        try:
            parse_action(bad)
        except ActionParseError:
            continue
        raise AssertionError(f"expected ActionParseError for {bad!r}")


def test_trim_transcript_keeps_system_and_recent():
    system = LLMMessage("system", "rules")
    msgs = [system] + [LLMMessage("user", "x" * 400) for _ in range(10)]
    budget = estimate_tokens("rules") + 3 * estimate_tokens("x" * 400)
    trimmed = trim_transcript(msgs, budget)
    assert trimmed[0] is system
    assert transcript_tokens(trimmed) <= budget
    assert len(trimmed) < len(msgs)
