from __future__ import annotations

from datetime import datetime, timezone

import pytest

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 _temporal_registry() -> InvariantProfileRegistry:
    return InvariantProfileRegistry(
        [
            InvariantProfile(
                name="retail-banking",
                policy="policy/account-debit",
                policy_version="2026.03",
                active_from="2026-01-01T00:00:00Z",
                active_until="2026-06-01T00:00:00Z",
                invariants=_balance_invariants(),
            ),
            InvariantProfile(
                name="retail-banking",
                policy="policy/account-debit",
                policy_version="2026.06",
                active_from="2026-06-01T00:00:00Z",
                invariants=_balance_invariants(),
            ),
        ]
    )


def test_profile_registry_resolves_temporal_variants_by_timestamp() -> None:
    registry = _temporal_registry()

    march_profile = registry.resolve(
        profile="retail-banking",
        active_at="2026-03-10T12:00:00Z",
    )
    july_profile = registry.resolve(
        profile="retail-banking",
        active_at="2026-07-10T12:00:00Z",
    )

    assert march_profile.policy_version == "2026.03"
    assert july_profile.policy_version == "2026.06"


def test_profile_registry_rejects_overlap_for_same_profile_name() -> None:
    with pytest.raises(ValueError, match="Temporal activation windows overlap"):
        InvariantProfileRegistry(
            [
                InvariantProfile(
                    name="retail-banking",
                    policy="policy/account-debit",
                    policy_version="2026.03",
                    active_from="2026-01-01T00:00:00Z",
                    active_until="2026-07-01T00:00:00Z",
                    invariants=_balance_invariants(),
                ),
                InvariantProfile(
                    name="retail-banking",
                    policy="policy/account-debit",
                    policy_version="2026.06",
                    active_from="2026-06-01T00:00:00Z",
                    invariants=_balance_invariants(),
                ),
            ]
        )


def test_profile_registry_requires_active_at_for_temporal_resolution() -> None:
    registry = _temporal_registry()

    with pytest.raises(ValueError, match="active_at must be provided"):
        registry.resolve(profile="retail-banking")


def test_transition_apply_switches_profile_at_cutover_boundary() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state.update({"balance": state["balance"] - 5}),
        profile_registry=_temporal_registry(),
    )

    before_cutover = transition.apply(
        state={"balance": 100},
        entity_type="account",
        entity_id="acct-001",
        actor="user:ops",
        profile="retail-banking",
        timestamp=datetime(2026, 5, 31, 23, 59, 59, tzinfo=timezone.utc),
    )
    at_cutover = transition.apply(
        state={"balance": 100},
        entity_type="account",
        entity_id="acct-001",
        actor="user:ops",
        profile="retail-banking",
        timestamp="2026-06-01T00:00:00Z",
    )

    assert before_cutover.evidence.authority == "policy/account-debit@2026.03"
    assert at_cutover.evidence.authority == "policy/account-debit@2026.06"


def test_transition_evidence_records_active_profile_provenance() -> None:
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state.update({"balance": state["balance"] - 5}),
        profile_registry=_temporal_registry(),
    )
    result = transition.apply(
        state={"balance": 100},
        entity_type="account",
        entity_id="acct-001",
        actor="user:ops",
        profile="retail-banking",
        timestamp="2026-07-10T12:00:00Z",
    )

    profile_provenance = next(
        item for item in result.evidence.provenance if item.source.startswith("policy-profile/")
    )
    assert profile_provenance.source == "policy-profile/retail-banking"
    assert profile_provenance.reference == "policy/account-debit@2026.06"
    assert profile_provenance.timestamp == "2026-07-10T12:00:00Z"
    assert profile_provenance.details["policy_version"] == "2026.06"
    assert profile_provenance.details["active_from"] == "2026-06-01T00:00:00Z"
