from __future__ import annotations

from unittest.mock import Mock

import pytest

pytest.importorskip("pydantic_ai")

from pydantic_ai._run_context import RunContext
from pydantic_ai.exceptions import ModelRetry
from pydantic_ai.usage import RunUsage

from rgltr.audit import AuditEvent
from rgltr.config import configure
from rgltr.context import context
from rgltr.decorators import policy
from rgltr.pydantic_ai import RegulatedToolset
from rgltr.types import PolicyDecision, PolicyRequest


async def deny_policy(_: PolicyRequest) -> PolicyDecision:
    return PolicyDecision.deny("blocked")


@policy(require=[deny_policy])
async def restricted() -> str:
    return "nope"


def _run_ctx(call_id: str | None = None) -> RunContext:
    return RunContext(deps=None, model=Mock(), usage=RunUsage(), tool_call_id=call_id)


def test_audit_event_drops_null_fields():
    event = AuditEvent(
        event_type="tool_allowed",
        run_id="run-1",
        tool_name="get_customer",
        tool_call_id="call-1",
        agent_id="support",
        active_tags=[],
        tool_kind="function",
        would_block=True,
        would_require_approval=False,
    )
    payload = event.as_dict()

    assert payload["event_type"] == "tool_allowed"
    assert payload["tool_call_id"] == "call-1"
    assert payload["would_block"] is True
    assert payload["would_require_approval"] is False
    assert "active_tags_after" not in payload
    assert "policy_reason" not in payload
    assert "decision" not in payload
    assert "payload" not in payload


@pytest.mark.anyio
async def test_monitor_mode_tool_allowed_includes_would_block_and_call_id():
    events: list[AuditEvent] = []
    configure(mode="monitor", audit_sink=events.append)
    toolset = RegulatedToolset([restricted])
    tools = await toolset.get_tools(_run_ctx("call-42"))
    tool = tools["restricted"]

    with context():
        await toolset.call_tool("restricted", {}, _run_ctx("call-42"), tool)

    allowed_events = [event for event in events if event.event_type == "tool_allowed"]
    assert len(allowed_events) == 1
    event = allowed_events[0]
    assert event.tool_call_id == "call-42"
    assert event.would_block is True
    assert event.would_require_approval is False
    assert event.active_tags == []
    assert event.active_tags_after == []


@pytest.mark.anyio
async def test_tool_call_requested_and_tools_presented_events():
    events: list[AuditEvent] = []
    configure(audit_sink=events.append)
    toolset = RegulatedToolset([restricted])

    with context():
        tools = await toolset.get_tools(_run_ctx("call-1"))
        tool = tools["restricted"]
        with pytest.raises(ModelRetry):
            await toolset.call_tool("restricted", {}, _run_ctx("call-1"), tool)

    event_types = [event.event_type for event in events]
    assert "tools_presented" in event_types
    assert "tool_call_requested" in event_types
