"""Tool 调用策略：人格白名单、后台权限和副作用边界。

策略会执行两次：构建工具列表时隐藏无权工具，真正调用前再次强制鉴权。
"""

from dataclasses import dataclass
from typing import Literal

from zbt.core.errors import AppError


@dataclass(frozen=True)
class ToolPolicy:
    """每个工具随代码声明的安全规则。"""

    personas: tuple[str, ...]
    effect: Literal["read", "draft"]
    required_permissions: tuple[str, ...] = ()


class HarnessPolicyEngine:
    """集中判断某个人格能否使用某个工具。"""

    def can_use(
        self,
        *,
        persona: str,
        allowed_by_persona: tuple[str, ...],
        tool_name: str,
        policy: ToolPolicy,
        permissions: tuple[str, ...],
    ) -> bool:
        """返回工具是否应该对本次模型调用可见。"""

        if tool_name not in allowed_by_persona or persona not in policy.personas:
            return False
        if persona == "operation" and policy.effect != "read":
            return False
        # ``*`` 代表拥有全部权限；否则至少命中一个 required_permissions。
        return not (
            policy.required_permissions
            and "*" not in permissions
            and not set(policy.required_permissions).intersection(permissions)
        )

    def authorize(
        self,
        *,
        persona: str,
        allowed_by_persona: tuple[str, ...],
        tool_name: str,
        policy: ToolPolicy,
        permissions: tuple[str, ...],
    ) -> None:
        """在工具实际执行前强制检查；不满足时抛出可识别的业务错误。"""

        if tool_name not in allowed_by_persona or persona not in policy.personas:
            raise AppError(
                "AGENT_TOOL_NOT_ALLOWED",
                f"当前Agent人格不能使用工具：{tool_name}",
                403,
            )
        if persona == "operation" and policy.effect != "read":
            raise AppError(
                "AGENT_OPERATION_READ_ONLY",
                "运营Agent只允许执行只读工具",
                403,
            )
        if (
            policy.required_permissions
            and "*" not in permissions
            and not set(policy.required_permissions).intersection(permissions)
        ):
            raise AppError(
                "PERMISSION_DENIED",
                f"缺少工具权限：{tool_name}",
                403,
            )
