"""Autotune harness mechanics (#19) — aggregation + ranking, DSP mocked out.

The real finder/grader are exercised elsewhere (test_loops/test_grade) and end-to-end
on stems; here we lock the harness logic deterministically and fast.

    cd tools/foundry && python3 -m pytest tests/test_autotune.py -q
"""
import sys
import types
from pathlib import Path

import numpy as np

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from engine import autotune as AT  # noqa: E402


def _cand(s, e, score):
    return types.SimpleNamespace(start_s=s, end_s=e, score=score)


def _patch(monkeypatch, n_cands=2, grade=0.7):
    monkeypatch.setattr(AT.L, "analyze_stem",
                        lambda y, sr, *, bars, top_n, grid, weights=None:
                        [_cand(i * 1.0, i * 1.0 + 1.0, 0.5) for i in range(n_cands)])
    monkeypatch.setattr(AT.G, "grade_array",
                        lambda sl, sr, **k: types.SimpleNamespace(grade=grade))


def _stems(n=2, secs=4):
    y = np.zeros(44100 * secs, dtype="float32")
    return [(y, 44100, None) for _ in range(n)]


def test_evaluate_aggregates(monkeypatch):
    _patch(monkeypatch, n_cands=2, grade=0.7)
    m = AT.evaluate(_stems(2), {"struct": 0.4})
    assert m["n_candidates"] == 4          # 2 stems × 2 candidates
    assert m["n_graded"] == 4
    assert m["mean_grade"] == 0.7
    assert 0.0 <= m["objective"] <= 0.7    # objective = grade × coverage factor


def test_evaluate_empty_is_zero(monkeypatch):
    _patch(monkeypatch, n_cands=0)
    m = AT.evaluate(_stems(2), {})
    assert m["n_candidates"] == 0 and m["objective"] == 0.0


def test_search_includes_baseline_and_is_ranked(monkeypatch):
    _patch(monkeypatch, n_cands=3, grade=0.6)
    res = AT.search(_stems(2), method="random", n=5, seed=0)
    assert len(res) == 6                              # baseline + 5
    assert any(r.get("is_baseline") for r in res)
    objs = [r["objective"] for r in res]
    assert objs == sorted(objs, reverse=True)         # best first
    assert all("settings" in r and "mean_grade" in r for r in res)


def test_weights_threaded_through(monkeypatch):
    # the weights subset must reach analyze_stem (else tuning is a no-op)
    seen = {}
    monkeypatch.setattr(AT.L, "analyze_stem",
                        lambda y, sr, *, bars, top_n, grid, weights=None:
                        (seen.update(weights or {}, bars=bars, top_n=top_n) or []))
    monkeypatch.setattr(AT.G, "grade_array", lambda sl, sr, **k: types.SimpleNamespace(grade=0.0))
    AT.evaluate(_stems(1), {"struct": 0.6, "seam": 0.25, "bars": (4, 8), "top_n": 6})
    assert seen["struct"] == 0.6 and seen["seam"] == 0.25
    assert seen["bars"] == (4, 8) and seen["top_n"] == 6
