"""Tests on the voice-budget recording: 20 live calls to Lab 08's three-model voice agent (4 questions x 5
runs, one laptop, one day), timed with the lab's own functions, and the lab's endpointer on made-up speech
flags. Run: pytest test_voice_practice.py"""
from voice_practice import end_of_turn, hedged, summary, turn_gap, words_turn

# Per call: speech-to-text, the language model's first sentence, the streamed voice's first sound
# (None = stalled, no sound in 30 s), and first sound with the backup voice (asked after 1 s).
CALLS = [
    (2.583, 1.0121, 0.5782, 2.1384), (0.4534, 0.6623, 0.5408, 3.2082), (0.8154, 0.7295, None, 1.8136),
    (0.4386, 1.2241, 0.4231, 0.9475), (1.9823, 0.8594, 0.9877, 3.0612), (0.7775, 0.6255, None, 3.4382),
    (0.9198, 0.8222, None, 0.5479), (0.527, 1.885, None, 2.4244), (0.741, 0.6158, None, 2.2879),
    (1.9709, 1.155, 0.6507, 2.5677), (0.5067, 1.0034, 0.6542, 1.9714), (0.6948, 0.8371, 0.8147, 0.5325),
    (2.073, 1.0644, 0.7236, 2.7619), (0.6256, 0.9036, None, 1.88), (0.54, 0.861, 0.5536, 2.192),
    (0.564, 0.9336, None, 2.4775), (0.5133, 0.953, 0.4606, 2.7767), (0.4334, 0.6913, None, 3.0145),
    (0.654, 0.5524, 0.3938, 2.3275), (0.4316, 0.6362, 0.4649, 0.4047),
]


def test_turn_gap_adds_up_the_walked_call():
    stt, first, stream, backup = CALLS[2]  # "What time do you open on Saturday?", run 3
    assert round(turn_gap(stt, first, backup), 2) == 3.86
    assert turn_gap(stt, first, stream) is None  # its streamed voice, without a backup, stalled
    assert round(turn_gap(0.2, 0.3, 0.1, wait=0.4), 2) == 1.0


def test_summary_streamed_without_backup():
    s = summary([turn_gap(c[0], c[1], c[2]) for c in CALLS])
    assert s["stalled"] == 8 and s["under"] == 0
    assert s["slowest"] == float("inf")
    assert round(s["median"], 2) == 4.35  # 8 dead lines pull the median up: the 12 that played had a median of 2.6 s


def test_summary_with_backup_voice():
    s = summary([turn_gap(c[0], c[1], c[3]) for c in CALLS])
    assert s["stalled"] == 0 and s["under"] == 0
    assert round(s["median"], 1) == 4.3 and round(s["slowest"], 1) == 6.4
    assert summary([0.9, 1.0, 1.1])["under"] == 2


def test_hedged_request():
    assert hedged(0.57, 1.45) == (0.57, "primary")  # the first voice was quick: no backup needed
    assert hedged(None, 0.81) == (1.81, "backup")  # stalled: the backup sounds 0.81 s after it was asked at 1 s
    assert hedged(2.5, 0.81) == (1.81, "backup")
    assert hedged(1.2, 0.81) == (1.2, "primary")  # asked at 1 s, but the first voice still won
    assert hedged(None, None) == (None, None)


# Made-up speech flags in 20 ms frames: 1 s of speech, a 0.6 s pause, 1 s of speech, then 2 s of silence.
FLAGS = [True] * 50 + [False] * 30 + [True] * 50 + [False] * 100
SECOND, LAST = 80, 129


def test_end_of_turn():
    assert end_of_turn(FLAGS, 500) == 74  # fires inside the pause: the caller gets cut off
    assert end_of_turn(FLAGS, 800) == 169  # waits out the pause; 800 ms after the last word
    assert end_of_turn([False] * 10 + [True] * 5 + [False] * 30, 200) == 24
    assert end_of_turn([False] * 50, 200) is None  # silence alone isn't a turn


def test_words_turn():
    # "My order number is" ... "4817." judged not finished at the pause, finished at the end
    assert words_turn(FLAGS, SECOND, LAST, mid_done=False, end_done=True) == 500
    # "Yes." ... "And can you also send me the receipt?": finished at the pause, so the short wait cuts in
    assert words_turn(FLAGS, SECOND, LAST, mid_done=True, end_done=True) is None
    # judged unfinished both times: never cut, but waits the long 1.2 s
    assert words_turn(FLAGS, SECOND, LAST, mid_done=False, end_done=False) == 1200
    # a short wait can fire at a pause inside part two (a comma)
    comma = [True] * 50 + [False] * 30 + [True] * 20 + [False] * 20 + [True] * 20 + [False] * 100
    assert words_turn(comma, 80, 139, mid_done=False, end_done=True, short=300) is None
