"""tools/pipeline/track.py — the hybrid motion-track pass (content-pipeline §11.5).

Pure geometry helpers are unit-tested here; the cv2 optical-flow propagation is an
opt-in integration test that runs on a real pool clip when present."""

from pathlib import Path

import pytest

from tools.pipeline.track import (
    clamp01_box,
    denormalize_box,
    infer_window,
    loop_t,
    normalize_box,
    sample_track,
    track_to_annotation,
)


# --- pure helpers -----------------------------------------------------------


def test_clamp01_box_keeps_box_on_screen():
    assert clamp01_box((-0.2, 0.1, 0.5, 0.5)) == [0.0, 0.1, 0.5, 0.5]
    # size trimmed so the box never runs past the right/bottom edge
    assert clamp01_box((0.8, 0.8, 0.5, 0.5)) == [0.8, 0.8, pytest.approx(0.2), pytest.approx(0.2)]


def test_normalize_denormalize_round_trip():
    px = (192, 108, 384, 216)
    norm = normalize_box(px, 1920, 1080)
    assert norm == [pytest.approx(0.1), pytest.approx(0.1), pytest.approx(0.2), pytest.approx(0.2)]
    assert denormalize_box(norm, 1920, 1080) == (192, 108, 384, 216)


def test_loop_t_is_frame_over_total():
    assert loop_t(0, 100) == 0.0
    assert loop_t(50, 100) == 0.5
    assert loop_t(5, 0) == 0.0   # guard divide-by-zero


def test_infer_window_spans_tracked_frames():
    appear, disappear = infer_window([10, 11, 30, 31], 100)
    assert appear == pytest.approx(0.1)
    assert disappear == pytest.approx(0.31)
    # no frames -> full clip
    assert infer_window([], 100) == (0.0, 1.0)


def test_sample_track_downsamples_to_sparse_keyframes():
    # 21 dense frames -> at most n_keyframes, including the first and last
    per_frame = {i: [i / 100, 0.2, 0.1, 0.1] for i in range(0, 21)}
    track = sample_track(per_frame, total_frames=100, n_keyframes=5)
    assert len(track) <= 5
    ts = [k["t"] for k in track]
    assert ts == sorted(ts)                 # sorted by t
    assert ts[0] == pytest.approx(0.0)       # first tracked frame
    assert ts[-1] == pytest.approx(0.2)      # last tracked frame (frame 20 / 100)
    assert len(set(ts)) == len(ts)           # de-duplicated on t


def test_sample_track_keeps_all_when_already_sparse():
    per_frame = {0: [0.1, 0.1, 0.1, 0.1], 60: [0.5, 0.3, 0.1, 0.1]}
    track = sample_track(per_frame, total_frames=120, n_keyframes=5)
    assert [k["t"] for k in track] == [pytest.approx(0.0), pytest.approx(0.5)]


def test_sample_track_empty_is_empty():
    assert sample_track({}, total_frames=100) == []


def test_track_to_annotation_assembles_geometry_only():
    track = [{"t": 0.0, "box": [0.1, 0.2, 0.1, 0.1]}, {"t": 0.5, "box": [0.4, 0.2, 0.1, 0.1]}]
    ann = track_to_annotation("detected.jelly", track, salience=3, appear=0.0, disappear=0.55)
    assert ann["key"] == "detected.jelly"
    assert ann["salience"] == 3
    assert ann["track"] == track
    assert ann["appear"] == 0.0 and ann["disappear"] == 0.55
    # no semantics leaked in — strings/tiers are the author's, added elsewhere
    assert "strings" not in ann and "_tiers" not in ann


def test_track_to_annotation_omits_window_when_absent():
    ann = track_to_annotation("detected.x", [], salience=4)
    assert "appear" not in ann and "disappear" not in ann


# --- opt-in: classical optical-flow propagation on a real clip --------------

_CLIP = Path("simulator/sample_media/abyss_wow/base.mp4")


def _cv2_available() -> bool:
    try:
        import cv2  # noqa: F401
        return True
    except Exception:
        return False


@pytest.mark.skipif(not _CLIP.exists(),
                    reason="abyss_wow base absent (run simulator/build_pool_manifest.py)")
@pytest.mark.skipif(not _cv2_available(), reason="opencv-python not available")
def test_track_seed_produces_a_sparse_annotation_on_real_footage():
    from tools.pipeline.track import track_box, track_seed

    seed = (0.2, 0.3, 0.15, 0.18)
    per_frame, total = track_box(_CLIP, seed, seed_frame=0, max_frames=40)
    assert total > 0
    assert per_frame[0] == pytest.approx(list(seed), abs=0.02)
    assert len(per_frame) > 1                       # propagated beyond the seed frame

    ann = track_seed(_CLIP, "detected.jelly", seed, seed_frame=0, n_keyframes=5, max_frames=40)
    assert ann["key"] == "detected.jelly"
    assert 2 <= len(ann["track"]) <= 5              # sparse keyframes
    for kf in ann["track"]:
        assert 0.0 <= kf["t"] <= 1.0
        assert all(0.0 <= v <= 1.0 for v in kf["box"])
    assert 0.0 <= ann["appear"] <= ann["disappear"] <= 1.0
