# #region AssistantToolRegistry [C:4] [TYPE Module] [SEMANTICS assistant, registry, tool, dispatch, catalog]
# @BRIEF Central `@assistant_tool` decorator-based registry: auto-generates LLM catalog, dispatches operations,
#        and enforces permissions. Replaces hand-maintained `_build_tool_catalog()`, `_dispatch_intent()`,
#        `INTENT_PERMISSION_CHECKS`, and `_SAFE_OPS` to eliminate cross-file desynchronisation.
# @LAYER API
# @RELATION DEPENDS_ON -> [AssistantSchemas]
# @RELATION DEPENDS_ON -> [AssistantLlmPlanner]
# @RELATION DISPATCHES -> [AssistantToolCapabilities]
# @RELATION DISPATCHES -> [AssistantToolTaskStatus]
# @RELATION DISPATCHES -> [AssistantToolCreateBranch]
# @RELATION DISPATCHES -> [AssistantToolCommit]
# @RELATION DISPATCHES -> [AssistantToolDeploy]
# @RELATION DISPATCHES -> [AssistantToolMigration]
# @RELATION DISPATCHES -> [AssistantToolBackup]
# @RELATION DISPATCHES -> [AssistantToolLlmValidation]
# @RELATION DISPATCHES -> [AssistantToolLlmDocumentation]
# @RELATION DISPATCHES -> [AssistantToolHealthSummary]
# @DATA_CONTRACT Handler[intent, current_user, task_manager, config_manager, db] -> (text: str, task_id: str | None, actions: list[AssistantAction])
# @INVARIANT  Every registered tool has exactly one handler. Unsupported operations raise HTTPException(400).
# @INVARIANT  Permission checks are mandatory metadata — not an afterthought.

from __future__ import annotations

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

from fastapi import HTTPException
from sqlalchemy.orm import Session

from src.core.config_manager import ConfigManager
from src.core.logger import belief_scope
from src.core.task_manager import TaskManager
from src.dependencies import has_permission
from src.schemas.auth import User

from ._schemas import AssistantAction

# Type alias for the handler signature
ToolHandler = Callable[
    ...,
    Coroutine[Any, Any, tuple[str, str | None, list[AssistantAction]]],
]


# #region AssistantTool [C:1] [TYPE Class]
@dataclass
class AssistantTool:
    """Registered assistant tool metadata + handler."""

    operation: str
    domain: str
    description: str
    handler: ToolHandler
    required_entities: list[str] = field(default_factory=list)
    optional_entities: list[str] = field(default_factory=list)
    risk_level: str = "safe"
    requires_confirmation: bool = False
    permission_checks: list[tuple[str, str]] = field(default_factory=list)
    defaults: dict[str, Any] | None = None


# #endregion AssistantTool


# #region _tools [C:1] [TYPE Constant]
# @BRIEF Central tool registry: operation_name -> AssistantTool.
_tools: dict[str, AssistantTool] = {}
# #endregion _tools


# #region assistant_tool [C:2] [TYPE Function]
# @BRIEF Decorator that registers a function as an assistant tool handler.
# @POST  Tool is registered in `_tools` by operation name. Handler gets `__assistant_tool__` attr.
# @SIDE_EFFECT  Mutates the module-level `_tools` dict.
def assistant_tool(
    *,
    operation: str,
    domain: str,
    description: str,
    required_entities: list[str] | None = None,
    optional_entities: list[str] | None = None,
    risk_level: str = "safe",
    requires_confirmation: bool = False,
    permission_checks: list[tuple[str, str]] | None = None,
    defaults: dict[str, Any] | None = None,
):
    def decorator(func):
        tool = AssistantTool(
            operation=operation,
            domain=domain,
            description=description,
            handler=func,
            required_entities=required_entities or [],
            optional_entities=optional_entities or [],
            risk_level=risk_level,
            requires_confirmation=requires_confirmation,
            permission_checks=permission_checks or [],
            defaults=defaults,
        )
        _tools[operation] = tool
        func.__assistant_tool__ = tool  # type: ignore[attr-defined]
        return func

    return decorator


# #endregion assistant_tool


# ── Permission helpers ─────────────────────────────────────────────────────


# #region _check_any_permission [C:2] [TYPE Function]
# @BRIEF Validate user against alternative permission checks (logical OR).
# @PRE  checks list contains resource-action tuples.
# @POST  Returns on first successful permission; raises 403-like HTTPException.
def _check_any_permission(current_user: User, checks: list[tuple[str, str]]):
    errors: list[HTTPException] = []
    for resource, action in checks:
        try:
            has_permission(resource, action)(current_user)
            return
        except HTTPException as exc:
            errors.append(exc)

    raise (
        errors[-1]
        if errors
        else HTTPException(status_code=403, detail="Permission denied")
    )


# #endregion _check_any_permission


# #region _has_any_permission [C:2] [TYPE Function]
# @BRIEF Boolean variant of _check_any_permission.
# @POST  Returns True when at least one permission check passes.
def _has_any_permission(current_user: User, checks: list[tuple[str, str]]) -> bool:
    try:
        _check_any_permission(current_user, checks)
        return True
    except HTTPException:
        return False


# #endregion _has_any_permission


# ── Derived lists (replaces _SAFE_OPS, INTENT_PERMISSION_CHECKS) ────────────


# #region get_safe_ops [C:1] [TYPE Function] [SEMANTICS safelist,operations]
# @BRIEF Return set of operation names with risk_level == 'safe'.
# @POST  Includes registered tool safe-ops plus known non-registry safe operations
#        (e.g. dataset_review_answer_context) that bypass the confirmation gate.
def get_safe_ops() -> set[str]:
    """Return set of operation names with risk_level == 'safe'.

    Includes registered tool safe-ops plus known non-registry safe operations
    that bypass the confirmation gate.
    """
    ops = {op for op, t in _tools.items() if t.risk_level == "safe"}
    # dataset_review_answer_context is not registered via @assistant_tool
    # (it's dispatched via special-case path) but is read-only / safe.
    ops.add("dataset_review_answer_context")
    return ops


# #endregion get_safe_ops


# #region get_permission_checks [C:1] [TYPE Function] [SEMANTICS permissions,authorization]
# @BRIEF Return dict of operation_name -> permission_checks list for all registered tools.
# @POST  Returns mapping used by _authorize_intent and catalog filtering.
def get_permission_checks() -> dict[str, list[tuple[str, str]]]:
    """Return dict of operation_name -> permission_checks list."""
    result: dict[str, list[tuple[str, str]]] = {}
    for op, t in _tools.items():
        if t.permission_checks:
            result[op] = t.permission_checks
    # Also include dataset_review operations that route via special-case dispatch
    from ._schemas import _DATASET_REVIEW_OPS, INTENT_PERMISSION_CHECKS
    for op in _DATASET_REVIEW_OPS:
        if op in INTENT_PERMISSION_CHECKS:
            result[op] = INTENT_PERMISSION_CHECKS[op]
    if "dataset_review_answer_context" in INTENT_PERMISSION_CHECKS:
        result["dataset_review_answer_context"] = INTENT_PERMISSION_CHECKS["dataset_review_answer_context"]
    return result


# #endregion get_permission_checks


# ── Catalog generation ─────────────────────────────────────────────────────


# #region get_catalog [C:2] [TYPE Function]
# @BRIEF Build LLM tool catalog from registry, filtered by user permissions.
# @POST  Returns list of dicts suitable for LLM planner prompt.
def get_catalog(
    current_user: User,
    config_manager: ConfigManager,
    db: Session,
) -> list[dict[str, Any]]:
    """Build LLM tool catalog from registry, filtered by user permissions."""
    result: list[dict[str, Any]] = []
    for op, tool in sorted(_tools.items(), key=lambda x: x[0]):
        if tool.permission_checks and not _has_any_permission(
            current_user, tool.permission_checks
        ):
            continue
        entry: dict[str, Any] = {
            "operation": op,
            "domain": tool.domain,
            "description": tool.description,
            "required_entities": tool.required_entities,
            "optional_entities": tool.optional_entities,
            "risk_level": tool.risk_level,
            "requires_confirmation": tool.requires_confirmation,
        }
        if tool.defaults:
            entry["defaults"] = tool.defaults
        result.append(entry)
    return result


# #endregion get_catalog


# ── Dispatch ────────────────────────────────────────────────────────────────


# #region dispatch [C:2] [TYPE Function]
# @BRIEF Look up operation in registry and call its handler.
# @PRE  operation is a known registered tool.
# @POST  Returns (text, task_id, actions) from the handler.
async def dispatch(
    operation: str,
    intent: dict[str, Any],
    current_user: User,
    task_manager: TaskManager,
    config_manager: ConfigManager,
    db: Session,
) -> tuple[str, str | None, list[AssistantAction]]:
    """Look up operation in registry and call its handler."""
    with belief_scope("tool_registry.dispatch"):
        from ._dataset_review_dispatch import _dispatch_dataset_review_intent
        from ._schemas import _DATASET_REVIEW_OPS

        # Special case: dataset review operations are dispatched through
        # a dedicated sub-dispatcher to isolate session-scoped logic.
        if operation in _DATASET_REVIEW_OPS or operation == "dataset_review_answer_context":
            return await _dispatch_dataset_review_intent(
                intent, current_user, config_manager, db
            )

        tool = _tools.get(operation)
        if not tool:
            raise HTTPException(status_code=400, detail=f"Unsupported operation: {operation}")

        return await tool.handler(intent, current_user, task_manager, config_manager, db)


# #endregion dispatch


# #endregion AssistantToolRegistry
