"""MCP tool-call firewall adapter."""

from __future__ import annotations

import re
from collections.abc import Callable, Mapping
from typing import Any, TypeVar

from .canonical import sha256_value
from .engine import ScopeWritEngine
from .errors import GatewayError
from .gateway import GuardedExecutor, GuardedResult
from .models import ActionProposal

T = TypeVar("T")
_MCP_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{1,127}$")


def mcp_resource(server_id: str, tool_name: str) -> str:
    if not _MCP_NAME.fullmatch(server_id):
        raise GatewayError("invalid MCP server id")
    if not _MCP_NAME.fullmatch(tool_name):
        raise GatewayError("invalid MCP tool name")
    return f"mcp://{server_id.lower()}/{tool_name}"


class GuardedMCPFirewall:
    """Checks and records an MCP call before dispatching to an upstream client."""

    def __init__(self, engine: ScopeWritEngine):
        self.engine = engine

    def call(
        self,
        *,
        attestation: str,
        run_id: str,
        request_id: str,
        server_id: str,
        tool_name: str,
        arguments: Mapping[str, Any],
        operation: Callable[[Mapping[str, Any]], T],
        approval: str | None = None,
        justification: str | None = None,
        outcome: Callable[[T], Mapping[str, Any]] | None = None,
    ) -> GuardedResult[T]:
        try:
            arguments_sha256 = sha256_value(dict(arguments))
        except (TypeError, ValueError) as exc:
            raise GatewayError("MCP arguments must be deterministic JSON") from exc
        proposal = ActionProposal(
            request_id=request_id,
            run_id=run_id,
            action="mcp.call",
            resource=mcp_resource(server_id, tool_name),
            attributes={"arguments_sha256": arguments_sha256},
            justification=justification,
        )
        return GuardedExecutor(self.engine, adapter="mcp").execute(
            attestation=attestation,
            proposal=proposal,
            approval=approval,
            operation=lambda: operation(arguments),
            outcome=outcome,
        )
