"""Shell wrapper for tool policy enforcement.

Wraps PydanticAI tools with side_effect_policy checks.

Intercepts tool calls:
- read_only: Blocks non-read-only tools, returns error to agent
- approval_required: Gates non-read-only tools pending approval
"""

# @invar:allow file_size: Multiple policy wrappers in single file
# @invar:allow shell_result: Tool factory functions return Tool objects, not Result

from functools import wraps
from inspect import signature
from typing import TYPE_CHECKING, Any

from returns.result import Failure

from anima.core.schemas import SideEffectPolicy, ToolBlockedError
from anima.core.schemas.errors import ApprovalRequiredError
from anima.core.tool_policy import ToolDecision, check_tool_policy

if TYPE_CHECKING:
    from pydantic_ai import Tool

    from anima.shell.agent import AgentRunner


# @invar:allow shell_result: Tool factory functions return Tool objects, not Result
# @shell_orchestration: Integrates policy checks with AgentRunner state
# @shell_complexity: Policy enforcement requires branching for each policy type
def create_policy_wrapped_tool(
    tool: "Tool",
    policy: SideEffectPolicy,
    agent_runner: "AgentRunner",
) -> "Tool":
    """Wrap a tool with policy enforcement.

    For read_only and approval_required policies, checks the tool name
    before allowing execution.

    Args:
        tool: Original PydanticAI Tool object.
        policy: Side effect policy to enforce.
        agent_runner: AgentRunner reference for approval state.

    Returns:
        Tool object with policy wrapper, or original tool if policy is 'allow'.

    Examples:
        >>> from anima.core.schemas import SideEffectPolicy
        >>> # This is tested via integration tests
    """
    from pydantic_ai import Tool

    # Allow policy: no wrapping needed
    if policy == SideEffectPolicy.allow:
        return tool

    # Get original function
    function = getattr(tool, "function", None)
    if function is None:
        # Can't wrap tool without function
        return tool

    # Get tool name (Tool objects have a name attribute or we use function name)
    tool_name = getattr(tool, "name", None) or getattr(function, "__name__", "unknown")

    # Create wrapper function
    @wraps(function)
    async def policy_aware_wrapper(**kwargs: Any) -> str:
        """Wrapped tool function with policy check."""
        decision = check_tool_policy(policy, tool_name)

        if decision == ToolDecision.ALLOW:
            # Tool permitted, call original
            if function is not None:
                result = await function(**kwargs)
                return result if isinstance(result, str) else str(result)
            return "Error: Tool function not available"

        if decision == ToolDecision.BLOCK:
            # read_only policy blocked this tool
            error = ToolBlockedError(tool_name, policy.value)
            return f"Error: {error.message}. Only read-only tools (read_*, get_*, list_*, search_*, find_*, show_*, status) are allowed in read_only mode."

        if decision == ToolDecision.APPROVAL:
            # approval_required: need to check approval state
            # This requires integration with AgentRunner approval state
            # For approval, we delegate to ctl_request_approval mechanism
            # The agent must call ctl_request_approval first
            # Check if we're in awaiting_approval state
            from anima.shell.state import read_state

            state_result = read_state(agent_runner.instance_id)
            if isinstance(state_result, Failure):
                return (
                    f"Error: Failed to check approval state: {state_result.failure()}"
                )

            state = state_result.unwrap()

            # Check if agent is in awaiting_approval state
            # This would mean approval was requested
            if state.state.value == "awaiting_approval":
                # Agent requested approval but hasn't received it yet
                return "Error: Approval pending. Wait for approval before retrying this tool."

            # No approval requested yet
            error = ApprovalRequiredError(tool_name)
            return f"Error: {error.message}. Use ctl_request_approval to request approval for this action."

        # Should never reach here
        return "Error: Unknown policy decision"

    # Preserve the original callable signature for tool schema generation.
    setattr(policy_aware_wrapper, "__signature__", signature(function))

    # Create new Tool with same name
    return Tool(policy_aware_wrapper, name=tool_name)


# @invar:allow shell_result: Tool factory functions return list of Tool objects, not Result
# @shell_orchestration: Wraps tools with AgentRunner integration for approval state
def create_policy_wrapped_tools(
    tools: list["Tool"],
    policy: SideEffectPolicy,
    agent_runner: "AgentRunner",
) -> list["Tool"]:
    """Wrap multiple tools with policy enforcement.

    Args:
        tools: List of PydanticAI Tool objects.
        policy: Side effect policy to enforce.
        agent_runner: AgentRunner reference for approval state.

    Returns:
        List of Tool objects with policy wrappers applied.

    Examples:
        >>> from anima.core.schemas import SideEffectPolicy
        >>> # This is tested via integration tests
    """
    return [create_policy_wrapped_tool(tool, policy, agent_runner) for tool in tools]


__all__ = [
    "create_policy_wrapped_tool",
    "create_policy_wrapped_tools",
]
