"""Tests on Lab 12's scripts 01 and 02, as recorded for the video: live gpt-5.4-nano calls from a laptop
over home internet, three runs on 2 Nov 2026, seconds rounded to 0.01. Run: pytest test_latency_practice.py"""
import pytest

from latency_practice import cut_off, percentile, slow_end, slower_than_average, total_time

# 60 identical requests, 5 at a time, in each of three runs back to back.
RUN1 = [0.85, 1.16, 1.23, 0.91, 1.59, 0.84, 1.09, 0.89, 1.1, 0.79, 0.8, 1.05, 0.96, 0.85, 0.77, 1.39,
    1.29, 1.09, 1.81, 0.91, 0.77, 0.87, 0.72, 0.83, 0.79, 0.88, 0.94, 0.88, 0.85, 0.85, 0.92,
    0.96, 0.8, 0.94, 1.2, 1.7, 1.29, 0.98, 3.04, 0.87, 0.74, 1.6, 0.87, 0.78, 0.83, 0.88, 0.81,
    1.87, 0.87, 1.25, 1.12, 0.83, 0.79, 0.87, 0.91, 1.16, 0.88, 0.79, 2.2, 0.99]
RUN2 = [0.78, 0.93, 1.04, 1.05, 1.21, 0.89, 0.94, 0.93, 0.81, 1.06, 0.8, 1.18, 0.75, 0.82, 0.6, 0.77,
    0.75, 0.86, 0.94, 1.02, 0.8, 0.71, 0.73, 1.08, 0.78, 0.94, 0.73, 0.74, 0.99, 1.08, 0.81,
    1.07, 1.26, 1.14, 0.76, 0.91, 1.01, 0.95, 0.75, 0.79, 1.0, 1.12, 0.77, 0.96, 0.92, 0.76,
    1.04, 0.8, 1.19, 0.73, 0.74, 0.99, 0.92, 0.9, 0.85, 0.77, 1.23, 0.79, 0.82, 0.99]
RUN3 = [0.79, 0.89, 0.93, 0.83, 0.82, 0.82, 0.91, 0.76, 0.73, 0.89, 0.7, 1.21, 1.04, 0.8, 0.83, 0.7,
    0.89, 1.05, 0.66, 0.82, 0.9, 0.82, 1.03, 0.88, 1.04, 0.89, 0.81, 1.14, 1.11, 0.82, 1.1, 0.78,
    0.94, 1.04, 0.91, 0.73, 0.96, 0.75, 0.9, 0.84, 0.87, 0.78, 0.73, 0.85, 0.93, 0.91, 0.96,
    1.31, 0.99, 0.76, 0.9, 0.74, 0.86, 0.84, 0.84, 0.76, 1.08, 0.79, 0.74, 1.26]

# A counting puzzle (answer 165), 60 answers per thinking setting.
OFF = [0.46, 0.61, 0.9, 0.71, 0.55, 0.62, 1.12, 1.1, 0.54, 0.58, 0.74, 1.09, 0.7, 0.67, 0.63, 1.02,
    0.84, 0.68, 0.58, 0.72, 0.57, 0.51, 0.65, 0.52, 0.6, 0.7, 2.06, 1.06, 0.75, 1.12, 0.59, 0.85,
    0.55, 0.49, 0.57, 0.54, 0.48, 0.48, 0.63, 0.75, 0.74, 0.66, 0.72, 0.58, 0.61, 0.65, 0.55,
    0.51, 0.56, 0.54, 0.56, 0.53, 0.69, 0.52, 0.54, 0.45, 0.64, 0.84, 0.74, 0.62]
LOW = [1.86, 1.77, 1.26, 0.94, 2.21, 1.43, 1.52, 1.91, 1.57, 1.61, 1.63, 1.47, 5.05, 1.86, 1.38,
    1.89, 1.42, 1.12, 1.54, 1.9, 2.45, 1.33, 1.38, 1.26, 1.45, 1.22, 2.65, 2.59, 1.44, 2.18,
    1.26, 1.21, 2.06, 1.11, 1.48, 3.0, 1.72, 2.47, 1.15, 1.77, 1.19, 1.25, 1.57, 2.64, 1.26,
    1.16, 1.45, 1.2, 2.76, 1.84, 1.74, 1.45, 1.29, 1.71, 1.29, 1.36, 1.25, 1.5, 1.42, 1.23]
MEDIUM = [1.63, 1.48, 1.86, 1.85, 1.66, 1.33, 1.5, 2.4, 1.66, 1.08, 1.6, 2.51, 1.33, 1.65, 3.31, 1.71,
    2.65, 3.8, 1.45, 1.39, 2.54, 1.53, 1.27, 1.56, 1.42, 1.64, 1.71, 1.27, 1.49, 1.52, 1.45, 1.4,
    1.85, 1.32, 1.51, 2.23, 2.16, 2.15, 2.48, 1.31, 1.36, 1.86, 1.41, 2.69, 2.11, 1.71, 1.61,
    1.14, 1.82, 1.45, 1.61, 1.56, 1.81, 1.46, 2.0, 1.52, 1.47, 1.72, 1.22, 1.45]


def test_percentiles_of_run_1():
    assert percentile(RUN1, 50) == 0.89
    assert percentile(RUN1, 95) == 1.81
    # With 60 answers, p99 is just the second slowest.
    assert percentile(RUN1, 99) == 2.2 == sorted(RUN1)[-2]


def test_percentile_edges():
    assert percentile([3.0, 1.0, 2.0], 50) == 2.0
    assert percentile([5.0], 95) == 5.0
    assert percentile([1.0, 2.0], 0) == 1.0  # rank is at least 1


def test_the_average_misleads_almost_a_third():
    assert round(sum(RUN1) / len(RUN1), 2) == 1.06
    assert slower_than_average(RUN1) == 19


def test_the_middle_held_but_the_slow_end_moved():
    assert [percentile(r, 50) for r in (RUN1, RUN2, RUN3)] == [0.89, 0.9, 0.86]
    assert [percentile(r, 95) for r in (RUN1, RUN2, RUN3)] == [1.81, 1.19, 1.14]


def test_a_timeout_from_one_run_cuts_off_another():
    assert cut_off(RUN2, max(RUN2)) == 0
    assert cut_off(RUN1, max(RUN2)) == 10  # run 2's slowest, 1.26 s
    assert cut_off(RUN1, max(RUN3)) == 8  # run 3's slowest, 1.31 s
    assert cut_off(RUN1, 3.5) == 0


def test_pooled_over_all_three_runs():
    pooled = RUN1 + RUN2 + RUN3
    assert percentile(pooled, 95) == 1.29
    assert cut_off(pooled, 1.3) == 9
    assert cut_off(pooled, 2.5) == 1


def test_total_time_is_first_token_plus_writing():
    # Five paragraphs, run 1: 486 tokens, first token 0.87 s, 169 tokens a second; measured 3.74 s.
    assert total_time(0.87, 486, 169) == pytest.approx(3.74, abs=0.02)
    # One sentence: 44 tokens, 0.50 s, 164 a second; measured 0.77 s.
    assert total_time(0.50, 44, 164) == pytest.approx(0.77, abs=0.02)
    # Halving the answer saves real time; the first token doesn't move.
    assert total_time(0.87, 243, 169) < total_time(0.87, 486, 169) - 1.4


def test_thinking_pushes_the_slow_end_out():
    assert [percentile(t, 50) for t in (OFF, LOW, MEDIUM)] == [0.62, 1.47, 1.6]
    assert [slow_end(t) for t in (OFF, LOW, MEDIUM)] == [0.48, 1.18, 1.05]
