"""Unit tests for vision primitives (synthetic BGR masks, fast, deterministic)."""
from __future__ import annotations

import cv2
import numpy as np

from atm.vision import find_top_dots


BG_RGB = (18, 18, 18)  # background in RGB


def _make_frame(h: int = 30, w: int = 100) -> np.ndarray:
    """Blank BGR frame filled with BG_RGB."""
    bgr_bg = (BG_RGB[2], BG_RGB[1], BG_RGB[0])
    frame = np.zeros((h, w, 3), dtype=np.uint8)
    frame[:, :] = bgr_bg
    return frame


def _paint_dot(frame: np.ndarray, cx: int, cy: int, radius: int = 5,
               bgr: tuple[int, int, int] = (0, 255, 0)) -> None:
    # radius ≥ 5 keeps blob above min_cluster_px after 2× erosion by 3x3 kernel.
    cv2.circle(frame, (cx, cy), radius, bgr, -1)


def test_find_top_dots_happy_three_blobs_sorted_desc():
    frame = _make_frame()
    _paint_dot(frame, 10, 15)
    _paint_dot(frame, 30, 15)
    _paint_dot(frame, 50, 15)
    result = find_top_dots(frame, BG_RGB, n=3)
    assert len(result) == 3
    # Sorted by right edge descending → x=50 first, then 30, then 10.
    xs = [pt[0] for pt in result]
    assert xs[0] > xs[1] > xs[2]
    assert xs[0] >= 48 and xs[2] <= 12  # allow ±2px wobble from centroid


def test_find_top_dots_zero_blobs_returns_empty():
    frame = _make_frame()
    assert find_top_dots(frame, BG_RGB, n=3) == []


def test_find_top_dots_one_blob_n3_returns_one():
    frame = _make_frame()
    _paint_dot(frame, 25, 15)
    result = find_top_dots(frame, BG_RGB, n=3)
    assert len(result) == 1
    cx, _cy = result[0]
    assert 23 <= cx <= 27


def test_find_top_dots_fused_wide_blob_anchors_to_right_edge():
    frame = _make_frame()
    # Paint a wide stripe (width > 12) — simulates fused anti-aliased dots.
    cv2.rectangle(frame, (20, 13), (60, 17), (0, 255, 0), -1)
    result = find_top_dots(frame, BG_RGB, n=1)
    assert len(result) == 1
    cx, _cy = result[0]
    # Anchor should be near right edge (~58 = 60-2), not centroid (~40).
    assert cx >= 55


def test_find_top_dots_tie_break_by_y_ascending():
    frame = _make_frame(h=40)
    # Two dots at same right-edge x=50, different y.
    _paint_dot(frame, 50, 10)  # upper — should come first
    _paint_dot(frame, 50, 30)  # lower
    result = find_top_dots(frame, BG_RGB, n=2)
    assert len(result) == 2
    # Tie-break: smaller y first.
    assert result[0][1] < result[1][1]
