import pytest

from recoil.pipeline.core.take import Beat


ARTIFACT = "prep/ep_001/storyboards/EP001_CONT_004_v01.png"
SOURCE_SHA256 = "a" * 64


def test_board_round_trips_through_dict() -> None:
    beat = Beat("EP001_CONT_004")
    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256)

    data = beat.to_dict()
    assert data["board"]["status"] == "proposed"
    assert data["board"]["artifact"] == ARTIFACT
    assert data["board"]["source_sha256"] == SOURCE_SHA256
    assert data["board"]["approved_by"] is None
    assert isinstance(data["board"]["updated_at"], str)

    restored = Beat.from_dict(data)
    assert restored.board == data["board"]
    assert restored.to_dict()["board"] == data["board"]


def test_board_proposed_defaults_to_fingerprint_version_1() -> None:
    beat = Beat("EP001_CONT_004")

    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256)

    assert beat.board["fingerprint_version"] == 1


def test_board_proposed_accepts_fingerprint_version_2() -> None:
    beat = Beat("EP001_CONT_004")

    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256, fingerprint_version=2)

    assert beat.board["fingerprint_version"] == 2


@pytest.mark.parametrize("fingerprint_version", [3, True, False, 1.0, 2.0])
def test_board_proposed_rejects_invalid_fingerprint_version(
    fingerprint_version: object,
) -> None:
    beat = Beat("EP001_CONT_004")

    with pytest.raises(ValueError):
        beat.set_board_proposed(
            ARTIFACT,
            SOURCE_SHA256,
            fingerprint_version=fingerprint_version,
        )


def test_legacy_dict_without_board_loads_with_none_board() -> None:
    data = Beat("EP001_CONT_004").to_dict()
    data.pop("board")

    restored = Beat.from_dict(data)

    assert restored.board is None
    assert restored.to_dict()["board"] is None


def test_board_validate_accepts_legacy_board_without_fingerprint_version() -> None:
    data = Beat("EP001_CONT_004").to_dict()
    data["board"] = {
        "status": "proposed",
        "artifact": ARTIFACT,
        "source_sha256": SOURCE_SHA256,
        "approved_by": None,
        "updated_at": "2026-06-14T00:00:00Z",
    }

    restored = Beat.from_dict(data)

    assert "fingerprint_version" not in restored.board


@pytest.mark.parametrize("fingerprint_version", [3, True, 1.0])
def test_board_validate_rejects_invalid_fingerprint_version(
    fingerprint_version: object,
) -> None:
    data = Beat("EP001_CONT_004").to_dict()
    data["board"] = {
        "status": "proposed",
        "artifact": ARTIFACT,
        "source_sha256": SOURCE_SHA256,
        "fingerprint_version": fingerprint_version,
        "approved_by": None,
        "updated_at": "2026-06-14T00:00:00Z",
    }

    with pytest.raises(ValueError):
        Beat.from_dict(data)


def test_board_helper_state_machine_approve_and_reject() -> None:
    approved = Beat("EP001_CONT_004")
    approved.set_board_proposed(ARTIFACT, SOURCE_SHA256)
    approved.approve_board("JT")

    assert approved.board["status"] == "approved"
    assert approved.board["approved_by"] == "JT"
    assert isinstance(approved.board["updated_at"], str)

    rejected = Beat("EP001_CONT_007")
    rejected.set_board_proposed(ARTIFACT, SOURCE_SHA256)
    rejected.reject_board("JT")

    assert rejected.board["status"] == "rejected"
    assert rejected.board["approved_by"] == "JT"
    assert isinstance(rejected.board["updated_at"], str)


def test_board_approve_preserves_fingerprint_version() -> None:
    beat = Beat("EP001_CONT_004")
    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256, fingerprint_version=2)

    beat.approve_board("JT")

    assert beat.board["fingerprint_version"] == 2


def test_approve_without_proposed_board_raises() -> None:
    beat = Beat("EP001_CONT_004")

    with pytest.raises(ValueError):
        beat.approve_board("JT")


def test_board_is_approved_truth_table() -> None:
    beat = Beat("EP001_CONT_004")
    assert beat.board_is_approved() is False

    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256)
    assert beat.board_is_approved() is False

    beat.approve_board("JT")
    assert beat.board_is_approved() is True

    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256)
    beat.reject_board("JT")
    assert beat.board_is_approved() is False
