"""Rigorous AuthZEN request mapping for AGT tool-call governance.

Maps ToolCallRequest fields into OpenID AuthZEN 1.0 evaluation requests
with proper subject/action/resource/context decomposition. Distinguishes
agent runtime identity from the human principal and supports delegation.
"""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from typing import Any

from authzen_backend.models import (
    AuthZENAction,
    AuthZENRequest,
    AuthZENResource,
    AuthZENSubject,
)


@dataclass(frozen=True)
class ToolCallMapping:
    """Fully resolved AuthZEN request derived from a ToolCallRequest."""

    authzen_request: AuthZENRequest
    original_args_hash: str
    call_id: str


def _args_hash(arguments: dict[str, Any]) -> str:
    """SHA-256 of canonically-serialized arguments for audit correlation."""
    canonical = json.dumps(arguments, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(canonical.encode()).hexdigest()


def map_tool_call(
    *,
    tool_name: str,
    arguments: dict[str, Any],
    call_id: str,
    agent_id: str,
    metadata: dict[str, Any],
    pdp_application: str,
) -> ToolCallMapping:
    """Map an AGT ToolCallRequest into an AuthZEN evaluation request.

    Args:
        tool_name: The tool being invoked.
        arguments: Raw arguments dict from the tool call.
        call_id: Unique call identifier from AGT.
        agent_id: Agent runtime identity.
        metadata: Arbitrary metadata from the ToolCallRequest.
        pdp_application: ARIA PDP application identifier.

    Returns:
        A ToolCallMapping with the fully resolved AuthZEN request.
    """
    server_name = str(metadata.get("server_name", "local"))

    subject_properties: dict[str, Any] = {}
    user_id = metadata.get("user_id")
    if user_id is not None:
        subject_properties["bound_to_user_id"] = str(user_id)
    delegator = metadata.get("delegator")
    if delegator is not None:
        subject_properties["delegator"] = str(delegator)

    subject = AuthZENSubject(
        type="agent",
        id=f"auth:agent:agentmesh:{agent_id}" if agent_id else "auth:agent:agentmesh:anonymous",
        properties=subject_properties,
    )

    action = AuthZENAction(
        name="tool.invoke",
        properties={
            "operation_ref_id": f"op:tool:{tool_name}",
        },
    )

    resource = AuthZENResource(
        type="mcp_tool",
        id=f"mcp://{server_name}/{tool_name}",
    )

    args_digest = _args_hash(arguments)

    context: dict[str, Any] = {
        "pdp_application": pdp_application,
        "original_args_hash": args_digest,
    }
    if call_id:
        context["call_id"] = call_id
    session_id = metadata.get("session_id")
    if session_id is not None:
        context["session_id"] = str(session_id)

    authzen_request = AuthZENRequest(
        subject=subject,
        action=action,
        resource=resource,
        context=context,
    )

    return ToolCallMapping(
        authzen_request=authzen_request,
        original_args_hash=args_digest,
        call_id=call_id,
    )
