from __future__ import annotations

import pytest

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


def _legal_invariants_v202601() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="stage_known_v202601",
                check=lambda state: state.get("stage") in {"draft", "filed", "reviewed", "ruled"},
            ),
            Invariant(
                name="review_requires_reviewer",
                check=lambda state: (
                    state.get("stage") not in {"reviewed", "ruled"}
                    or bool(str(state.get("reviewed_by", "")).strip())
                ),
            ),
            Invariant(
                name="ruling_requires_summary",
                check=lambda state: (
                    state.get("stage") != "ruled"
                    or bool(str(state.get("ruling_summary", "")).strip())
                ),
            ),
        ]
    )


def _legal_invariants_v202606() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="stage_known_v202606",
                check=lambda state: (
                    state.get("stage") in {"draft", "filed", "reviewed", "ruled", "appealed"}
                ),
            ),
            Invariant(
                name="review_requires_reviewer",
                check=lambda state: (
                    state.get("stage") not in {"reviewed", "ruled", "appealed"}
                    or bool(str(state.get("reviewed_by", "")).strip())
                ),
            ),
            Invariant(
                name="ruling_requires_summary",
                check=lambda state: (
                    state.get("stage") not in {"ruled", "appealed"}
                    or bool(str(state.get("ruling_summary", "")).strip())
                ),
            ),
            Invariant(
                name="appeal_requires_reason",
                check=lambda state: (
                    state.get("stage") != "appealed"
                    or bool(str(state.get("appeal_reason", "")).strip())
                ),
            ),
        ]
    )


def _legal_profile_registry() -> InvariantProfileRegistry:
    return InvariantProfileRegistry(
        [
            InvariantProfile(
                name="legal-case",
                policy="law/legal-case-procedure",
                policy_version="2026.01",
                active_from="2026-01-01T00:00:00Z",
                active_until="2026-06-01T00:00:00Z",
                invariants=_legal_invariants_v202601(),
            ),
            InvariantProfile(
                name="legal-case",
                policy="law/legal-case-procedure",
                policy_version="2026.06",
                active_from="2026-06-01T00:00:00Z",
                invariants=_legal_invariants_v202606(),
            ),
        ]
    )


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 _base_case_state() -> dict[str, object]:
    return _legal_invariants_v202601().construct_state(
        {
            "case_id": "LC-CONF-001",
            "stage": "draft",
            "reviewed_by": "",
            "ruling_summary": "",
            "appeal_reason": "",
        }
    )


def test_legal_conformance_policy_cutover_and_evidence_provenance() -> None:
    transition_filing = Transition(
        name="file_case",
        mutate=lambda state: state.update({"stage": "filed"}),
        profile_registry=_legal_profile_registry(),
        default_profile="legal-case",
        authority_validator=Transition.require_authority_sources(minimum=1),
    )
    transition_review = Transition(
        name="review_case",
        mutate=lambda state: state.update({"stage": "reviewed", "reviewed_by": "reviewer:panel-a"}),
        profile_registry=_legal_profile_registry(),
        default_profile="legal-case",
        authority_validator=Transition.require_authority_sources(minimum=1),
    )
    transition_rule = Transition(
        name="issue_ruling",
        mutate=lambda state: state.update(
            {"stage": "ruled", "ruling_summary": "Claim partially upheld."}
        ),
        profile_registry=_legal_profile_registry(),
        default_profile="legal-case",
        authority_validator=Transition.require_authority_sources(minimum=1),
    )
    transition_appeal = Transition(
        name="file_appeal",
        mutate=lambda state: state.update(
            {
                "stage": "appealed",
                "appeal_reason": "Material procedural issue in witness handling.",
            }
        ),
        profile_registry=_legal_profile_registry(),
        default_profile="legal-case",
        authority_validator=Transition.require_authority_sources(minimum=1),
    )

    initial = _base_case_state()
    filing_context = transition_filing.build_context(
        profile="legal-case",
        policy_version="2026.01",
        active_at="2026-02-10T10:00:00Z",
        authority_sources=[
            AuthoritySource(
                source_type="statute",
                source_id="civil-procedure-rules",
                reference="CPR-Part-7",
            )
        ],
    )
    filed = transition_filing.apply_with_context(
        state=initial,
        entity_type="legal_case",
        entity_id="LC-CONF-001",
        actor="clerk:registry",
        context=filing_context,
        timestamp="2026-02-10T10:00:00Z",
    )

    review_context = transition_review.build_context(
        profile="legal-case",
        policy_version="2026.01",
        active_at="2026-03-10T10:00:00Z",
        authority_sources=[
            AuthoritySource(
                source_type="court-registry",
                source_id="high-court-docket",
                reference="docket/LC-CONF-001",
            )
        ],
    )
    reviewed = transition_review.apply_with_context(
        state=filed.after_state,
        entity_type="legal_case",
        entity_id="LC-CONF-001",
        actor="reviewer:court",
        context=review_context,
        timestamp="2026-03-10T10:00:00Z",
    )

    ruling_context = transition_rule.build_context(
        profile="legal-case",
        policy_version="2026.01",
        active_at="2026-05-29T15:45:00Z",
        authority_sources=[
            AuthoritySource(
                source_type="precedent",
                source_id="court-of-appeal",
                reference="CA-2022-0045",
            )
        ],
    )
    ruled = transition_rule.apply_with_context(
        state=reviewed.after_state,
        entity_type="legal_case",
        entity_id="LC-CONF-001",
        actor="judge:panel-a",
        context=ruling_context,
        timestamp="2026-05-29T15:45:00Z",
    )

    appeal_context = transition_appeal.build_context(
        profile="legal-case",
        policy_version="2026.06",
        active_at="2026-06-15T09:20:00Z",
        authority_sources=[
            AuthoritySource(
                source_type="appeal-registry",
                source_id="appellate-intake",
                reference="appeal/LC-CONF-001",
            )
        ],
    )
    appealed = transition_appeal.apply_with_context(
        state=ruled.after_state,
        entity_type="legal_case",
        entity_id="LC-CONF-001",
        actor="counsel:claimant",
        context=appeal_context,
        timestamp="2026-06-15T09:20:00Z",
    )

    assert appealed.after_state["stage"] == "appealed"
    assert appealed.evidence.verify_integrity(
        before_state=appealed.before_state,
        after_state=appealed.after_state,
    )

    filed_provenance = _policy_provenance(filed.evidence)
    ruled_provenance = _policy_provenance(ruled.evidence)
    appealed_provenance = _policy_provenance(appealed.evidence)

    assert filed_provenance["reference"] == "law/legal-case-procedure@2026.01"
    assert filed_provenance["details"]["policy_version"] == "2026.01"
    assert filed_provenance["details"]["active_from"] == "2026-01-01T00:00:00Z"
    assert filed_provenance["details"]["active_until"] == "2026-06-01T00:00:00Z"

    assert ruled_provenance["reference"] == "law/legal-case-procedure@2026.01"
    assert ruled_provenance["details"]["policy_version"] == "2026.01"
    assert ruled_provenance["details"]["active_at"] == "2026-05-29T15:45:00Z"

    assert appealed_provenance["reference"] == "law/legal-case-procedure@2026.06"
    assert appealed_provenance["details"]["policy_version"] == "2026.06"
    assert appealed_provenance["details"]["active_from"] == "2026-06-01T00:00:00Z"
    assert appealed_provenance["details"]["active_at"] == "2026-06-15T09:20:00Z"
    assert "active_until" not in appealed_provenance["details"]


def test_legal_conformance_rejects_pre_cutover_appeal_transition() -> None:
    transition_appeal = Transition(
        name="file_appeal",
        mutate=lambda state: state.update({"stage": "appealed", "appeal_reason": "New evidence"}),
        profile_registry=_legal_profile_registry(),
        default_profile="legal-case",
        authority_validator=Transition.require_authority_sources(minimum=1),
    )
    pre_cutover_state = _legal_invariants_v202601().construct_state(
        {
            "case_id": "LC-CONF-002",
            "stage": "ruled",
            "reviewed_by": "reviewer:panel-a",
            "ruling_summary": "Claim denied.",
            "appeal_reason": "",
        }
    )
    context = transition_appeal.build_context(
        profile="legal-case",
        policy_version="2026.01",
        active_at="2026-05-30T12:00:00Z",
        authority_sources=[
            AuthoritySource(
                source_type="appeal-registry",
                source_id="appellate-intake",
                reference="appeal/LC-CONF-002",
            )
        ],
    )

    with pytest.raises(InvariantViolationError) as exc_info:
        transition_appeal.apply_with_context(
            state=pre_cutover_state,
            entity_type="legal_case",
            entity_id="LC-CONF-002",
            actor="counsel:claimant",
            context=context,
            timestamp="2026-05-30T12:00:00Z",
        )

    assert any(failure.invariant == "stage_known_v202601" for failure in exc_info.value.failures)
