from styles.blink import (
    STATE_BLINKING,
    STATE_EVALUATING,
    STATE_WAIT_BEFORE_BLINK,
    STATE_WAIT_BEFORE_RETRY,
    STATE_WAIT_FOR_INPUT,
    BlinkStyle,
)


def _led():
    """Return (calls list, set_led callable recording each call)."""
    calls = []
    return calls, lambda on: calls.append(on)


def test_press_starts_puzzle():
    _, set_led = _led()
    style = BlinkStyle(set_led)
    assert style.update(100.0, True) is True
    assert style.state == STATE_WAIT_BEFORE_BLINK


def test_blink_correct_sequence_stops(monkeypatch):
    _, set_led = _led()
    style = BlinkStyle(set_led)

    style.update(100.0, True)  # press -> start puzzle
    assert style.state == STATE_WAIT_BEFORE_BLINK

    monkeypatch.setattr("styles.blink.random.randint", lambda a, b: 3)
    style.update(103.1, False)  # 3s passed -> blinking, 3 blinks
    assert style.state == STATE_BLINKING
    assert style.target_blinks == 3

    style.update(105.0, False)  # run through the blink sequence
    assert style.state == STATE_WAIT_FOR_INPUT

    # Press exactly 3 times
    for t in (105.5, 106.0, 106.5):
        style.update(t, True)
        style.update(t + 0.05, False)
    assert style.user_presses == 3

    style.update(109.7, False)  # 3s idle -> evaluating
    assert style.state == STATE_EVALUATING
    assert style.update(109.8, False) is False  # correct -> stop


def test_blink_incorrect_retries():
    _, set_led = _led()
    style = BlinkStyle(set_led)
    style.state = STATE_WAIT_FOR_INPUT
    style.target_blinks = 3

    style.update(100.0, True)  # one press
    style.update(100.1, False)

    style.last_interaction_time = 100.1
    style.update(103.2, False)  # -> evaluating
    keep_running = style.update(103.3, False)  # 1 != 3 -> retry

    assert keep_running is True
    assert style.state == STATE_WAIT_BEFORE_RETRY


def test_blinking_non_blocking():
    calls, set_led = _led()
    style = BlinkStyle(set_led)
    style.state = STATE_BLINKING
    style.target_blinks = 2

    style.update(100.0, False)  # start of blinking: LED on
    assert style.state == STATE_BLINKING
    assert calls[-1] is True

    style.update(100.3, False)  # mid-blink toggles by elapsed time
    assert calls[-1] is False

    # 2 blinks * 2 phases * 0.3s = 1.2s
    style.update(101.2, False)
    assert style.state == STATE_WAIT_FOR_INPUT
    assert calls[-1] is False


def test_blinking_advances_time():
    _, set_led = _led()
    style = BlinkStyle(set_led)
    style.state = STATE_BLINKING
    style.target_blinks = 4

    # 4 blinks * 2 phases * 0.3s = 2.4s
    style.update(100.0, False)
    style.update(102.4, False)

    assert style.state == STATE_WAIT_FOR_INPUT
    assert style.last_interaction_time == 102.4
