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.migration import migrate_legacy_states
from icsd.core.transition import Transition


def _schema_invariants_v1() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="title_required",
                check=lambda state: bool(str(state.get("title", "")).strip()),
            ),
            Invariant(
                name="schema_v1_family",
                check=lambda state: str(state.get("schema_version", "")).startswith("1."),
            ),
        ]
    )


def _schema_invariants_v2() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="title_required",
                check=lambda state: bool(str(state.get("title", "")).strip()),
            ),
            Invariant(
                name="schema_v2_required",
                check=lambda state: str(state.get("schema_version", "")) == "2.0",
            ),
            Invariant(
                name="classification_required",
                check=lambda state: (
                    str(state.get("classification", "")) in {"public", "restricted"}
                ),
            ),
        ]
    )


def _schema_registry() -> InvariantProfileRegistry:
    return InvariantProfileRegistry(
        [
            InvariantProfile(
                name="schema-contract",
                policy="policy/schema-contract",
                policy_version="2026.01",
                active_from="2026-01-01T00:00:00Z",
                active_until="2026-06-01T00:00:00Z",
                invariants=_schema_invariants_v1(),
            ),
            InvariantProfile(
                name="schema-contract",
                policy="policy/schema-contract",
                policy_version="2026.06",
                active_from="2026-06-01T00:00:00Z",
                invariants=_schema_invariants_v2(),
            ),
        ]
    )


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 _legacy_contract_state() -> dict[str, object]:
    return _schema_invariants_v1().construct_state(
        {
            "record_id": "SC-CONF-001",
            "title": "Bridge inspection schema contract",
            "schema_version": "1.0",
            "status": "draft",
        }
    )


def _migrate_to_v2(state: dict[str, object]) -> None:
    state["schema_version"] = "2.0"
    state["classification"] = "restricted"


def test_schema_conformance_agent_enforcement_cutover_and_migration() -> None:
    transition = Transition(
        name="approve_contract",
        mutate=lambda state: state.update({"status": "approved"}),
        profile_registry=_schema_registry(),
    )
    agent = AgentExecutionContext(
        transition,
        enforcement=AgentPolicyEnforcement(
            require_profile=True,
            require_policy_version=True,
            minimum_authority_sources=1,
        ),
    )

    legacy_state = _legacy_contract_state()
    pre_cutover = agent.apply(
        state=legacy_state,
        entity_type="schema_contract",
        entity_id="SC-CONF-001",
        actor="governor:anna",
        profile="schema-contract",
        policy_version="2026.01",
        authority_sources=[
            AuthoritySource(
                source_type="contract-registry",
                source_id="schemas-vault",
                reference="schemas/SC-CONF-001@1.0",
            )
        ],
        timestamp="2026-05-20T10:00:00Z",
    )
    assert pre_cutover.evidence.verify_integrity(
        before_state=pre_cutover.before_state,
        after_state=pre_cutover.after_state,
    )
    assert _policy_provenance(pre_cutover.evidence)["details"]["policy_version"] == "2026.01"

    with pytest.raises(InvariantViolationError):
        agent.apply(
            state=legacy_state,
            entity_type="schema_contract",
            entity_id="SC-CONF-001",
            actor="governor:anna",
            profile="schema-contract",
            policy_version="2026.06",
            authority_sources=[
                AuthoritySource(
                    source_type="contract-registry",
                    source_id="schemas-vault",
                    reference="schemas/SC-CONF-001@1.0",
                )
            ],
            timestamp="2026-06-10T10:00:00Z",
        )

    report = migrate_legacy_states(
        [legacy_state],
        invariants=_schema_invariants_v2(),
        transform=_migrate_to_v2,
    )
    assert report.migrated_count == 1
    migrated_state = report.migrated_states[0]
    post_cutover = agent.apply(
        state=migrated_state,
        entity_type="schema_contract",
        entity_id="SC-CONF-001",
        actor="governor:anna",
        profile="schema-contract",
        policy_version="2026.06",
        authority_sources=[
            AuthoritySource(
                source_type="contract-registry",
                source_id="schemas-vault",
                reference="schemas/SC-CONF-001@2.0",
            )
        ],
        timestamp="2026-06-10T10:30:00Z",
    )
    assert post_cutover.after_state["status"] == "approved"
    assert post_cutover.evidence.verify_integrity(
        before_state=post_cutover.before_state,
        after_state=post_cutover.after_state,
    )
    provenance = _policy_provenance(post_cutover.evidence)
    assert provenance["details"]["policy_version"] == "2026.06"
    assert provenance["details"]["active_at"] == "2026-06-10T10:30:00Z"


def test_schema_conformance_agent_rejects_missing_policy_inputs() -> None:
    transition = Transition(
        name="approve_contract",
        mutate=lambda state: state.update({"status": "approved"}),
        profile_registry=_schema_registry(),
    )
    agent = AgentExecutionContext(
        transition,
        enforcement=AgentPolicyEnforcement(
            require_profile=True,
            require_policy_version=True,
            minimum_authority_sources=1,
        ),
    )

    with pytest.raises(ValueError, match="requires explicit policy_version"):
        agent.apply(
            state=_legacy_contract_state(),
            entity_type="schema_contract",
            entity_id="SC-CONF-002",
            actor="governor:anna",
            profile="schema-contract",
            authority_sources=[
                AuthoritySource(
                    source_type="contract-registry",
                    source_id="schemas-vault",
                    reference="schemas/SC-CONF-002@1.0",
                )
            ],
            timestamp="2026-05-20T11:00:00Z",
        )
