"""Deferred sequence start on beat / downbeat."""

import asyncio
import os
import sys

PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC_PATH = os.path.join(PROJECT_ROOT, "src")
if SRC_PATH not in sys.path:
    sys.path.insert(0, SRC_PATH)

from util import sequence_playback as sp  # noqa: E402


def test_normalize_wait_for():
    assert sp._normalize_wait_for({"wait_for": "beat"}) == "beat"
    assert sp._normalize_wait_for({"start_on": "downbeat"}) == "downbeat"
    assert sp._normalize_wait_for({"wait_for": "next_beat"}) == "beat"
    assert sp._normalize_wait_for({}) is None
    assert sp._play_options_without_wait({"wait_for": "beat", "zone_id": "1"}) == {"zone_id": "1"}


def test_pending_play_status_empty():
    sp.clear_pending_play()
    assert sp.pending_play_status() == {"pending": False}


def test_queue_and_clear_pending():
    sp.clear_pending_play()
    sp._queue_pending_start("z1", "s1", "p1", {"simulated_bpm": 120}, "beat", bpm=120.0)
    st = sp.pending_play_status()
    assert st["pending"] is True
    assert st["wait_for"] == "beat"
    assert st["sequence_id"] == "s1"
    sp.clear_pending_play()
    assert sp.pending_play_status()["pending"] is False


def test_try_consume_pending_beat():
    sp.clear_pending_play()
    sp._queue_pending_start("z1", "s1", "p1", None, "beat", bpm=120.0)

    async def fake_start(*_a, **_k):
        return None

    sp._start_immediate = fake_start  # type: ignore[method-assign]
    assert asyncio.run(sp._try_consume_pending_play(is_downbeat=False)) is True
    assert sp.pending_play_status()["pending"] is False


def test_try_consume_pending_downbeat_skips_upbeat():
    sp.clear_pending_play()
    sp._queue_pending_start("z1", "s1", "p1", None, "downbeat", bpm=120.0)
    assert asyncio.run(sp._try_consume_pending_play(is_downbeat=False)) is False
    assert sp.pending_play_status()["pending"] is True

    async def fake_start(*_a, **_k):
        return None

    sp._start_immediate = fake_start  # type: ignore[method-assign]
    assert asyncio.run(sp._try_consume_pending_play(is_downbeat=True)) is True
    sp.clear_pending_play()


def test_downbeat_start_counts_trigger_beat(monkeypatch):
    """The downbeat that starts playback is beat 1 of the step, not beat 0."""
    sp.clear_pending_play()
    sp.stop()

    async def fake_start(_z, _s, _p, _opts):
        sp._beat_run = {
            "lanes": [[{"preset_id": "1", "beats": 4}]],
            "lane_states": [{"stepIdx": 0, "beatCount": 0, "done": False}],
            "num_lanes": 1,
            "sequence_loop_beat": 0,
        }

    monkeypatch.setattr(sp, "_start_immediate", fake_start)
    sp._queue_pending_start("z1", "s1", "p1", None, "downbeat", bpm=120.0)

    async def run():
        assert await sp._try_consume_pending_play(is_downbeat=True) is True
        await sp.process_active_beat_advance()

    asyncio.run(run())
    assert sp._beat_run["lane_states"][0]["beatCount"] == 1
    sp.stop()

