from pathlib import Path
import sys

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from server import (  # noqa: E402
    LATEST_SESSION_PAGES,
    best_consecutive,
    build_track_rankings,
    cache_session_key,
    combined_ranking,
    connected_now,
    fold_name,
    names_match,
    occupancy_from_stream,
    parse_cache_stem,
    parse_driver_laps,
    parse_sessionstat_rows,
    plan_current_event,
    plan_latest_sessions,
    pretty_track,
    resolve_pilot_id,
    select_latest_cache,
    unique_session_ids,
)


def test_solo_baja_sesiones_que_faltan_de_la_tanda_reciente():
    to_fetch, latest = plan_latest_sessions(
        ["4250", "4249", "4249", "4101"],
        cached_ids={4250, 4249},
        max_new=160,
    )
    assert latest == ["4250", "4249", "4101"]
    assert to_fetch == ["4101"]


def test_no_pide_historial_aunque_el_cache_este_vacio():
    page = [str(n) for n in range(1015, 865, -1)]
    to_fetch, latest = plan_latest_sessions(page, cached_ids=set(), max_new=3)
    assert latest[0] == "1015"
    assert to_fetch == ["1015", "1014", "1013"]
    assert "866" not in to_fetch


def test_select_latest_cache_ignora_sesiones_viejas():
    folder = Path(__file__).resolve().parent / "_tmp_ac_cache"
    folder.mkdir(exist_ok=True)
    try:
        for stem in ("8771_4008", "8771_4250", "8771_4249", "8772_900"):
            (folder / f"{stem}.json").write_text('{"session_id": "x", "drivers": []}', encoding="utf-8")
        (folder / "basura.json").write_text("{}", encoding="utf-8")
        files = select_latest_cache(folder, per_port=2)
        stems = sorted(path.stem for path in files)
        assert stems == ["8771_4249", "8771_4250", "8772_900"]
        assert "8771_4008" not in stems
        only = select_latest_cache(folder, only_keys={"8771_4250", "no_existe"})
        assert [path.stem for path in only] == ["8771_4250"]
    finally:
        for path in folder.glob("*"):
            path.unlink()
        folder.rmdir()


def test_helpers_de_clave():
    assert cache_session_key("8771", "4250") == "8771_4250"
    assert parse_cache_stem("8771_4250") == ("8771", 4250)
    assert parse_cache_stem("roto") is None
    assert unique_session_ids(["2", "2", "1"]) == ["2", "1"]


def test_el_escaner_no_abre_paginas_viejas():
    assert LATEST_SESSION_PAGES == 1
    source = (ROOT / "server.py").read_text(encoding="utf-8")
    assert "while page < 80" not in source
    assert "sessionstat?page=0" in source
    assert "AC_REFRESH_SECONDS = 25" in source


def test_el_evento_actual_corta_cuando_cambia_el_circuito():
    rows = [
        {"id": "4250", "track": "d7fox_mid_ohio-no_chicane"},
        {"id": "4249", "track": "d7fox_mid_ohio-no_chicane"},
        {"id": "4244", "track": "grobnik"},
        {"id": "4243", "track": "grobnik"},
    ]
    to_fetch, live, latest = plan_current_event(rows, cached_ids={4250}, max_new=10)
    assert live == "d7fox_mid_ohio-no_chicane"
    assert latest == ["4250", "4249"]
    assert to_fetch == ["4250", "4249"]
    assert "4244" not in latest


def test_releen_la_sesion_en_curso_aunque_ya_este_en_cache():
    rows = [
        {"id": "4251", "track": "d7fox_mid_ohio-no_chicane"},
        {"id": "4250", "track": "d7fox_mid_ohio-no_chicane"},
        {"id": "4244", "track": "grobnik"},
    ]
    to_fetch, live, latest = plan_current_event(
        rows, cached_ids={4251, 4250, 4244}, max_new=10, refresh_latest=2
    )
    assert live == "d7fox_mid_ohio-no_chicane"
    assert to_fetch == ["4251", "4250"]
    assert "4244" not in to_fetch
    frozen, _, _ = plan_current_event(rows, cached_ids={4251, 4250}, max_new=10, refresh_latest=0)
    assert frozen == []


def test_parsea_filas_de_sessionstat():
    html = """
    <tr class="clickableRow" href="sessiondetails?sessionid=4250#">
      <td>d7fox_mid_ohio-no_chicane</td><td>Qualify</td><td>A</td><td></td><td></td><td>1</td>
      <td>2026-09-19 02:03</td>
    </tr>
    <tr class="clickableRow" href="sessiondetails?sessionid=4244#">
      <td>grobnik</td><td>Race</td><td>B</td><td></td><td></td><td>7</td>
      <td>2026-07-30 23:10</td>
    </tr>
    """
    rows = parse_sessionstat_rows(html)
    assert [row["id"] for row in rows] == ["4250", "4244"]
    assert rows[0]["track"] == "d7fox_mid_ohio-no_chicane"


def test_un_piloto_aparece_en_ohio_aunque_sea_mas_rapido_en_otro_sitio():
    folder = Path(__file__).resolve().parent / "_tmp_tracks"
    folder.mkdir(exist_ok=True)
    try:
        ohio = {
            "track": "d7fox_mid_ohio-no_chicane",
            "session_type": "Qualify",
            "date_text": "2026-09-19 00:20",
            "drivers": [
                {
                    "id": "1",
                    "name": "Andoni Garcia",
                    "car": "ks_mazda_787b",
                    "laps": [
                        {"time_ms": 78202, "valid": True, "splits": [0, 0, 0], "car": "ks_mazda_787b"},
                        {"time_ms": 78300, "valid": True, "splits": [0, 0, 0], "car": "ks_mazda_787b"},
                    ],
                }
            ],
        }
        grobnik = {
            "track": "grobnik",
            "session_type": "Race",
            "date_text": "2026-07-30 23:10",
            "drivers": [
                {
                    "id": "1",
                    "name": "Andoni Garcia",
                    "car": "otro",
                    "laps": [{"time_ms": 62000, "valid": True, "splits": [0, 0, 0], "car": "otro"}],
                }
            ],
        }
        (folder / "8771_1.json").write_text(__import__("json").dumps(ohio), encoding="utf-8")
        (folder / "8771_2.json").write_text(__import__("json").dumps(grobnik), encoding="utf-8")
        by_track = build_track_rankings(list(folder.glob("*.json")), {"d7fox_mid_ohio-no_chicane"})
        names = [row["name"] for row in by_track["d7fox_mid_ohio-no_chicane"]["ranking"]]
        assert names == ["Andoni Garcia"]
        assert by_track["d7fox_mid_ohio-no_chicane"]["ranking"][0]["best_ms"] == 78202
        assert "grobnik" not in by_track
    finally:
        for path in folder.glob("*"):
            path.unlink()
        folder.rmdir()


def test_clasificatoria_suma_media2_de_los_dos_circuitos():
    by_track = {
        "ohio": {
            "ranking": [
                {"id": "ac:oliver", "name": "Oliver", "avg2_ms": 77200, "avg2_display": "1:17.200"},
                {"id": "ac:andoni", "name": "Andoni", "avg2_ms": 78400, "avg2_display": "1:18.400"},
            ]
        },
        "laguna": {
            "ranking": [
                {"id": "ac:oliver", "name": "Oliver", "avg2_ms": 79800, "avg2_display": "1:19.800"},
            ]
        },
    }
    rows = combined_ranking(by_track, ["ohio", "laguna"], "avg2")
    assert [row["name"] for row in rows] == ["Oliver"]
    assert rows[0]["total_ms"] == 77200 + 79800
    assert rows[0]["gap_display"] == "Lider"


def test_nombres_con_tilde_son_el_mismo_piloto():
    assert fold_name("Jose Luis Núñez") == "jose luis nunez"
    assert fold_name("Jose Luis Nunez") == "jose luis nunez"
    assert pretty_track("d7fox_mid_ohio-no_chicane") == "Mid Ohio"
    assert pretty_track("ks_laguna_seca") == "Laguna Seca"


def test_media2_exige_dos_validas_seguidas():
    solo_una = [
        {"time_ms": 78310, "valid": False},
        {"time_ms": 77707, "valid": True},
    ]
    assert best_consecutive(solo_una, 2) is None
    separadas = [
        {"time_ms": 78310, "valid": True},
        {"time_ms": 90000, "valid": False},
        {"time_ms": 77707, "valid": True},
    ]
    assert best_consecutive(separadas, 2) is None
    seguidas = [
        {"time_ms": 78310, "valid": True},
        {"time_ms": 77707, "valid": True},
    ]
    got = best_consecutive(seguidas, 2)
    assert got is not None
    assert got["laps"] == [78310, 77707]
    assert got["avg_ms"] == (78310 + 77707) // 2


def test_media2_trunca_como_el_server_no_redondea_al_alza():
    """Suma impar → media .5 ms. Python round() sube 1 ms; el server se queda abajo."""
    laps = [
        {"time_ms": 85005, "valid": True},
        {"time_ms": 85006, "valid": True},
    ]
    got = best_consecutive(laps, 2)
    assert got is not None
    assert got["avg_ms"] == 85005
    assert got["avg_ms"] == (85005 + 85006) // 2
    assert got["avg_ms"] != int(round((85005 + 85006) / 2))


def test_salvi_y_salvador_son_el_mismo_piloto():
    assert names_match("Salvador Vargas", "Salvi Vargas")
    assert names_match("Toño Martinez", "Tono Martinez")
    assert names_match("Jose Luis Núñez", "Jose Luis Nunez")
    assert names_match("torino arenas", "Torino Arenas")
    assert not names_match("Jordi Martinez", "Jose Martinez")
    assert not names_match("Andoni Garcia", "Diego Garcia")
    assert not names_match("Alvaro fernandez", "Oliver Fernandez")
    known: list[tuple[str, str]] = []
    first = resolve_pilot_id("Salvador Vargas", known)
    known.append((first, "Salvador Vargas"))
    assert resolve_pilot_id("Salvi Vargas", known) == first


def test_clasificatoria_une_salvi_y_salvador():
    by_track = {
        "ohio": {
            "ranking": [
                {"id": "ac:salvador vargas", "name": "Salvador Vargas", "avg2_ms": 78008, "avg2_display": "1:18.008"},
            ]
        },
        "laguna": {
            "ranking": [
                {"id": "ac:salvi vargas", "name": "Salvi Vargas", "avg2_ms": 80867, "avg2_display": "1:20.867"},
            ]
        },
    }
    rows = combined_ranking(by_track, ["ohio", "laguna"], "avg2")
    assert len(rows) == 1
    assert rows[0]["name"] == "Salvador Vargas"
    assert rows[0]["total_ms"] == 78008 + 80867
    assert rows[0]["parts_display"] == ["1:18.008", "1:20.867"]


def test_assetto_corsa_1_es_el_juego_por_defecto():
    html = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
    assert 'let activeTab = "track:0";' in html
    assert "Clasificatoria" in html
    assert 'id="gameSel"' not in html
    combo = html.find('id: "combo"')
    evo = html.find('id: "evo"')
    assert combo != -1 and evo != -1
    assert combo < evo
    assert "function namesMatch" in html
    assert "<th>Vueltas</th><th>Mejor</th>" in html
    assert "servers_online || []).length" not in html
    assert "state.message || state.error" in html
    assert "state.error || state.message" not in html
    assert "function connectedNow" in html
    assert ">Road Atlanta<" not in html


def test_livemap_vacio_son_cero_pilotos_no_dos_servers():
    raw = (
        "<html><head></head><body>"
        '{&#34;class_data&#34;: &#34;\\n&#34;, &#34;session_data&#34;: &#34;d7fox_mid_ohio-no_chicaneQualify178:42\\n&#34;}'
        "</body></html>"
    )
    occ = occupancy_from_stream(raw)
    assert occ["connected_drivers"] == 0
    assert occ["drivers_now"] == []


def test_livemap_html_entities_reales_vacio():
    raw = (
        '<html><head></head><body>{&#34;alive_data&#34;: &#34;<circle></circle>\\n&#34;, '
        '&#34;class_data&#34;: &#34;\\n&#34;, &#34;state&#34;: &#34;ready&#34;, '
        '&#34;session_data&#34;: &#34;d7fox_mid_ohio-no_chicaneQualify168:32\\n&#34;}</body></html>'
    )
    occ = occupancy_from_stream(raw)
    assert occ["connected_drivers"] == 0
    assert occ["drivers_now"] == []


def test_livemap_html_entities_cuenta_piloto():
    raw = (
        '<html><head></head><body>{&#34;class_data&#34;: &#34;'
        "<tr><td>1</td><td>ks_mazda_787b</td><td>Jordi Martinez</td><td>01:19.171</td></tr>"
        '\\n&#34;, &#34;state&#34;: &#34;ready&#34;}</body></html>'
    )
    occ = occupancy_from_stream(raw)
    assert occ["connected_drivers"] == 1
    assert occ["drivers_now"] == ["Jordi Martinez"]


def test_livemap_cuenta_filas_de_pilotos():
    raw = (
        '<html><body>{"class_data": "'
        "<tr><td>1</td><td>ks_mazda_787b</td><td>Jordi Martinez</td><td>01:19.171</td></tr>"
        "<tr><td>2</td><td>ks_mazda_787b</td><td>Oliver Fernandez</td><td>01:17.201</td></tr>"
        '", "state": "ready"}</body></html>'
    )
    occ = occupancy_from_stream(raw)
    assert occ["connected_drivers"] == 2
    assert occ["drivers_now"] == ["Jordi Martinez", "Oliver Fernandez"]


def test_connected_now_es_por_circuito():
    tracks = [
        {"id": "ohio", "connected_drivers": 0},
        {"id": "laguna", "connected_drivers": 1},
    ]
    assert connected_now(tracks) == 1
    assert connected_now(tracks, "ohio") == 0
    assert connected_now(tracks, "laguna") == 1


def test_valid_es_la_columna_quinta_no_el_fuel():
    fragment = """
    <tr>
      <td>1.</td><td>01:54.287</td><td>-</td><td>-</td>
      <td>no</td><td>2</td><td>0 (0)</td><td>-</td>
    </tr>
    <tr>
      <td>2.</td><td>01:19.171</td><td>-</td><td>-</td>
      <td>yes</td><td>0</td><td>0 (0)</td><td>-</td>
    </tr>
    """
    laps = parse_driver_laps(fragment)
    assert [(lap["time_ms"], lap["valid"]) for lap in laps] == [(114287, False), (79171, True)]
