"""REC-225: fail-loud board re-point invariant.

After a board write re-points beat.board via save_active_scene_status, the
canonical active scene is re-read and the pointer must have advanced to the
artifact we wrote — otherwise a silently-dropped re-point (stale pointer +
newer sidecar) is raised immediately instead of surfacing at the spend gate.
"""

import pytest

from recoil.pipeline._lib import board_builder as bb


class _Beat:
    def __init__(self, artifact):
        self.board = {"artifact": artifact} if artifact is not None else None


def _patch(monkeypatch, persisted_artifact):
    monkeypatch.setattr(bb, "load_scene_active", lambda *a, **k: object())
    monkeypatch.setattr(bb, "_single_r2v_multi_beat", lambda s: _Beat(persisted_artifact))


V6 = "prep/ep_001/storyboards/EP001_CONT_001_v06.png"
V4 = "prep/ep_001/storyboards/EP001_CONT_001_v04.png"


def test_passes_when_pointer_advanced(monkeypatch):
    _patch(monkeypatch, V6)
    # persisted pointer == the artifact we wrote → no raise
    bb._assert_board_repointed("tartarus", "ep_001", "ep_001_BATCH_001", V6)


def test_raises_on_stale_pointer_drift(monkeypatch):
    _patch(monkeypatch, V4)  # the REC-225 live case: sidecar v06, pointer still v04
    with pytest.raises(bb.BoardBuilderError, match="re-point drift"):
        bb._assert_board_repointed("tartarus", "ep_001", "ep_001_BATCH_001", V6)


def test_raises_when_board_pointer_absent(monkeypatch):
    _patch(monkeypatch, None)  # re-point no-op'd entirely → beat.board still None
    with pytest.raises(bb.BoardBuilderError, match="re-point drift"):
        bb._assert_board_repointed("tartarus", "ep_001", "ep_001_BATCH_001", V6)
