"""Pure-logic tests for fit_marks: rounding clusters, figure-8 assignment, >=2 m clearance."""
import math
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from fit_marks import (  # noqa: E402
    assign_figure8,
    build_course_block,
    clear_marks,
    cluster,
    min_clearance_m,
    rounding_clusters,
    turn_apexes,
)
from segment_gsys import to_xy  # noqa: E402

# 4 marks in local XY: west gate (windward) at x=-100, east gate (leeward) at x=+100
WEST_P, WEST_S = (-100.0, 20.0), (-100.0, -20.0)
EAST_P, EAST_S = (100.0, 20.0), (100.0, -20.0)
MARKS = [WEST_P, EAST_P, EAST_S, WEST_S]
REF = (59.32, 18.107)


def _leg(a, b, step=4.0):
    n = max(1, int(math.dist(a, b) / step))
    return [(a[0] + (b[0] - a[0]) * i / n, a[1] + (b[1] - a[1]) * i / n) for i in range(n)]


def _rect_track(offset=0.0, laps=2):
    """A boat sailing `laps` loops of the 4 marks (a rectangle) — a hard turn at each mark every lap,
    so even the start/finish corner is an interior vertex with a detectable rounding."""
    o = (offset, offset)
    loop = [WEST_P, EAST_P, EAST_S, WEST_S]
    seq = loop * laps + [WEST_P]
    corners = [(c[0] + o[0], c[1] + o[1]) for c in seq]
    pts = []
    for a, b in zip(corners, corners[1:], strict=False):
        pts += _leg(a, b)
    return pts


def test_turn_apexes_finds_the_corner():
    track = _leg((-50, 0), (0, 0)) + _leg((0, 0), (0, 50))    # straight, then a 90 deg left turn
    apexes = turn_apexes(track)
    assert apexes
    assert min(math.dist(a, (0, 0)) for a in apexes) < 6.0


def test_turn_apexes_drops_tacks_when_wind_known():
    # A boat beating to windward (wind from the north, 0°) zig-zags NE↔NW, a ~90° turn at every tack.
    # Without the wind those tacks look like roundings; WITH wind_from they're dropped (both legs are
    # close-hauled toward the wind), so the windward leg stops swamping the real-mark clusters.
    zig = []
    x, y = 0.0, 0.0
    for leg in range(8):
        x += 30 if leg % 2 == 0 else -30      # alternate NE / NW, always gaining north
        y += 30
        zig.append((x, y))
    dense = [zig[0]]
    for a, b in zip(zig, zig[1:], strict=False):           # ~5 m spacing so the per-vertex angle is real
        for t in range(1, 7):
            dense.append((a[0] + (b[0] - a[0]) * t / 6, a[1] + (b[1] - a[1]) * t / 6))
    raw = turn_apexes(dense)
    filtered = turn_apexes(dense, wind_from=0.0)
    assert len(raw) >= 4                                    # the tacks register as hard turns...
    assert len(filtered) < len(raw)                        # ...and the wind-aware pass drops them
    assert len(filtered) <= 1


def test_cluster_groups_and_ranks():
    pts = [(0, 0), (1, 0), (0, 1)] + [(100, 100), (101, 100)]
    cs = cluster(pts, radius_m=10.0)
    assert len(cs) == 2
    assert cs[0][1] == 3 and cs[1][1] == 2                    # biggest group first


def test_rounding_clusters_recovers_four_marks():
    tracks = [_rect_track(o) for o in (0.0, 3.0, 6.0)]
    cents = rounding_clusters(tracks, top_n=4, radius_m=15.0)
    assert len(cents) == 4
    for m in MARKS:                                          # each true mark has a centroid near it
        assert min(math.dist(c, m) for c in cents) < 12.0


def test_assign_figure8_splits_west_east_and_orders_port():
    a = assign_figure8(MARKS)
    assert a["marks_xy"][0][0] < 0 and a["marks_xy"][2][0] > 0   # wp0 west, wp2 east
    assert a["marks_xy"][0][1] > a["marks_xy"][1][1]            # within west pair, port (north) first
    assert a["marks_xy"][:2] == a["marks_xy"][4:]              # figure-8 reuses the west gate (wp4/5)
    assert a["start_xy"][0][0] > 0                             # start line on the leeward (east) pair


def test_clear_marks_pushes_off_the_track():
    track = _leg((-10, 0), (10, 0))                           # passes straight through the origin
    (m,) = clear_marks([(0.0, 0.0)], [track], min_m=2.0)
    assert min(math.dist(m, p) for p in track) >= 1.9


def test_build_course_block_is_valid_and_clears_2m():
    tracks_xy = [_rect_track(o) for o in (0.0, 3.0, 6.0)]
    # back to lat/lon for the public API
    def to_ll(x, y):
        return (REF[0] + y / 111_320.0, REF[1] + x / (111_320.0 * math.cos(math.radians(REF[0]))))
    tracks_ll = [[to_ll(*p) for p in t] for t in tracks_xy]
    course = build_course_block(tracks_ll, REF, min_clear_m=2.0)
    assert len(course["waypoints"]) == 6
    assert course["startLine"]["port"] and course["finishLine"]["starboard"]
    assert min_clearance_m(course, tracks_ll, REF) >= 1.8     # marks pushed off every boat's track
    # waypoints round-trip through XY near the (cleared) marks
    wxy = [to_xy(w["lat"], w["lon"], REF) for w in course["waypoints"]]
    assert wxy[0][0] < 0 and wxy[2][0] > 0
