from __future__ import annotations

import pytest

from icsd.adapters import TransparencyLogReceipt
from icsd.core.errors import InvariantViolationError
from icsd.core.evidence import AuthoritySource, EvidenceBundle
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition


def _supply_chain_invariants() -> InvariantSet:
    return InvariantSet(
        [
            Invariant(
                name="stage_known",
                check=lambda state: (
                    state.get("stage") in {"batch_created", "handed_off", "received", "released"}
                ),
            ),
            Invariant(
                name="temperature_bounds_valid",
                check=lambda state: float(state["min_temp_c"]) < float(state["max_temp_c"]),
            ),
            Invariant(
                name="temperature_within_range",
                check=lambda state: (
                    float(state["min_temp_c"])
                    <= float(state["temperature_c"])
                    <= float(state["max_temp_c"])
                ),
            ),
            Invariant(
                name="custody_continuity",
                check=lambda state: (
                    state.get("stage") == "batch_created"
                    or (
                        bool(str(state.get("previous_holder", "")).strip())
                        and bool(str(state.get("current_holder", "")).strip())
                        and bool(str(state.get("custody_actor", "")).strip())
                    )
                ),
            ),
            Invariant(
                name="released_requires_inspection_reference",
                check=lambda state: (
                    state.get("stage") != "released"
                    or (
                        bool(str(state.get("released_by", "")).strip())
                        and bool(str(state.get("release_inspection_ref", "")).strip())
                    )
                ),
            ),
        ]
    )


class _ColdChainReceiptLog:
    def __init__(self) -> None:
        self._counter = 0

    def append(self, *, evidence_payload: dict[str, object]) -> TransparencyLogReceipt:
        self._counter += 1
        source_details = _authority_source_details(evidence_payload)
        return TransparencyLogReceipt(
            adapter="cold-chain-receipt-log",
            reference=f"receipt/{self._counter:04d}",
            timestamp=str(source_details["reading_timestamp"]),
            details={
                "actor": str(evidence_payload["actor"]),
                "location": str(source_details["location"]),
                "reading_timestamp": str(source_details["reading_timestamp"]),
                "temperature_c": float(source_details["temperature_c"]),
            },
        )

    def verify(self, *, evidence_payload: dict[str, object], receipt: object = None) -> bool:
        del evidence_payload, receipt
        return True


def _authority_source_details(evidence_payload: dict[str, object]) -> dict[str, object]:
    authority_sources = evidence_payload.get("authority_sources", [])
    source = authority_sources[0]
    assert isinstance(source, dict)
    details = source.get("details", {})
    assert isinstance(details, dict)
    return details


def _cold_chain_provenance(evidence: EvidenceBundle) -> dict[str, object]:
    entry = next(
        item
        for item in evidence.provenance
        if item.source == "transparency-log/cold-chain-receipt-log"
    )
    return entry.as_dict()


def _cold_chain_source(
    *,
    source_type: str,
    source_id: str,
    reference: str,
    location: str,
    reading_timestamp: str,
    temperature_c: float,
) -> AuthoritySource:
    return AuthoritySource(
        source_type=source_type,
        source_id=source_id,
        reference=reference,
        details={
            "location": location,
            "reading_timestamp": reading_timestamp,
            "temperature_c": temperature_c,
        },
    )


def _base_batch_state() -> dict[str, object]:
    return _supply_chain_invariants().construct_state(
        {
            "batch_id": "BATCH-CONF-001",
            "stage": "batch_created",
            "previous_holder": "manufacturer:line-7",
            "current_holder": "warehouse:cold-room-a",
            "custody_actor": "operator:create-1",
            "location": "warehouse:cold-room-a",
            "temperature_c": 4.1,
            "reading_timestamp": "2026-03-07T08:00:00Z",
            "min_temp_c": 2.0,
            "max_temp_c": 8.0,
            "released_by": "",
            "release_inspection_ref": "",
        }
    )


def _require_release_inspector(authority: str, sources: tuple[AuthoritySource, ...]) -> None:
    if not sources or all(source.source_type != "inspector" for source in sources):
        msg = (
            f"release authority validation failed for {authority!r}: "
            "expected at least one source_type='inspector'."
        )
        raise ValueError(msg)


def test_supply_chain_conformance_records_custody_and_temperature_provenance() -> None:
    invariants = _supply_chain_invariants()
    receipt_log = _ColdChainReceiptLog()

    handoff = Transition(
        name="handed_off",
        mutate=lambda state: state.update(
            {
                "stage": "handed_off",
                "previous_holder": "warehouse:cold-room-a",
                "current_holder": "courier:route-14",
                "custody_actor": "operator:handoff-1",
                "location": "dock:west-gate",
                "temperature_c": 4.8,
                "reading_timestamp": "2026-03-07T09:15:00Z",
            }
        ),
        invariants=invariants,
        authority_validator=Transition.require_authority_sources(minimum=1),
        transparency_log=receipt_log,
    )
    receive = Transition(
        name="received",
        mutate=lambda state: state.update(
            {
                "stage": "received",
                "previous_holder": "courier:route-14",
                "current_holder": "clinic:cold-room-b",
                "custody_actor": "receiver:clinic-ops",
                "location": "clinic:cold-room-b",
                "temperature_c": 4.5,
                "reading_timestamp": "2026-03-07T10:20:00Z",
            }
        ),
        invariants=invariants,
        authority_validator=Transition.require_authority_sources(minimum=1),
        transparency_log=receipt_log,
    )
    release = Transition(
        name="released",
        mutate=lambda state: state.update(
            {
                "stage": "released",
                "previous_holder": "clinic:cold-room-b",
                "current_holder": "hospital:pharmacy-1",
                "custody_actor": "pharmacist:release-1",
                "released_by": "pharmacist:release-1",
                "release_inspection_ref": "inspection/BATCH-CONF-001/release",
                "location": "hospital:pharmacy-1",
                "temperature_c": 4.3,
                "reading_timestamp": "2026-03-07T11:00:00Z",
            }
        ),
        invariants=invariants,
        authority_validator=_require_release_inspector,
        transparency_log=receipt_log,
    )

    handoff_context = handoff.build_context(
        authority="policy/supply-chain/handoff",
        authority_sources=[
            _cold_chain_source(
                source_type="handoff-terminal",
                source_id="dock-west-gate",
                reference="batch/BATCH-CONF-001/handoff-1",
                location="dock:west-gate",
                reading_timestamp="2026-03-07T09:15:00Z",
                temperature_c=4.8,
            )
        ],
    )
    handed_off = handoff.apply_with_context(
        state=_base_batch_state(),
        entity_type="cold_chain_batch",
        entity_id="BATCH-CONF-001",
        actor="operator:handoff-1",
        context=handoff_context,
        timestamp="2026-03-07T09:15:00Z",
    )

    receive_context = receive.build_context(
        authority="policy/supply-chain/receive",
        authority_sources=[
            _cold_chain_source(
                source_type="receiver-system",
                source_id="clinic-cold-room-b",
                reference="batch/BATCH-CONF-001/receipt",
                location="clinic:cold-room-b",
                reading_timestamp="2026-03-07T10:20:00Z",
                temperature_c=4.5,
            )
        ],
    )
    received = receive.apply_with_context(
        state=handed_off.after_state,
        entity_type="cold_chain_batch",
        entity_id="BATCH-CONF-001",
        actor="receiver:clinic-ops",
        context=receive_context,
        timestamp="2026-03-07T10:20:00Z",
    )

    release_context = release.build_context(
        authority="policy/supply-chain/release",
        authority_sources=[
            _cold_chain_source(
                source_type="inspector",
                source_id="qa-release-team",
                reference="batch/BATCH-CONF-001/release",
                location="hospital:pharmacy-1",
                reading_timestamp="2026-03-07T11:00:00Z",
                temperature_c=4.3,
            )
        ],
    )
    released = release.apply_with_context(
        state=received.after_state,
        entity_type="cold_chain_batch",
        entity_id="BATCH-CONF-001",
        actor="pharmacist:release-1",
        context=release_context,
        timestamp="2026-03-07T11:00:00Z",
    )

    assert released.after_state["stage"] == "released"
    assert handed_off.evidence.verify_integrity(
        before_state=handed_off.before_state,
        after_state=handed_off.after_state,
    )
    assert received.evidence.verify_integrity(
        before_state=received.before_state,
        after_state=received.after_state,
    )
    assert released.evidence.verify_integrity(
        before_state=released.before_state,
        after_state=released.after_state,
    )

    handoff_provenance = _cold_chain_provenance(handed_off.evidence)
    received_provenance = _cold_chain_provenance(received.evidence)
    release_provenance = _cold_chain_provenance(released.evidence)

    assert handoff_provenance["details"]["actor"] == "operator:handoff-1"
    assert handoff_provenance["details"]["location"] == "dock:west-gate"
    assert handoff_provenance["details"]["reading_timestamp"] == "2026-03-07T09:15:00Z"
    assert handoff_provenance["details"]["temperature_c"] == 4.8

    assert received_provenance["details"]["actor"] == "receiver:clinic-ops"
    assert received_provenance["details"]["location"] == "clinic:cold-room-b"
    assert received_provenance["details"]["reading_timestamp"] == "2026-03-07T10:20:00Z"
    assert received_provenance["details"]["temperature_c"] == 4.5

    assert release_provenance["details"]["actor"] == "pharmacist:release-1"
    assert release_provenance["details"]["location"] == "hospital:pharmacy-1"
    assert release_provenance["details"]["reading_timestamp"] == "2026-03-07T11:00:00Z"
    assert release_provenance["details"]["temperature_c"] == 4.3


def test_supply_chain_conformance_rejects_out_of_range_and_missing_inspector() -> None:
    invariants = _supply_chain_invariants()
    receipt_log = _ColdChainReceiptLog()

    handoff = Transition(
        name="handed_off",
        mutate=lambda state: state.update(
            {
                "stage": "handed_off",
                "previous_holder": "warehouse:cold-room-a",
                "current_holder": "courier:route-14",
                "custody_actor": "operator:handoff-1",
                "location": "dock:west-gate",
                "temperature_c": 9.6,
                "reading_timestamp": "2026-03-07T09:10:00Z",
            }
        ),
        invariants=invariants,
        authority_validator=Transition.require_authority_sources(minimum=1),
        transparency_log=receipt_log,
    )
    invalid_handoff_context = handoff.build_context(
        authority="policy/supply-chain/handoff",
        authority_sources=[
            _cold_chain_source(
                source_type="handoff-terminal",
                source_id="dock-west-gate",
                reference="batch/BATCH-CONF-001/handoff-attempt-1",
                location="dock:west-gate",
                reading_timestamp="2026-03-07T09:10:00Z",
                temperature_c=9.6,
            )
        ],
    )

    with pytest.raises(InvariantViolationError) as exc_info:
        handoff.apply_with_context(
            state=_base_batch_state(),
            entity_type="cold_chain_batch",
            entity_id="BATCH-CONF-001",
            actor="operator:handoff-1",
            context=invalid_handoff_context,
            timestamp="2026-03-07T09:10:00Z",
        )
    assert any(
        failure.invariant == "temperature_within_range" for failure in exc_info.value.failures
    )

    release = Transition(
        name="released",
        mutate=lambda state: state.update(
            {
                "stage": "released",
                "previous_holder": "clinic:cold-room-b",
                "current_holder": "hospital:pharmacy-1",
                "custody_actor": "pharmacist:release-1",
                "released_by": "pharmacist:release-1",
                "release_inspection_ref": "inspection/BATCH-CONF-001/release",
                "location": "hospital:pharmacy-1",
                "temperature_c": 4.3,
                "reading_timestamp": "2026-03-07T11:00:00Z",
            }
        ),
        invariants=invariants,
        authority_validator=_require_release_inspector,
        transparency_log=receipt_log,
    )
    with pytest.raises(ValueError, match="release authority validation failed"):
        release.build_context(
            authority="policy/supply-chain/release",
            authority_sources=[
                _cold_chain_source(
                    source_type="receiver-system",
                    source_id="clinic-cold-room-b",
                    reference="batch/BATCH-CONF-001/release-attempt-1",
                    location="hospital:pharmacy-1",
                    reading_timestamp="2026-03-07T11:00:00Z",
                    temperature_c=4.3,
                )
            ],
        )
