from __future__ import annotations

import pytest

from icsd.adapters import JsonSchemaInvariant
from icsd.adapters.transparency_stream import TransparencyStreamReceipt
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.serialise import hash_canonical_json
from icsd.core.transition import Transition

CUTOVER_TIMESTAMP = "2026-06-01T00:00:00Z"


def _event_schema_v1() -> dict[str, object]:
    return {
        "type": "object",
        "required": ["event_id", "schema_version", "stage", "payload"],
        "properties": {
            "event_id": {"type": "string", "minLength": 1},
            "schema_version": {"const": "1.0"},
            "stage": {"type": "string", "enum": ["ingested", "validated"]},
            "payload": {
                "type": "object",
                "required": ["record_count", "total_amount"],
                "properties": {
                    "record_count": {"type": "integer", "minimum": 1},
                    "total_amount": {"type": "number"},
                },
                "additionalProperties": False,
            },
        },
        "additionalProperties": False,
    }


def _event_schema_v2() -> dict[str, object]:
    return {
        "type": "object",
        "required": ["event_id", "schema_version", "stage", "payload"],
        "properties": {
            "event_id": {"type": "string", "minLength": 1},
            "schema_version": {"const": "2.0"},
            "stage": {"type": "string", "enum": ["ingested", "validated", "released"]},
            "payload": {
                "type": "object",
                "required": [
                    "record_count",
                    "total_amount",
                    "lineage_id",
                    "quality_score",
                    "anomaly_count",
                ],
                "properties": {
                    "record_count": {"type": "integer", "minimum": 1},
                    "total_amount": {"type": "number"},
                    "lineage_id": {"type": "string", "minLength": 1},
                    "quality_score": {"type": "number", "minimum": 0.0, "maximum": 1.0},
                    "anomaly_count": {"type": "integer", "minimum": 0},
                },
                "additionalProperties": False,
            },
        },
        "additionalProperties": False,
    }


def _event_invariants_v1() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="schema_v1_contract",
                check=JsonSchemaInvariant(
                    schema=_event_schema_v1(),
                    name="schema_v1_contract",
                ),
            ),
            Invariant(
                name="non_negative_total_amount",
                check=_non_negative_total_amount,
            ),
        ]
    )


def _event_invariants_v2() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="schema_v2_contract",
                check=JsonSchemaInvariant(
                    schema=_event_schema_v2(),
                    name="schema_v2_contract",
                ),
            ),
            Invariant(
                name="non_negative_total_amount",
                check=_non_negative_total_amount,
            ),
            Invariant(
                name="lineage_prefix_valid",
                check=_lineage_prefix_valid,
            ),
            Invariant(
                name="released_quality_gate",
                check=_released_quality_gate,
            ),
        ]
    )


def _event_registry() -> InvariantProfileRegistry:
    return InvariantProfileRegistry(
        [
            InvariantProfile(
                name="event-pipeline",
                policy="policy/event-pipeline-governance",
                policy_version="2026.03",
                active_from="2026-01-01T00:00:00Z",
                active_until=CUTOVER_TIMESTAMP,
                invariants=_event_invariants_v1(),
            ),
            InvariantProfile(
                name="event-pipeline",
                policy="policy/event-pipeline-governance",
                policy_version="2026.06",
                active_from=CUTOVER_TIMESTAMP,
                invariants=_event_invariants_v2(),
            ),
        ]
    )


def _upgrade_event_to_v2(state: dict[str, object]) -> None:
    payload = state.get("payload", {})
    if not isinstance(payload, dict):
        payload = {}
    payload = dict(payload)
    payload.setdefault("lineage_id", f"lineage/{state.get('event_id', 'unknown')}")
    payload.setdefault("quality_score", 0.99)
    payload.setdefault("anomaly_count", 0)
    state["schema_version"] = "2.0"
    state["payload"] = payload
    if state.get("stage") not in {"ingested", "validated"}:
        state["stage"] = "validated"


def _mutate_validated(state: dict[str, object]) -> None:
    state["stage"] = "validated"


def _payload_mapping(state: dict[str, object]) -> dict[str, object]:
    payload = state.get("payload", {})
    if isinstance(payload, dict):
        return payload
    return {}


def _non_negative_total_amount(state: dict[str, object]) -> bool:
    payload = _payload_mapping(state)
    value = payload.get("total_amount")
    return isinstance(value, (int, float)) and not isinstance(value, bool) and value >= 0.0


def _lineage_prefix_valid(state: dict[str, object]) -> bool:
    payload = _payload_mapping(state)
    return str(payload.get("lineage_id", "")).startswith("lineage/")


def _released_quality_gate(state: dict[str, object]) -> bool:
    if state.get("stage") != "released":
        return True
    payload = _payload_mapping(state)
    quality_score = payload.get("quality_score")
    anomaly_count = payload.get("anomaly_count")
    return (
        isinstance(quality_score, (int, float))
        and not isinstance(quality_score, bool)
        and quality_score >= 0.95
        and isinstance(anomaly_count, int)
        and not isinstance(anomaly_count, bool)
        and anomaly_count == 0
    )


def _mutate_release_low_quality(state: dict[str, object]) -> None:
    payload = dict(state["payload"])
    payload["quality_score"] = 0.40
    payload["anomaly_count"] = 0
    state["payload"] = payload
    state["stage"] = "released"


def _mutate_release_compliant(state: dict[str, object]) -> None:
    payload = dict(state["payload"])
    payload["quality_score"] = 0.99
    payload["anomaly_count"] = 0
    state["payload"] = payload
    state["stage"] = "released"


class _InMemoryAuditorFeed:
    def __init__(self) -> None:
        self.events: list[dict[str, object]] = []

    def emit(self, *, event_payload: dict[str, object]) -> TransparencyStreamReceipt:
        self.events.append(dict(event_payload))
        return TransparencyStreamReceipt(
            adapter="auditor-feed",
            event_id=f"evt/{len(self.events):04d}",
        )


def _legacy_event_state() -> dict[str, object]:
    return _event_invariants_v1().construct_state(
        {
            "event_id": "EVT-CONF-0040",
            "schema_version": "1.0",
            "stage": "ingested",
            "payload": {
                "record_count": 12,
                "total_amount": 1025.5,
            },
        }
    )


def _authority_source(reference: str) -> AuthoritySource:
    return AuthoritySource(
        source_type="pipeline-controller",
        source_id="event-governor",
        reference=reference,
    )


def _policy_profile_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_event_pipeline_conformance_cutover_migration_and_streaming() -> None:
    registry = _event_registry()
    validate_transition = Transition(
        name="validate_event",
        mutate=_mutate_validated,
        profile_registry=registry,
        default_profile="event-pipeline",
        authority_validator=Transition.require_authority_sources(minimum=1),
    )
    release_transition = Transition(
        name="release_event",
        mutate=_mutate_release_compliant,
        profile_registry=registry,
        default_profile="event-pipeline",
        authority_validator=Transition.require_authority_sources(minimum=1),
    )

    legacy_state = _legacy_event_state()
    validate_context = validate_transition.build_context(
        profile="event-pipeline",
        authority_sources=[_authority_source("pipeline/EVT-CONF-0040/validate")],
        active_at="2026-05-30T22:45:00Z",
    )
    pre_cutover = validate_transition.apply_with_context(
        state=legacy_state,
        entity_type="pipeline_event",
        entity_id="EVT-CONF-0040",
        actor="pipeline:validator",
        context=validate_context,
        timestamp="2026-05-30T22:45:00Z",
    )
    assert pre_cutover.evidence.verify_integrity(
        before_state=pre_cutover.before_state,
        after_state=pre_cutover.after_state,
    )
    assert (
        _policy_profile_provenance(pre_cutover.evidence)["details"]["policy_version"] == "2026.03"
    )

    release_context = release_transition.build_context(
        profile="event-pipeline",
        authority_sources=[_authority_source("pipeline/EVT-CONF-0040/release-attempt-1")],
        active_at="2026-06-02T09:00:00Z",
    )
    with pytest.raises(InvariantViolationError) as legacy_cutover_error:
        release_transition.apply_with_context(
            state=pre_cutover.after_state,
            entity_type="pipeline_event",
            entity_id="EVT-CONF-0040",
            actor="pipeline:release-controller",
            context=release_context,
            timestamp="2026-06-02T09:00:00Z",
        )
    assert any(
        failure.invariant == "schema_v2_contract" for failure in legacy_cutover_error.value.failures
    )

    report = migrate_legacy_states(
        [
            pre_cutover.after_state,
            {
                "event_id": "EVT-CONF-0041",
                "schema_version": "1.0",
                "stage": "validated",
                "payload": {"record_count": 3, "total_amount": -5.0},
            },
        ],
        invariants=_event_invariants_v2(),
        transform=_upgrade_event_to_v2,
    )
    assert report.migrated_count == 1
    assert report.quarantined_count == 1
    migrated_state = report.migrated_states[0]

    stream = _InMemoryAuditorFeed()
    low_quality_transition = Transition(
        name="release_event",
        mutate=_mutate_release_low_quality,
        profile_registry=registry,
        default_profile="event-pipeline",
        authority_validator=Transition.require_authority_sources(minimum=1),
        transparency_stream=stream,
    )
    low_quality_context = low_quality_transition.build_context(
        profile="event-pipeline",
        authority_sources=[_authority_source("pipeline/EVT-CONF-0040/release-attempt-2")],
        active_at="2026-06-02T09:05:00Z",
    )
    with pytest.raises(InvariantViolationError) as low_quality_error:
        low_quality_transition.apply_with_context(
            state=migrated_state,
            entity_type="pipeline_event",
            entity_id="EVT-CONF-0040",
            actor="pipeline:release-controller",
            context=low_quality_context,
            timestamp="2026-06-02T09:05:00Z",
        )
    assert any(
        failure.invariant == "released_quality_gate" for failure in low_quality_error.value.failures
    )
    assert len(stream.events) == 0

    release_streaming = Transition(
        name="release_event",
        mutate=_mutate_release_compliant,
        profile_registry=registry,
        default_profile="event-pipeline",
        authority_validator=Transition.require_authority_sources(minimum=1),
        transparency_stream=stream,
    )
    release_stream_context = release_streaming.build_context(
        profile="event-pipeline",
        authority_sources=[_authority_source("pipeline/EVT-CONF-0040/release")],
        active_at="2026-06-02T09:10:00Z",
    )
    released = release_streaming.apply_with_context(
        state=migrated_state,
        entity_type="pipeline_event",
        entity_id="EVT-CONF-0040",
        actor="pipeline:release-controller",
        context=release_stream_context,
        timestamp="2026-06-02T09:10:00Z",
    )
    assert released.after_state["stage"] == "released"
    assert released.evidence.verify_integrity(
        before_state=released.before_state,
        after_state=released.after_state,
    )
    provenance = _policy_profile_provenance(released.evidence)
    assert provenance["details"]["policy_version"] == "2026.06"
    assert provenance["details"]["active_at"] == "2026-06-02T09:10:00Z"

    assert len(stream.events) == 1
    stream_event = stream.events[0]
    assert stream_event["transition_name"] == "release_event"
    assert stream_event["transition_id"] == released.evidence.transition_id
    assert stream_event["evidence_hash"] == hash_canonical_json(released.evidence.as_dict())
    assert stream_event["evidence"]["authority"] == "policy/event-pipeline-governance@2026.06"
