"""Decorator-based tool registry.

Each tool is registered via ``@tool(...)`` which captures the OpenAI-style
function schema and handler in one place.  ``get_tools_for_user`` and
``execute_tool`` are the public API consumed by the rest of the app.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Any, Callable, Coroutine

from fabledassistant.config import Config
from fabledassistant.services.caldav import is_caldav_configured

logger = logging.getLogger(__name__)

type ToolHandler = Callable[..., Coroutine[Any, Any, dict]]


@dataclass(frozen=True, slots=True)
class ToolDef:
    name: str
    description: str
    parameters: dict
    handler: ToolHandler
    read_only: bool = False
    briefing: bool = False
    journal: bool = False
    requires: str | None = None
    required_params: list[str] = field(default_factory=list)

    def schema(self) -> dict:
        """Return the OpenAI function-calling schema dict."""
        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": {
                    "type": "object",
                    "properties": self.parameters,
                    **({"required": self.required_params} if self.required_params else {}),
                },
            },
        }


_REGISTRY: dict[str, ToolDef] = {}


def tool(
    *,
    name: str,
    description: str,
    parameters: dict | None = None,
    required: list[str] | None = None,
    read_only: bool = False,
    briefing: bool = False,
    journal: bool = False,
    requires: str | None = None,
) -> Callable[[ToolHandler], ToolHandler]:
    """Register an async tool handler with its schema metadata."""

    def decorator(fn: ToolHandler) -> ToolHandler:
        if name in _REGISTRY:
            raise ValueError(f"Duplicate tool registration: {name}")
        _REGISTRY[name] = ToolDef(
            name=name,
            description=description,
            parameters=parameters or {},
            handler=fn,
            read_only=read_only,
            briefing=briefing,
            journal=journal,
            requires=requires,
            required_params=required or [],
        )
        return fn

    return decorator


async def _check_requires(user_id: int, requires: str) -> bool:
    if requires == "caldav":
        return await is_caldav_configured(user_id)
    if requires == "searxng":
        return Config.searxng_enabled()
    return True


async def get_tools_for_user(
    user_id: int, *, conversation_type: str = "chat"
) -> list[dict]:
    """Build the tool schema list for a user, scoped by conversation type.

    - 'chat': all tools except those marked journal-only.
    - 'journal': all tools except set_rag_scope (scope is implicit in journal).
    """
    tools: list[dict] = []
    for td in _REGISTRY.values():
        if td.requires and not await _check_requires(user_id, td.requires):
            continue
        if conversation_type == "journal":
            if td.name == "set_rag_scope":
                continue
        else:
            if td.journal:
                continue
        tools.append(td.schema())
    logger.debug(
        "User %d / %s: %d tools available", user_id, conversation_type, len(tools)
    )
    return tools


async def execute_tool(
    user_id: int,
    tool_name: str,
    arguments: dict,
    conv_id: int | None = None,
    workspace_project_id: int | None = None,
) -> dict:
    """Execute a tool call and return the result."""
    td = _REGISTRY.get(tool_name)
    if td is None:
        return {"success": False, "error": f"Unknown tool: {tool_name}"}
    try:
        return await td.handler(
            user_id=user_id,
            arguments=arguments,
            conv_id=conv_id,
            workspace_project_id=workspace_project_id,
        )
    except Exception as e:
        logger.exception("Tool execution failed: %s", tool_name)
        return {"success": False, "error": str(e)}
