from __future__ import annotations

from pathlib import Path

import pytest

from sync_state_machine.common.types import BindingStatus, SyncAction, SyncStatus
from sync_state_machine.engine import StateMachineConfig, StateMachineRuntime
from sync_state_machine.engine.events import e10_bind_core
from sync_state_machine.engine.invariants import InvariantViolation
from tests.mocks.mock_datasource import MockSyncNode


CFG_PATH = Path(__file__).resolve().parents[2] / "sync_state_machine" / "config" / "node_state_machine.yaml"


def _new_node(node_id: str) -> MockSyncNode:
    return MockSyncNode(
        node_id=node_id,
        data_id="seed-id",
        data={"id": node_id, "name": "mock"},
        action=SyncAction.NONE,
        status=SyncStatus.PENDING,
        binding_status=BindingStatus.UNCHECKED,
    )


def test_event_path_calls_check_invariants(monkeypatch: pytest.MonkeyPatch) -> None:
    runtime = StateMachineRuntime(StateMachineConfig.load(CFG_PATH))
    called = {"value": False}

    def _fake_check_invariants(snapshot):
        called["value"] = True
        return []

    monkeypatch.setattr(runtime, "check_invariants", _fake_check_invariants)

    node = _new_node("inv-call")
    decision = e10_bind_core(
        runtime,
        node=node,
        has_binding_record=False,
    )

    assert decision.to_state == "S02"
    assert called["value"] is True


def test_event_path_raises_on_invariant_violation(monkeypatch: pytest.MonkeyPatch) -> None:
    runtime = StateMachineRuntime(StateMachineConfig.load(CFG_PATH))

    def _fake_violation(snapshot):
        return [InvariantViolation(invariant_id="INV-X", message="boom")]

    monkeypatch.setattr(runtime, "check_invariants", _fake_violation)

    node = _new_node("inv-fail")
    with pytest.raises(RuntimeError, match="Invariant violation"):
        e10_bind_core(
            runtime,
            node=node,
            has_binding_record=False,
        )
