"""Unit tests for lens inference — which measurement can SEE which control.

Every positive case below is a REAL line from live/ (694 tracks, 5046 control bindings),
because a classifier tuned on invented examples classifies invented code. The cases that
matter most are the ones asserting a control is NOT measured by rms: rms is blind to a
filter and to a bitcrusher, so an rms-based "did anything change?" reports a confident NO
on an effect that is working perfectly. That single mistake would make the whole
per-track acceptance test worthless while looking green.
"""

from __future__ import annotations

import sys
from pathlib import Path

import pytest

TOOLS = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(TOOLS))

from at import lens  # noqa: E402
from pvlint.core import Track  # noqa: E402


def ctl(src: str, cc: int):
    """The Control for `cc` in a one-orbit source snippet."""
    found = {c.cc: c for c in lens.controls(Track(path="<test>", text=src))}
    return found.get(cc)


# --------------------------------------------------------- the right lens

@pytest.mark.parametrize("src,cc,lens_name", [
    # spectrum — rms CANNOT see any of these
    ('d1 $ s "bd" # crushbus 44 (range 16 1.6 "^53")', 53, "centroid"),
    ('d1 $ s "bd" # djfbus 3 (range 0.05 0.95 "^51")', 51, "centroid"),
    ('d1 $ s "bd" # lpf (range 200 8000 "^13")', 13, "centroid"),
    ('d1 $ s "bd" # squizbus 7 (range 1 4 "^30")', 30, "centroid"),
    # the sub-octave family ADDS weight underneath; brightness is the wrong question
    ('d1 $ s "bd" # octersubbus 21 (range 0 1.2 "^32")', 32, "low_ratio"),
    ('d1 $ s "bd" # octerbus 6 (range 0 1 "^31")', 31, "high_ratio"),
    # level
    ('d1 $ gMute3 $ s "bd"\n  $ midiOn "^75" (const silence)', 75, "rms"),
    ('d1 $ s "bd" # gain (range 0 1.2 "^29")', 29, "rms"),
    # density — a ply that fires changes the onset rate and almost nothing else
    ('d1 $ midiOn "^59" (ply 4) $ s "bd*4"', 59, "onsets"),
    ('d1 $ midiOn "^89" (slice 4 "0 1 2 3") $ s "break"', 89, "onsets"),
    ('d1 $ s "bd" # chop (range 1 16 "^44")', 44, "onsets"),
    # time axis — a reverb extends ACTIVITY, which an aggregate cannot show
    ('d1 $ s "bd" # roombus 9 (range 0 0.6 "^33")', 33, "duty"),
    ('d1 $ s "bd" # lratebus 43 (range 0.6 6.7 "^33")', 33, "duty"),
    ('d1 $ s "bd" # legato (range 0.2 2 "^34")', 34, "duty"),
])
def test_the_lens_matches_the_effect_kind(src, cc, lens_name):
    c = ctl(src, cc)
    assert c is not None, f"CC{cc} not detected at all"
    assert c.lens == lens_name, f"CC{cc} classified {c.kind}/{c.lens}"


@pytest.mark.parametrize("src,cc", [
    ('d1 $ s "bd" # crushbus 44 (range 16 1.6 "^53")', 53),
    ('d1 $ s "bd" # djfbus 3 (range 0.05 0.95 "^51")', 51),
    ('d1 $ s "bd" # coarse (range 1 24 "^54")', 54),
])
def test_spectral_effects_are_NEVER_measured_by_level(src, cc):
    """The single most expensive possible mistake in this module.

    A crusher or a filter rearranges the spectrum and leaves the level alone. Measure it
    with rms and the suite reports "no impact" on a working effect — which is how a
    green test suite comes to mean nothing.
    """
    assert ctl(src, cc).lens != "rms"


# ------------------------------------------------------ multi-line reality

def test_a_wrapped_expression_is_still_classified():
    """PLN wraps his lines, and a line-by-line scan left a THIRD of the corpus
    unclassified. An unclassified control is one the AT silently skips — so the
    controls most likely to be broken would be the ones excluded from the test."""
    src = ('d1 $ midiOn "^57" (\n'
           '    (# crushbus 41 (range 16 4 "^54"))\n'
           '  ) $ s "bd*4"\n')
    assert ctl(src, 57).lens == "centroid"


def test_the_keyword_NEAREST_the_cc_wins_not_the_first_in_the_table():
    """`midiOn "^59" (ply 4) # crushbus ...` is about the ply; the crush has its own CC.
    Resolving by table order rather than position handed such lines to whichever effect
    happened to sit higher in the list."""
    src = 'd1 $ midiOn "^59" (ply 4) $ s "bd" # crushbus 1 (range 16 4 "^60")'
    assert ctl(src, 59).lens == "onsets"


def test_a_gated_pattern_swap_is_measured_on_onsets():
    """`midiOn "^41" (<| "k k k <k k*2>")` names no effect at all — it replaces the
    pattern. What changes is WHEN events happen."""
    src = 'd1 $ midiOn "^41" (<| "k k k <k [<~ k> k]>") $ s "bd"'
    c = ctl(src, 41)
    assert c.kind == "pattern" and c.lens == "onsets"


def test_a_bare_single_value_string_is_not_mistaken_for_a_rhythm():
    src = 'd1 $ s "bd" # n (range 0 8 "^15")'
    assert ctl(src, 15).kind != "pattern"


# ------------------------------------------------- helper -> orbit attribution

def test_a_let_helper_attributes_its_cc_to_every_orbit_that_applies_it():
    """you_my_sunshine's real shape: ONE gF3 helper owned FOUR orbits, which is why one
    knob left d5/d7/d9/d11 all inaudible. Attributing the CC only to the `let` line
    would have reported "CC51 affects no orbits" — exactly backwards."""
    src = ('let gF3 = (# djfbus 3 (range 0.05 0.95 "^51"))\n'
           'd5 $ gF3 $ s "voice"\n'
           'd7 $ gF3 $ s "hh"\n'
           'd9 $ gF3 $ s "pad"\n'
           'd11 $ s "chop"\n')
    c = ctl(src, 51)
    assert c.orbits == [5, 7, 9]
    assert 11 not in c.orbits


def test_a_helper_no_orbit_applies_is_still_reported_as_dead_wiring():
    src = ('let gF9 = (# djfbus 9 (range 0.05 0.95 "^55"))\n'
           'd1 $ s "bd"\n')
    c = ctl(src, 55)
    assert c is not None and c.orbits == []
    assert c not in lens.measurable(Track(path="t", text=src))


# ------------------------------------------------------------------ safety

@pytest.mark.parametrize("cc", list(range(77, 85)))
def test_the_ardour_fader_bank_can_never_be_returned(cc):
    """CC 77-84 are MIDI-learned to Ardour's track gains and CC77 is the GLOBAL gain --
    down is total silence. Excluded by construction, not by convention, so no future
    caller can opt back in by accident."""
    src = f'd1 $ s "bd" # gain "^{cc}"'
    assert ctl(src, cc) is None
    assert cc in lens.FORBIDDEN_CC


def test_no_measurable_control_is_ever_in_the_forbidden_bank():
    src = 'd1 $ s "bd" # gain "^77" # crushbus 1 (range 16 4 "^54")'
    ccs = {c.cc for c in lens.measurable(Track(path="t", text=src))}
    assert ccs == {54}


# ----------------------------------------------- rest values and probes

@pytest.mark.parametrize("cc", [49, 50, 51])
def test_a_dj_filter_rests_at_CENTRE_not_at_zero(cc):
    """djf 0.5 is bypass; djf 0.05 is a ~26 Hz low-pass, i.e. SILENCE. Parking a filter
    at 0 to establish a baseline would measure silence and call the track broken. This
    exact confusion cost an evening (#48) and left four orbits dark on stage."""
    c = ctl(f'd1 $ s "bd" # djfbus 1 (range 0.05 0.95 "^{cc}")', cc)
    assert c.rest == 64
    assert c.bipolar


def test_a_dj_filter_probes_BOTH_directions():
    """It is bipolar — one side closes a low-pass, the other opens a high-pass. Probing
    one side only is how you get a confident wrong answer about a working filter."""
    c = ctl('d1 $ s "bd" # djfbus 1 (range 0.05 0.95 "^49")', 49)
    assert min(c.probes) < 64 < max(c.probes)


def test_an_ordinary_control_rests_at_zero():
    c = ctl('d1 $ s "bd" # crushbus 1 (range 16 4 "^54")', 54)
    assert c.rest == 0 and not c.bipolar


# ---------------------------------------------------------- unmeasurable

def test_pan_is_reported_UNMEASURABLE_not_broken():
    """The capture sums to mono, so no available lens can see pan. Reporting it as "no
    impact" would be a false failure on a working control — and a suite that cries wolf
    is a suite you learn to ignore."""
    c = ctl('d1 $ s "bd" # pan (range 0 1 "^35")', 35)
    assert c.kind == "pan" and not c.measurable


def test_lens_value_reads_every_declared_lens():
    """Guards against a lens name in the table with no reader behind it — which would
    raise KeyError mid-run, on the rig, during a soundcheck."""
    from at.harness import Shape
    sh = Shape(orbit=1, peak_db=-6.0, rms_db=-20.0, centroid_hz=800.0,
               low_ratio=0.5, high_ratio=0.2, onsets_per_s=4.0, duty=0.7)
    for _kw, _kind, name in lens.EFFECT_LENS:
        if name:
            assert isinstance(lens.lens_value(sh, name), float)
            assert name in lens.LENS_UNITS


# ------------------------------------------------------- corpus coverage

def test_the_classifier_covers_the_real_corpus():
    """A coverage GUARD, not a vanity metric: it fails if a future edit regresses the
    multi-line scan that took unknown from 33% to 3.3%. Numbers measured 2026-07-29
    over 694 tracks / 5046 bindings."""
    import glob

    from pvlint.core import load
    total = unknown = 0
    for f in sorted(glob.glob(str(TOOLS.parent / "live/**/*.tidal"), recursive=True)):
        for c in lens.controls(load(f)):
            total += 1
            unknown += c.kind == "unknown"
    assert total > 4000, f"corpus shrank unexpectedly ({total} bindings)"
    assert unknown / total < 0.08, f"{unknown}/{total} unclassified — the scan regressed"
