from __future__ import annotations

import pytest

from icsd.core.context import AgentExecutionContext, AgentPolicyEnforcement
from icsd.core.errors import InvariantViolationError
from icsd.core.evidence import AuthoritySource, EvidenceBundle
from icsd.core.invariants import Invariant, InvariantProfile, InvariantProfileRegistry, InvariantSet
from icsd.core.transition import Transition

REQUEST_STAGES = {"queued", "approved", "executed"}


def _guardrail_invariants() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="request_id_present",
                check=lambda state: bool(str(state.get("request_id", "")).strip()),
            ),
            Invariant(
                name="stage_known",
                check=lambda state: state.get("stage") in REQUEST_STAGES,
            ),
            Invariant(
                name="risk_score_valid",
                check=lambda state: (
                    isinstance(state.get("risk_score"), int) and 0 <= state["risk_score"] <= 100
                ),
            ),
            Invariant(
                name="risk_limit_valid",
                check=lambda state: (
                    isinstance(state.get("approved_risk_limit"), int)
                    and 0 <= state["approved_risk_limit"] <= 100
                ),
            ),
            Invariant(
                name="approved_or_executed_requires_approver",
                check=lambda state: (
                    state.get("stage") not in {"approved", "executed"}
                    or bool(str(state.get("approved_by", "")).strip())
                ),
            ),
            Invariant(
                name="executed_requires_executor",
                check=lambda state: (
                    state.get("stage") != "executed"
                    or bool(str(state.get("executed_by", "")).strip())
                ),
            ),
            Invariant(
                name="executed_risk_within_approved_limit",
                check=lambda state: (
                    state.get("stage") != "executed"
                    or int(state.get("risk_score", 0)) <= int(state.get("approved_risk_limit", 0))
                ),
            ),
        ]
    )


def _profile_registry() -> InvariantProfileRegistry:
    return InvariantProfileRegistry(
        [
            InvariantProfile(
                name="agent-guardrails",
                policy="policy/agent-guardrails",
                policy_version="2026.03",
                invariants=_guardrail_invariants(),
            )
        ]
    )


def _agent_context(transition: Transition) -> AgentExecutionContext:
    return AgentExecutionContext(
        transition,
        enforcement=AgentPolicyEnforcement(
            require_profile=True,
            require_policy_version=True,
            minimum_authority_sources=1,
        ),
    )


def _source(*, source_id: str, reference: str, actor_ref: str) -> AuthoritySource:
    return AuthoritySource(
        source_type="agent-review-ticket",
        source_id=source_id,
        reference=reference,
        details={
            "actor_ref": actor_ref,
            "reviewer": "reviewer:alice",
        },
    )


def _base_state() -> dict[str, object]:
    return _guardrail_invariants().construct_state(
        {
            "request_id": "REQ-CONF-AGENT-001",
            "stage": "queued",
            "risk_score": 42,
            "approved_risk_limit": 80,
            "approved_by": "",
            "executed_by": "",
        }
    )


def _mutate_approved(data: dict[str, object]) -> None:
    data.update(
        {
            "stage": "approved",
            "approved_by": "reviewer:alice",
            "executed_by": "",
        }
    )


def _mutate_unsafe_execute(data: dict[str, object]) -> None:
    data.update(
        {
            "stage": "executed",
            "approved_by": "",
            "executed_by": "agent:executor-7",
        }
    )


def _mutate_safe_execute(data: dict[str, object]) -> None:
    data.update(
        {
            "stage": "executed",
            "approved_by": "reviewer:alice",
            "executed_by": "agent:executor-7",
        }
    )


def _policy_provenance(evidence: EvidenceBundle) -> dict[str, object]:
    entry = next(item for item in evidence.provenance if item.source.startswith("policy-profile/"))
    return entry.as_dict()


def test_agent_guardrails_conformance_refusal_is_deterministic() -> None:
    profile = "agent-guardrails"
    policy_version = "2026.03"
    approve_transition = Transition(
        name="approve_request",
        mutate=_mutate_approved,
        profile_registry=_profile_registry(),
    )
    unsafe_transition = Transition(
        name="execute_request",
        mutate=_mutate_unsafe_execute,
        profile_registry=_profile_registry(),
    )
    safe_transition = Transition(
        name="execute_request",
        mutate=_mutate_safe_execute,
        profile_registry=_profile_registry(),
    )

    approved_result = _agent_context(approve_transition).apply(
        state=_base_state(),
        entity_type="agent_request",
        entity_id="REQ-CONF-AGENT-001",
        actor="agent:planner-1",
        profile=profile,
        policy_version=policy_version,
        authority_sources=[
            _source(
                source_id="ticket-801",
                reference="ops/REQ-CONF-AGENT-001/approve",
                actor_ref="agent:planner-1",
            )
        ],
        timestamp="2026-03-12T09:00:00Z",
    )

    with pytest.raises(InvariantViolationError) as exc_info:
        _agent_context(unsafe_transition).apply(
            state=approved_result.after_state,
            entity_type="agent_request",
            entity_id="REQ-CONF-AGENT-001",
            actor="agent:executor-7",
            profile=profile,
            policy_version=policy_version,
            authority_sources=[
                _source(
                    source_id="ticket-802",
                    reference="ops/REQ-CONF-AGENT-001/execute-attempt-1",
                    actor_ref="agent:executor-7",
                )
            ],
            timestamp="2026-03-12T09:01:00Z",
        )
    refusal_payload = exc_info.value.as_dict()
    assert refusal_payload == {
        "error": "invariant_violation",
        "failures": [
            {
                "invariant": "approved_or_executed_requires_approver",
                "code": "invariant_failed",
                "message": "Invariant 'approved_or_executed_requires_approver' failed.",
            }
        ],
    }

    executed_result = _agent_context(safe_transition).apply(
        state=approved_result.after_state,
        entity_type="agent_request",
        entity_id="REQ-CONF-AGENT-001",
        actor="agent:executor-7",
        profile=profile,
        policy_version=policy_version,
        authority_sources=[
            _source(
                source_id="ticket-803",
                reference="ops/REQ-CONF-AGENT-001/execute",
                actor_ref="agent:executor-7",
            )
        ],
        timestamp="2026-03-12T09:02:00Z",
    )

    assert executed_result.after_state["stage"] == "executed"
    assert executed_result.after_state["approved_by"] == "reviewer:alice"
    assert approved_result.evidence.verify_integrity(
        before_state=approved_result.before_state,
        after_state=approved_result.after_state,
    )
    assert executed_result.evidence.verify_integrity(
        before_state=executed_result.before_state,
        after_state=executed_result.after_state,
    )
    approved_policy = _policy_provenance(approved_result.evidence)
    executed_policy = _policy_provenance(executed_result.evidence)
    assert approved_policy["details"]["profile"] == "agent-guardrails"
    assert approved_policy["details"]["policy_version"] == "2026.03"
    assert executed_policy["details"]["profile"] == "agent-guardrails"
    assert executed_policy["details"]["policy_version"] == "2026.03"


def test_agent_guardrails_conformance_enforces_agent_policy_flags() -> None:
    approve_transition = Transition(
        name="approve_request",
        mutate=_mutate_approved,
        profile_registry=_profile_registry(),
    )
    agent_context = _agent_context(approve_transition)

    with pytest.raises(ValueError, match="requires explicit profile"):
        agent_context.apply(
            state=_base_state(),
            entity_type="agent_request",
            entity_id="REQ-CONF-AGENT-002",
            actor="agent:planner-1",
            policy_version="2026.03",
            authority_sources=[
                _source(
                    source_id="ticket-901",
                    reference="ops/REQ-CONF-AGENT-002/approve",
                    actor_ref="agent:planner-1",
                )
            ],
        )

    with pytest.raises(ValueError, match="requires explicit policy_version"):
        agent_context.apply(
            state=_base_state(),
            entity_type="agent_request",
            entity_id="REQ-CONF-AGENT-002",
            actor="agent:planner-1",
            profile="agent-guardrails",
            authority_sources=[
                _source(
                    source_id="ticket-901",
                    reference="ops/REQ-CONF-AGENT-002/approve",
                    actor_ref="agent:planner-1",
                )
            ],
        )

    with pytest.raises(ValueError, match="at least 1 authority source"):
        agent_context.apply(
            state=_base_state(),
            entity_type="agent_request",
            entity_id="REQ-CONF-AGENT-002",
            actor="agent:planner-1",
            profile="agent-guardrails",
            policy_version="2026.03",
            authority_sources=[],
        )
