"""Casos de la revisión de Codex del 30-sep, traídos aquí para que no vuelvan."""
from pathlib import Path
import copy
import json
import sys
import threading

import pytest

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

MAZDA = "ks_mazda_787b"
PORSCHE = "ks_porsche_962c_shorttail"
LONGTAIL = "ks_porsche_962c_longtail"
TRACK = "doningtonpark2018-gp"
FECHA = "2026-09-30T10:00:00+00:00"


def marca(n, ms=90000, tyre="M", ballast=20):
    return {"driver": "Piloto Uno", "car": MAZDA, "num_laps": n, "last_ms": ms, "tyre": tyre, "ballast": ballast}


def directo(n=1, date=FECHA, track=TRACK, **kw):
    return {"track": track, "connected_drivers": 1, "drivers_now": ["Piloto Uno"], "session": {"date": date, "track": track, "type": "live", "laps": [], "ballast": {}}, "marks": [marca(n, **kw)]}


def resultado(laps, date=FECHA):
    return {"date": date, "track": TRACK, "ballast": {}, "laps": [{"driver": "Piloto Uno", "car": car, "ms": ms, "tyre": tyre, "ballast": kg, "cuts": 0, "ts": i} for i, (car, ms, tyre, kg) in enumerate(laps)]}


def stracker(path, car, ms, valid, date="2026-09-30 10:00", name="Piloto Uno"):
    path.write_text(json.dumps({"track": TRACK, "date_text": date, "session_type": "Practice", "drivers": [{"id": "1", "name": name, "car": car, "laps": [{"car": car, "time_ms": ms, "valid": valid}]}]}), encoding="utf-8")


@pytest.mark.parametrize("cambio", [{"tyre": "S"}, {"ballast": 0}])
def test_no_atribuye_datos_actuales_a_vuelta_antes_de_boxes(cambio):
    store = {}
    server.capture_live_laps(store, directo(3))
    server.capture_live_laps(store, directo(4, ms=91000, **cambio))
    assert store["laps"] == [], "Sin evento de paso por meta no se puede atribuir la rueda/lastre nuevos a la vuelta anterior"


def test_salto_de_contador_no_inventa_las_vueltas_intermedias():
    store = {}
    server.capture_live_laps(store, directo(3))
    server.capture_live_laps(store, directo(6, ms=93000))
    assert [(l["ms"], l["ts"]) for l in store["laps"]] == [(93000, 1)]
    assert store["seen"]["Piloto Uno|" + MAZDA] == 6


@pytest.mark.parametrize("nuevo", [{"date": "2026-09-30T11:00:00+00:00"}, {"track": "otro"}, {"date": "2026-09-30T11:00:00+00:00", "track": "otro"}])
def test_cambio_sesion_o_circuito_borra_capturas(nuevo):
    store = {}
    server.capture_live_laps(store, directo(1))
    server.capture_live_laps(store, directo(2))
    assert len(store["laps"]) == 1
    server.capture_live_laps(store, directo(1, **nuevo))
    assert store["laps"] == []
    assert store["key"] == [nuevo.get("track", TRACK), nuevo.get("date", FECHA)]


@pytest.mark.parametrize("origen", ["otro_coche", "otra_sesion"])
def test_no_valida_directo_con_vuelta_de_otro_coche_o_sesion(tmp_path, origen):
    ac = server.AcScanner()
    ac.cache_dir = tmp_path / "ac"
    ac.results_dir = tmp_path / "results"
    ac.cache_dir.mkdir()
    ac.results_dir.mkdir()
    car_actual = PORSCHE if origen == "otro_coche" else MAZDA
    stracker(ac.cache_dir / "8772_1.json", MAZDA, 90000, True, date="2026-09-30 10:00" if origen == "otro_coche" else "2026-09-29 10:00")
    stracker(ac.cache_dir / "8772_2.json", car_actual, 90000, False)
    live = directo()
    live["session"] = resultado([])
    ac.live_by_port["8772"] = live
    ac.captured_by_port["8772"] = {"key": [TRACK, FECHA], "laps": resultado([(car_actual, 90000, "M", 0)])["laps"]}
    block = ac.build_boards([{"id": "8772:" + TRACK, "track": TRACK, "port": "8772"}])["8772:" + TRACK]
    row = next(r for r in block["ranking"] if r["car"] == car_actual)
    assert not any(v["ms"] is not None for v in row["by_tyre"].values()), "Una cortada actual no puede convertirse en mejor limpia por coincidir en tiempo"


def test_dos_vueltas_iguales_con_compuestos_distintos_son_ambiguas():
    log = [{"date_iso": "2026-09-30 10:00", "laps": [{"ms": 90000, "valid": True}, {"ms": 90000, "valid": True}]}]
    known = [{"ms": 90000, "tyre": t, "ballast": 0, "date": FECHA} for t in ("S", "M")]
    a = server.tag_lap_log(log, known)
    b = server.tag_lap_log(log, list(reversed(known)))
    assert a == b, "Invertir la enumeracion de las fuentes no debe cambiar el compuesto atribuido"
    assert [l["tyre"] for l in a[0]["laps"]] == [None, None]


def test_no_pega_resultado_de_otro_dia_por_igualdad_de_tiempo():
    log = [{"date_iso": "2026-09-30 10:00", "laps": [{"ms": 90000, "valid": True}]}]
    known = [{"ms": 90000, "tyre": "S", "ballast": 40, "date": "2026-08-01T10:00:00Z"}]
    out = server.tag_lap_log(log, known)
    assert out[0]["laps"][0]["tyre"] is None


def test_fechas_con_distinto_huso_representan_mismo_instante():
    assert server.minute_of("2026-09-30T12:00:00+02:00") == server.minute_of("2026-09-30T10:00:00Z")


def test_nombres_completos_distintos_no_se_fusionan(tmp_path):
    uno, dos = tmp_path / "8772_1.json", tmp_path / "8772_2.json"
    stracker(uno, MAZDA, 90000, True, name="Jose Luis Nunez")
    stracker(dos, MAZDA, 91000, True, name="Jose Manuel Nunez")
    rows = server.build_track_rankings([uno, dos])[TRACK]["ranking"]
    assert len(rows) == 2


def test_iniciales_ambiguas_no_eligen_primer_piloto():
    known = [("ac:jose luis nunez", "Jose Luis Nunez"), ("ac:juan luis nunez", "Juan Luis Nunez")]
    assert server.resolve_pilot_id("JL Nunez", known) == "ac:jl nunez"
    assert server.resolve_pilot_id("JL Nunez", known[::-1]) == "ac:jl nunez"


def test_lastre_ajeno_no_se_presenta_como_lastre_del_coche():
    block = {"ranking": [{"name": "Piloto Uno", "car": MAZDA}]}
    server.apply_tyre_stats(block, server.tyre_stats([resultado([(PORSCHE, 90000, "S", 45)])]))
    assert block["ranking"][0]["ballast_kg"] is None


def test_modelos_porsche_distintos_no_comparten_neumatico_ni_lastre():
    block = {"ranking": [{"name": "Piloto Uno", "car": PORSCHE}, {"name": "Piloto Uno", "car": LONGTAIL}]}
    server.apply_tyre_stats(block, server.tyre_stats([resultado([(PORSCHE, 90000, "S", 20), (LONGTAIL, 89000, "M", 0)])]))
    corto, largo = block["ranking"]
    assert {v["tyre"] for v in corto["by_tyre"].values()} == {"S"}
    assert {v["tyre"] for v in largo["by_tyre"].values()} == {"M"}
    assert corto["ballast_kg"] == 20 and largo["ballast_kg"] == 0


def test_mejor_y_capturada_no_usan_dos_veces_una_misma_vuelta(tmp_path):
    ac = server.AcScanner()
    ac.cache_dir = tmp_path / "ac"
    ac.results_dir = tmp_path / "results"
    ac.cache_dir.mkdir()
    ac.results_dir.mkdir()
    stracker(ac.cache_dir / "8772_1.json", MAZDA, 90000, True)
    data = json.loads((ac.cache_dir / "8772_1.json").read_text())
    data["drivers"][0]["laps"] *= 2
    (ac.cache_dir / "8772_1.json").write_text(json.dumps(data), encoding="utf-8")
    live = directo()
    live["session"] = resultado([(MAZDA, 90000, "M", 20)])
    ac.live_by_port["8772"] = live
    ac.captured_by_port["8772"] = {"key": [TRACK, FECHA], "laps": copy.deepcopy(live["session"]["laps"])}
    row = ac.build_boards([{"id": "8772:" + TRACK, "port": "8772", "track": TRACK}])["8772:" + TRACK]["ranking"][0]
    assert sum(l["tyre"] is not None for l in row["lap_log"][0]["laps"]) == 1


def test_dos_hilos_no_rebobinan_sesion_ni_borran_vuelta(monkeypatch):
    ac = server.AcScanner()
    ac.live_by_port = {}
    ac.captured_by_port = {}
    viejo_esperando = threading.Event()
    liberar_viejo = threading.Event()
    errores = []
    base = server.STRACKER_BASES[1]

    def fetch(_):
        if threading.current_thread().name == "lectura_vieja":
            viejo_esperando.set()
            assert liberar_viejo.wait(3)
            return directo(8, date="2026-09-30T09:00:00+00:00")
        return directo(1)

    monkeypatch.setattr(ac, "fetch_live", fetch)

    def atrasado():
        try:
            ac.refresh_live([base])
        except Exception as exc:
            errores.append(exc)

    worker = threading.Thread(target=atrasado, name="lectura_vieja")
    worker.start()
    try:
        assert viejo_esperando.wait(2)
        ac.refresh_live([base])
        server.capture_live_laps(ac.captured_by_port["8772"], directo(2))
    finally:
        liberar_viejo.set()
        worker.join(4)
    assert not worker.is_alive() and not errores
    assert ac.live_by_port["8772"]["session"]["date"] == FECHA
    assert ac.captured_by_port["8772"]["key"] == [TRACK, FECHA]
    assert len(ac.captured_by_port["8772"]["laps"]) == 1
