from __future__ import annotations

import pytest

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


def _balance_invariants() -> InvariantSet:
    return InvariantSet(
        [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
    )


def _profile_registry() -> InvariantProfileRegistry:
    return InvariantProfileRegistry(
        [
            InvariantProfile(
                name="retail-banking",
                policy="policy/account-debit",
                policy_version="2026.03",
                invariants=_balance_invariants(),
            )
        ]
    )


def test_agent_execution_context_applies_transition_with_default_enforcement() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state.update({"balance": state["balance"] - 5}),
        invariants=_balance_invariants(),
    )
    context = AgentExecutionContext(transition)

    result = context.apply(
        state={"balance": 100},
        entity_type="account",
        entity_id="acct-001",
        actor="user:ops",
        authority="policy/account-debit",
    )

    assert result.after_state == {"balance": 95}


def test_agent_execution_context_requires_profile_when_enabled() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state,
        profile_registry=_profile_registry(),
    )
    context = AgentExecutionContext(
        transition,
        enforcement=AgentPolicyEnforcement(require_profile=True),
    )

    with pytest.raises(ValueError, match="requires explicit profile"):
        context.apply(
            state={"balance": 100},
            entity_type="account",
            entity_id="acct-001",
            actor="user:ops",
        )


def test_agent_execution_context_requires_policy_version_when_enabled() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state,
        profile_registry=_profile_registry(),
    )
    context = AgentExecutionContext(
        transition,
        enforcement=AgentPolicyEnforcement(require_policy_version=True),
    )

    with pytest.raises(ValueError, match="requires explicit policy_version"):
        context.apply(
            state={"balance": 100},
            entity_type="account",
            entity_id="acct-001",
            actor="user:ops",
            profile="retail-banking",
        )


def test_agent_execution_context_requires_minimum_authority_sources_when_enabled() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state,
        invariants=_balance_invariants(),
    )
    context = AgentExecutionContext(
        transition,
        enforcement=AgentPolicyEnforcement(minimum_authority_sources=1),
    )

    with pytest.raises(ValueError, match="at least 1 authority source"):
        context.apply(
            state={"balance": 100},
            entity_type="account",
            entity_id="acct-001",
            actor="user:ops",
            authority="policy/account-debit",
        )


def test_agent_execution_context_hard_fails_on_invariant_violations() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state.update({"balance": -1}),
        invariants=_balance_invariants(),
    )
    context = AgentExecutionContext(transition)

    with pytest.raises(InvariantViolationError):
        context.apply(
            state={"balance": 100},
            entity_type="account",
            entity_id="acct-001",
            actor="user:ops",
            authority="policy/account-debit",
        )


def test_agent_execution_context_rejects_profile_flags_for_non_profile_transition() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state,
        invariants=_balance_invariants(),
    )
    context = AgentExecutionContext(
        transition,
        enforcement=AgentPolicyEnforcement(require_profile=True),
    )

    with pytest.raises(
        ValueError,
        match="require_profile enforcement requires a profile-registry transition",
    ):
        context.apply(
            state={"balance": 100},
            entity_type="account",
            entity_id="acct-001",
            actor="user:ops",
            authority="policy/account-debit",
            profile="retail-banking",
        )
