"""Tests for the RunningHub ("RunningHub AI生成") visible-watermark engine.

Every tuned constant in ``runninghub_engine`` was measured on the 73-frame
vendor cohort (2026-07-22, ``scripts/vendor_cohort_harvest.py`` +
``scripts/vendor_mark_calibrate.py``); these tests pin the load-bearing ones:
the top-left corner, the gray front-end, the exact-size tight ladder, the
strict-only gate, and the mask/coverage parity regression (the partial-blob
"Runni" miss).
"""

from __future__ import annotations

import cv2
import numpy as np
import pytest

from remove_ai_watermarks import watermark_registry as registry
from remove_ai_watermarks.runninghub_engine import (
    _ALPHA_HEIGHT_FRAC,
    _ALPHA_WIDTH_FRAC,
    RunningHubEngine,
    _alpha_template,
)

_MARK_FRAC = 0.32  # measured mark width, fraction of the frame WIDTH


def _compose(w: int, h: int, mode: float = _MARK_FRAC, bg: float = 100.0):
    """Composite the RunningHub silhouette at the measured size, top-left."""
    img = np.full((h, w, 3), bg, np.float32)
    at = _alpha_template()
    gw = int(mode * w)
    gh = max(4, int(mode * (_ALPHA_HEIGHT_FRAC / _ALPHA_WIDTH_FRAC) * w))
    ax, ay = int(0.008 * w), int(0.006 * h)
    amap = np.zeros((h, w), np.float32)
    amap[ay : ay + gh, ax : ax + gw] = cv2.resize(at, (gw, gh))
    a3 = amap[:, :, None]
    wm = (a3 * 255.0 + (1 - a3) * img).clip(0, 255).astype(np.uint8)
    return wm, (ax, ay, gw, gh)


class TestLocate:
    def test_box_anchored_top_left(self):
        eng = RunningHubEngine()
        img = np.zeros((2048, 1536, 3), np.uint8)
        loc = eng.locate(img)
        assert loc.x < 40  # hugs the left edge
        assert loc.y < 40  # hugs the top edge (corner="tl")

    def test_box_scales_with_width(self):
        # scale_basis="width" (measured: mark width is 0.32 of the frame width).
        eng = RunningHubEngine()
        narrow = eng.locate(np.zeros((2048, 1024, 3), np.uint8))
        wide = eng.locate(np.zeros((2048, 2048, 3), np.uint8))
        assert wide.w == pytest.approx(narrow.w * 2, rel=0.05)


class TestConfig:
    def test_gray_frontend(self):
        # The mark is a faint mid-gray the top-hat suppresses to clean-arm levels;
        # the raw-grayscale front-end is what separates (measured 2026-07-22).
        assert RunningHubEngine().config.detect_frontend == "gray"

    def test_tight_ladder(self):
        # The NCC comb is razor-sharp in size (0.537 on-size, 0.223 at +5.6%), so
        # the nominal sits exactly on the measured 0.32 with +-5% rungs.
        assert RunningHubEngine().config.ladder == (0.95, 1.0, 1.05)
        assert RunningHubEngine().config.alpha_width_frac == pytest.approx(0.32)

    def test_strict_only_no_provenance_relaxation(self):
        assert RunningHubEngine().config.provenance_ncc_factor == 1.0

    def test_gate_above_clean_arm_max(self):
        # Clean arm scored p99 0.273 / max 0.295 on 286 hand-labelled frames.
        assert RunningHubEngine().config.detect_ncc_threshold > 0.295

    def test_registry_row(self):
        mark = registry.get_mark("runninghub")
        assert mark.location == "top-left"
        assert mark.in_auto


class TestDetectAndMask:
    def test_detects_composed_mark(self):
        eng = RunningHubEngine()
        wm, _ = _compose(1080, 1620)
        det = eng.detect(wm)
        assert det.detected, f"composed mark missed (conf={det.confidence:.3f})"

    def test_clean_frame_stays_quiet(self):
        eng = RunningHubEngine()
        img = np.full((1620, 1080, 3), 100, np.uint8)
        assert not eng.detect(img).detected

    def test_mask_covers_the_whole_mark(self):
        """Regression (2026-07-22): the binary blob under-segments the faint head
        glyphs, so a blob-bbox mask left "Runni" unremoved. The gray front-end's
        mask must come from the detector's own match box and cover the mark."""
        eng = RunningHubEngine()
        wm, (ax, ay, gw, gh) = _compose(1080, 1620)
        mask = eng.footprint_mask(wm)
        assert mask is not None
        ys, xs = np.where(mask > 0)
        assert xs.min() <= ax + int(0.05 * gw)  # covers the LEFT edge of the mark
        assert xs.max() >= ax + gw - int(0.05 * gw)
        assert ys.min() <= ay + gh // 2 <= ys.max()

    def test_no_mask_on_clean_frame(self):
        eng = RunningHubEngine()
        img = np.full((1620, 1080, 3), 100, np.uint8)
        assert eng.footprint_mask(img) is None

    def test_anchor_window_rejects_off_corner_match(self):
        """The raw-gray front-end false-fires on text-like structure ANYWHERE in
        the box during compatibility testing; the
        anchor window is what keeps it about THIS mark. A composed mark placed
        off the measured corner anchor must NOT be detected."""
        eng = RunningHubEngine()
        wm, _ = _compose(1080, 1620)
        det = eng.detect(wm)
        assert det.detected  # on-anchor control
        # the same mark shifted right/down, off the anchor window
        shifted = np.full((1620, 1080, 3), 100, np.uint8)
        region = wm[10:60, 12:360]
        shifted[100 : 100 + region.shape[0], 200 : 200 + region.shape[1]] = region
        assert not eng.detect(shifted).detected


class TestPillInteraction:
    def test_confident_runninghub_detection_suppresses_the_jimeng_pill(self):
        # A RunningHub frame names its own product; its detection must veto the
        # Jimeng pill the same way Doubao/Qwen/Kling do (``_keep_pill``).
        from remove_ai_watermarks.watermark_registry import _keep_pill

        assert not _keep_pill({"runninghub"}, provenance=frozenset({"jimeng"}), footprint_flat=1.0)
