from __future__ import annotations

import hashlib
from datetime import datetime, timezone

import pytest

from icsd.adapters.transparency_stream import TransparencyStreamReceipt
from icsd.core.evidence import EvidenceBundle, EvidenceSignature
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition


def _balance_invariants() -> InvariantSet:
    return InvariantSet(
        [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
    )


class _InMemoryTransparencyStream:
    def __init__(
        self,
        *,
        receipt: TransparencyStreamReceipt | dict[str, object] | None = None,
    ) -> None:
        self.emit_calls: list[dict[str, object]] = []
        self._receipt = receipt

    def emit(self, *, event_payload: dict[str, object]) -> dict[str, object] | None:
        self.emit_calls.append(dict(event_payload))
        if isinstance(self._receipt, TransparencyStreamReceipt):
            payload: dict[str, object] = {
                "adapter": self._receipt.adapter,
                "event_id": self._receipt.event_id,
            }
            if self._receipt.timestamp is not None:
                payload["timestamp"] = self._receipt.timestamp
            if self._receipt.details:
                payload["details"] = dict(self._receipt.details)
            return payload
        return self._receipt


def test_evidence_bundle_as_stream_event_payload_includes_signed_shape() -> None:
    bundle = EvidenceBundle.from_states(
        transition_id="11111111-1111-4111-8111-111111111111",
        entity_type="account",
        entity_id="acct-001",
        actor="user:ops",
        authority="policy/account-debit",
        before_state={"balance": 100},
        after_state={"balance": 80},
        timestamp=datetime(2026, 3, 3, 10, 0, tzinfo=timezone.utc),
        signatures=[
            EvidenceSignature(
                key_id="k1",
                algorithm="ed25519",
                signature="aGVsbG8=",
            )
        ],
    )

    event = bundle.as_stream_event_payload(
        transition_name="debit_account",
        emitted_at="2026-03-03T11:00:00Z",
    )

    assert event["event_type"] == "icsd.evidence_bundle.v0.1"
    assert event["stream"] == "icsd.evidence"
    assert event["emitted_at"] == "2026-03-03T11:00:00Z"
    assert event["transition_id"] == bundle.transition_id
    assert event["signature_count"] == 1
    assert event["signable_hash"] == hashlib.sha256(bundle.signable_bytes()).hexdigest()
    assert event["evidence"]["signatures"][0]["key_id"] == "k1"


def test_transition_apply_emits_transparency_stream_event() -> None:
    stream = _InMemoryTransparencyStream(
        receipt=TransparencyStreamReceipt(adapter="memory-stream", event_id="event/1")
    )
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state.update({"balance": state["balance"] - 20}),
        invariants=_balance_invariants(),
        transparency_stream=stream,
    )

    result = transition.apply(
        state={"balance": 100},
        entity_type="account",
        entity_id="acct-001",
        actor="user:ops",
        authority="policy/account-debit",
    )

    assert result.after_state == {"balance": 80}
    assert len(stream.emit_calls) == 1
    event_payload = stream.emit_calls[0]
    assert event_payload["transition_name"] == "debit_account"
    assert event_payload["transition_id"] == result.evidence.transition_id
    assert event_payload["evidence"]["authority"] == "policy/account-debit"


def test_transition_apply_accepts_stream_adapter_without_receipt() -> None:
    stream = _InMemoryTransparencyStream(receipt=None)
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state,
        invariants=_balance_invariants(),
        transparency_stream=stream,
    )

    transition.apply(
        state={"balance": 100},
        entity_type="account",
        entity_id="acct-001",
        actor="user:ops",
        authority="policy/account-debit",
    )

    assert len(stream.emit_calls) == 1


def test_transition_apply_rejects_invalid_stream_receipt_shape() -> None:
    stream = _InMemoryTransparencyStream(receipt={"adapter": "memory-stream"})
    transition = Transition(
        name="debit_account",
        mutate=lambda state: state,
        invariants=_balance_invariants(),
        transparency_stream=stream,
    )

    with pytest.raises(ValueError, match="missing required fields: event_id"):
        transition.apply(
            state={"balance": 100},
            entity_type="account",
            entity_id="acct-001",
            actor="user:ops",
            authority="policy/account-debit",
        )


def test_transition_rejects_invalid_transparency_stream_adapter_type() -> None:
    with pytest.raises(TypeError, match="transparency_stream must implement"):
        Transition(
            name="debit_account",
            mutate=lambda state: state,
            invariants=_balance_invariants(),
            transparency_stream=object(),  # type: ignore[arg-type]
        )
