"""Tool endpoint handlers."""

from __future__ import annotations

import logging
import time

from aiohttp import web

from core.pathways.context import Context
from core.pathways.run_state import RunState
from core.pathways.service_container import ServiceContainer
from core.tools import get_tool_registry
from server.errors import ErrorCode, sanitize_error_message
from server.transport.schemas import (
    ToolCallRequest,
    ToolCallResponse,
    ToolListItem,
    ToolListResponse,
    validate_request,
)
from server.transport.utils import (
    error_response,
    get_request_id,
    get_runtime,
    json_response,
)

logger = logging.getLogger(__name__)


async def handle_list_tools(request: web.Request) -> web.Response:
    """GET /api/v1/tools - List all available tools."""
    request_id = get_request_id(request)
    category_filter = request.query.get("category")
    output_format = request.query.get("format", "list")

    registry = get_tool_registry()
    tools: list[ToolListItem] = []
    categories: dict[str, list[ToolListItem]] = {}

    for tool_def in registry.list():
        cat = tool_def.name.split(".")[0]

        if category_filter and cat != category_filter:
            continue

        tool_info = ToolListItem(
            name=tool_def.name,
            category=cat,
            description=tool_def.description,
            parameters=tool_def.parameters or {},
        )
        tools.append(tool_info)
        categories.setdefault(cat, []).append(tool_info)

    logger.info(
        "List tools  category=%s  format=%s  count=%d  [request_id=%s]",
        category_filter or "all",
        output_format,
        len(tools),
        request_id,
    )

    if output_format == "grouped":
        return json_response(
            request,
            {
                "success": True,
                "categories": {k: len(v) for k, v in categories.items()},
                "tools_by_category": {
                    k: [t.model_dump(mode="json") for t in v] for k, v in categories.items()
                },
                "total": len(tools),
            },
        )

    resp = ToolListResponse(
        tools=tools,
        count=len(tools),
        categories=sorted(categories.keys()),
    )
    return json_response(request, resp.model_dump(mode="json"))


async def handle_tool_call(request: web.Request) -> web.Response:
    """POST /api/v1/tools/{tool_name} - Execute a tool directly."""
    start = time.time()
    tool_name = request.match_info["tool_name"]
    request_id = get_request_id(request)

    registry = get_tool_registry()
    tool_def = registry.get(tool_name)
    tool_fn = tool_def.handler if tool_def else None
    if not tool_fn:
        logger.warning(
            "Tool not found: %s  [request_id=%s]",
            tool_name,
            request_id,
        )
        return error_response(
            request,
            f"Tool not found: {tool_name}",
            ErrorCode.TOOL_NOT_FOUND,
            status=404,
            details={"available_tools": registry.names()[:20]},
        )

    try:
        body = await request.json()
    except Exception:
        logger.debug("No JSON body in tool_call request for %s, using empty dict", tool_name)
        body = {}

    # Validate with Pydantic
    validated, error_resp = validate_request(ToolCallRequest, body)
    if error_resp:
        logger.info(
            "Validation failed for tool_call %s  [request_id=%s]: %s",
            tool_name,
            request_id,
            error_resp.error,
        )
        return json_response(request, error_resp.model_dump(mode="json"), status=400)

    assert validated is not None  # Type narrowing

    logger.info(
        "Tool call: %s  [request_id=%s]",
        tool_name,
        request_id,
    )

    try:
        # Build a proper Context for the tool call.
        runtime = get_runtime(request)
        vm_ctx = runtime.pathway_vm.ctx if runtime.pathway_vm else None

        # Reuse the VM's frozen ServiceContainer when available.
        svc = vm_ctx.services if vm_ctx else ServiceContainer()

        workspace_id = validated.workspace_id or (vm_ctx.run.workspace_id if vm_ctx else None)
        thread_id = validated.thread_id or (vm_ctx.run.thread_id if vm_ctx else None)

        tool_ctx = Context(
            tools=vm_ctx.tools if vm_ctx else {},
            services=svc,
            run=RunState(
                workspace_id=workspace_id,
                thread_id=thread_id,
            ),
        )

        result = await tool_fn(validated.arguments, tool_ctx)
        duration_ms = (time.time() - start) * 1000
        # Tool handlers are required to return ToolOutput (serialized as dict).
        tool_success = True
        if isinstance(result, dict):
            tool_success = bool(result.get("success", True))

        logger.info(
            "Tool call completed: %s  success=%s  duration=%.1fms  [request_id=%s]",
            tool_name,
            tool_success,
            duration_ms,
            request_id,
        )

        # Tool handlers return ToolOutput (BaseModel) — serialize it.
        result_data = result.model_dump(mode="json") if hasattr(result, "model_dump") else result
        resp = ToolCallResponse(
            tool=tool_name,
            result=result_data if isinstance(result_data, dict) else {"value": result_data},
            duration_ms=duration_ms,
        )
        return json_response(request, resp.model_dump(mode="json"))
    except Exception as e:
        duration_ms = (time.time() - start) * 1000
        logger.error(
            "Tool call failed: %s  duration=%.1fms  [request_id=%s]: %s",
            tool_name,
            duration_ms,
            request_id,
            str(e),
            exc_info=True,
        )
        error_msg = sanitize_error_message(str(e), is_production=None)
        return error_response(
            request,
            error_msg,
            ErrorCode.TOOL_EXECUTION_FAILED,
            status=500,
            details={"tool": tool_name, "duration_ms": duration_ms},
        )


__all__ = [
    "handle_list_tools",
    "handle_tool_call",
]
