import pytest

from agent_framework.guardrails.base import RailDecision
from agent_framework.guardrails.output_supervisor import OutputSupervisor
from agent_framework.guardrails.parallel_executor import ParallelRailExecutor
from agent_framework.guardrails.rail_action import RailAction
from agent_framework.observability.code_mapper import ObservabilityCodeMapper


def test_compact_and_rich_mapping_are_backward_compatible():
    mapper = ObservabilityCodeMapper({
        "guardrail.dlex_in": "GRL.004",
        "guardrail.tox": {"label": "GRL.005", "aliases": ["TOX"]},
    })
    assert mapper.map("guardrail.dlex_in") == "GRL.004"
    assert mapper.map("DLEX_IN") == "GRL.004"
    assert mapper.map("TOX") == "GRL.005"
    assert mapper.map("guardrail.unknown") == "guardrail.unknown"


def test_action_resolution_accepts_internal_and_external_aliases():
    mapper = ObservabilityCodeMapper({
        "guardrail.revprec": {
            "action": "retry",
            "aliases": ["REVPREC", "TIM_REVPREC"],
        },
        "guardrail.handover": {
            "action": "handover",
            "aliases": ["ATH", "HUMAN"],
        },
    })
    assert mapper.action_for("REVPREC") == "retry"
    assert mapper.action_for("TIM_REVPREC") == "retry"
    assert mapper.action_for("guardrail.revprec") == "retry"
    assert mapper.action_for("ATH") == "handover"
    assert mapper.action_for("UNKNOWN") is None


class _DeniedLegacyRail:
    code = "REVPREC"

    async def evaluate(self, text, context):
        return RailDecision(code=self.code, allowed=False, reason="premature")


class _DeniedPolicyRail:
    code = "REVPREC"
    _guardrail_policy = {"on_deny": "handover"}

    async def evaluate(self, text, context):
        return RailDecision(code=self.code, allowed=False, reason="premature")


@pytest.mark.asyncio
async def test_output_supervisor_uses_registry_action_without_name_hardcode():
    mapper = ObservabilityCodeMapper({"guardrail.revprec": {"action": "retry"}})
    supervisor = OutputSupervisor(
        rails=[_DeniedLegacyRail()],
        enable_parallel=False,
        observability_mapper=mapper,
    )
    decision = await supervisor.evaluate("candidate", {})
    assert decision.action == RailAction.RETRY
    assert decision.results[0].metadata.get("action_source") == "observability_mapping"


@pytest.mark.asyncio
async def test_guardrails_yaml_policy_precedes_registry_action():
    mapper = ObservabilityCodeMapper({"guardrail.revprec": {"action": "retry"}})
    supervisor = OutputSupervisor(
        rails=[_DeniedPolicyRail()],
        enable_parallel=False,
        observability_mapper=mapper,
    )
    decision = await supervisor.evaluate("candidate", {})
    assert decision.action == RailAction.HANDOVER


@pytest.mark.asyncio
async def test_parallel_executor_uses_same_registry():
    mapper = ObservabilityCodeMapper({"guardrail.revprec": {"action": "retry"}})
    executor = ParallelRailExecutor(
        fail_fast=True,
        observability_mapper=mapper,
    )
    execution = await executor.run("candidate", {}, [_DeniedLegacyRail()])
    assert execution.terminal_result is not None
    assert execution.terminal_result.action == RailAction.RETRY
    assert execution.terminal_result.metadata.get("action_source") == "observability_mapping"
