r"""Race-course review: for every race draw a top-down view of the fitted course (the 4 figure-8 buoys,
the start/finish line) with each boat's track trimmed to [start-10 s, finish+10 s], and detect the START
as the earliest <=60 s window in which ALL boats cross the start line onto the course.

ALL races share ONE projection origin (`detect_gsys_races.CENTER`) and the SAME plot axes, so a buoy that
stays put across races appears at the same (x, y) on every sheet. Writes one PNG per race, a `courses.csv`
of every buoy's local coordinates + lat/lon + start time, and a printed summary.

    ../../../.venv-demo/Scripts/python.exe export/course_review.py
"""
from __future__ import annotations

import collections
import csv
import json
import sys
from datetime import UTC, datetime
from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt  # noqa: E402
from matplotlib.ticker import MultipleLocator  # noqa: E402

_HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(_HERE))
sys.path.insert(0, str(_HERE.parents[2] / "scoring"))
from detect_gsys_races import CENTER  # noqa: E402
from fit_marks import to_latlon  # noqa: E402
from fit_template import detect_start, global_fit, race_window  # noqa: E402
from segment_gsys import to_xy  # noqa: E402

DATA = _HERE.parent / "data" / "races" / "manifest.json"
OUT = _HERE.parent / "data" / "reviews" / "courses"
BOAT_COLOR = {"2": "#c0392b", "4": "#e67e22", "5": "#2471a3", "6": "#5dade2"}
# physical buoy -> (waypoint index it fits, the mark numbers it serves) for the figure-8 [C,D,A,B,C,D]
BUOYS = {"C": (0, "M1·M5"), "D": (1, "M2·M6"), "A": (2, "M3"), "B": (3, "M4")}
# Martin's reviewed buoy positions (2026-07-09 review round, courses.xlsx / the shared sheet) — local
# metres from CENTER, (east, north). Applied OVER the fitted/manifest marks; lat/lon recomputed from
# these. Only the corrected buoys are listed; the rest keep the fit he approved.
MARTIN_PINS: dict[str, dict[str, tuple[float, float]]] = {
    "gsys01": {"D": (-90.0, 10.0)},
    "gsys02": {"D": (-90.0, 10.0)},
    "gsys06": {"C": (-160.0, -70.0)},
    "gsys07": {"A": (50.0, -125.0), "B": (74.0, -70.0), "D": (-145.0, -5.0)},
}


def race_data(entry: dict) -> dict:
    d = entry["data"]
    t0 = d["meta"]["trackStartUtcMs"]
    wps = d["course"]["waypoints"]
    title = d["meta"].get("title", "")
    devs = {b["deviceId"]: b["sailId"] for tm in d["teams"] for b in tm["boats"]}
    timed = collections.defaultdict(list)
    for s, fr in sorted(d["frames"].items(), key=lambda kv: int(kv[0])):
        for dev, p in fr.items():
            if dev in devs:
                timed[devs[dev]].append((int(s), to_xy(p["lat"], p["lon"], CENTER)))
    # Marks: the May-22 races carry the AUTHORITATIVE V2 course (use it); the reconstructed races are
    # GLOBAL best-fit from the tracks — the 4 candidate buoys that make the MOST boats complete a figure-8.
    manifest_xy = {k: to_xy(wps[i]["lat"], wps[i]["lon"], CENTER) for k, (i, _m) in BUOYS.items()}
    if title.startswith("2026-05-22"):
        buoys, source = manifest_xy, "V2 course"
    else:
        gb, _n, _r = global_fit(dict(timed))
        buoys, source = (gb, "global-fit") if gb else (manifest_xy, "manifest")
    pins = MARTIN_PINS.get(entry["id"]) or {}
    if pins:
        buoys = {**buoys, **pins}
        source += " + reviewed"
    buoy_ll = {k: to_latlon(xy[0], xy[1], CENTER) for k, xy in buoys.items()}
    A, B, C = buoys["A"], buoys["B"], buoys["C"]
    # START = the earliest window where ALL boats cross the line within 60 s (the mass start — a lone
    # pre-start commitment minutes earlier must not set it). Same rule the bake (refit_gsys) uses.
    gun_sec, spread, counts = detect_start(dict(timed), A, B, C)
    # ONLY boats that sail a COMPLETE race FROM that start: cross -> round all 6 marks in order -> finish.
    # Each such boat's track is trimmed to exactly [its start, its finish] (no pre-start, no sail-home).
    wp_xy = [buoys["C"], buoys["D"], buoys["A"], buoys["B"], buoys["C"], buoys["D"]]
    seg: dict[str, list] = {}
    for sail, tr in timed.items():
        trimmed = [q for q in tr if gun_sec is None or q[0] >= gun_sec - 15]
        win = race_window(trimmed, wp_xy, A, B, C)
        if win is not None:
            lo, hi = win
            seg[sail] = [p for p in tr if lo <= p[0] <= hi]
    n_complete = len(seg)
    g = gun_sec
    when = datetime.fromtimestamp((t0 + g * 1000) / 1000, tz=UTC) if g is not None else None
    start_utc = when.strftime("%H:%M:%S") if when else ""
    start_cest = (f"{when.hour + 2:02d}" + when.strftime(":%M:%S")) if when else ""
    return {"rid": entry["id"], "title": title, "buoys": buoys, "buoy_ll": buoy_ll, "source": source,
            "seg": seg, "n_complete": n_complete, "start_utc": start_utc, "start_cest": start_cest,
            "spread": spread, "counts": counts,
            "start_txt": (f"{start_utc} UTC ({start_cest} CEST)" if when else "NOT FOUND")}


def plot_race(rd: dict, xlim: tuple, ylim: tuple) -> None:
    fig, ax = plt.subplots(figsize=(9, 9))
    for sail, seg in sorted(rd["seg"].items()):
        ax.plot([p[1][0] for p in seg], [p[1][1] for p in seg], "-", lw=1.0, alpha=0.7,
                color=BOAT_COLOR.get(sail, "#888"), label=f"boat {sail}")
    A, B = rd["buoys"]["A"], rd["buoys"]["B"]
    ax.plot([A[0], B[0]], [A[1], B[1]], "k-", lw=1.5, label="start/finish line")
    for k, (x, y) in rd["buoys"].items():
        # small dot (reviewer: big dots hid the tracks rounding close to the mark)
        ax.plot(x, y, "o", ms=3.5, mfc="#f5b301", mec="k", mew=0.6, zorder=5)
        ax.annotate(f"{k}  {BUOYS[k][1]}\n({x:.0f},{y:.0f})", (x, y), textcoords="offset points",
                    xytext=(5, 5), fontsize=7, weight="bold")
    ax.set_title(f"{rd['rid']}  ({rd['title']}) — start {rd['start_txt']}\n"
                 f"{rd['n_complete']}/4 boats sailed a complete race  (marks: {rd['source']})", fontsize=11)
    ax.set_xlim(*xlim)
    ax.set_ylim(*ylim)
    ax.set_aspect("equal")
    ax.set_xlabel("east (m) — origin at 59.32120 N, 18.10721 E")
    ax.set_ylabel("north (m)")
    # 10 m grid for precise mark corrections (labels every 50 m to stay readable)
    for axis in (ax.xaxis, ax.yaxis):
        axis.set_major_locator(MultipleLocator(50))
        axis.set_minor_locator(MultipleLocator(10))
    ax.grid(which="major", alpha=0.55, lw=0.8)
    ax.grid(which="minor", alpha=0.35, lw=0.4)
    ax.legend(loc="upper right", fontsize=8)
    fig.savefig(OUT / f"{rd['rid']}.png", dpi=110, bbox_inches="tight")
    plt.close(fig)


def main() -> int:
    doc = json.loads(DATA.read_text(encoding="utf-8"))
    rds = [race_data(e) for e in doc["races"] if (e["data"].get("course") or {}).get("waypoints")]
    good = [rd for rd in rds if rd["n_complete"] == 4]      # only races where all 4 sailed a full race
    incomplete = [rd for rd in rds if rd["n_complete"] < 4]
    # ONE common axis range across the presented races (their complete tracks + buoys + a margin)
    xs = [p[1][0] for rd in good for seg in rd["seg"].values() for p in seg] + \
         [b[0] for rd in good for b in rd["buoys"].values()]
    ys = [p[1][1] for rd in good for seg in rd["seg"].values() for p in seg] + \
         [b[1] for rd in good for b in rd["buoys"].values()]
    m = 15
    xlim, ylim = (min(xs) - m, max(xs) + m), (min(ys) - m, max(ys) + m)
    OUT.mkdir(parents=True, exist_ok=True)
    # remove any stale PNG for a race that no longer qualifies, then draw the good ones
    for rd in incomplete:
        (OUT / f"{rd['rid']}.png").unlink(missing_ok=True)
    for rd in good:
        plot_race(rd, xlim, ylim)

    csv_path = OUT / "courses.csv"
    with csv_path.open("w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["race", "title", "start_CEST", "marks_source", "buoy", "marks",
                    "x_m", "y_m", "lat", "lon"])
        for rd in good:
            for k in ("A", "B", "C", "D"):
                x, y = rd["buoys"][k]
                la, lo = rd["buoy_ll"][k]
                w.writerow([rd["rid"], rd["title"], rd["start_cest"], rd["source"], k, BUOYS[k][1],
                            round(x, 1), round(y, 1), round(la, 7), round(lo, 7)])

    print("PRESENTED (all 4 boats sailed a complete race):")
    for rd in good:
        b = "  ".join(f"{k}({rd['buoys'][k][0]:.0f},{rd['buoys'][k][1]:.0f})" for k in ("A", "B", "C", "D"))
        print(f"  {rd['rid']:8} start {rd['start_cest']}   {b}")
    print("\nEXCLUDED (fewer than 4 complete races — bad fit or dropouts):")
    for rd in incomplete:
        print(f"  {rd['rid']:8} only {rd['n_complete']}/4 boats completed")
    print(f"\nPNG sheets -> {OUT}\nCSV        -> {csv_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
