"""Light feature + sample-analysis engine tests (#38) — torch-free, real DSP.

Synth clips with known properties so the asserts are about MEASUREMENT, not a
fixture: a low sine reads bass register, a bright noise reads high register, the
feature stack carries the L0 floor + a correct tempo. conftest sets up paths.

    cd armada/api && ~/.virtualenvs/douanier/bin/python -m pytest tests/test_feats.py -q
"""
import sys
import tempfile
from pathlib import Path

import numpy as np
import soundfile as sf

from engines import feats

SR = 22050


def _write(y):
    p = Path(tempfile.mktemp(suffix=".wav"))
    sf.write(str(p), y.astype("float32"), SR)
    return str(p)


def _sine(freqs, secs=2.0):
    t = np.linspace(0, secs, int(SR * secs), endpoint=False)
    y = sum(np.sin(2 * np.pi * f * t) for f in freqs) / len(freqs)
    return _write(0.3 * y)


def _noise(secs=2.0, highpass=False):
    rng = np.random.default_rng(0)
    y = rng.standard_normal(int(SR * secs))
    if highpass:                      # crude high-emphasis → bright, high centroid
        y = np.diff(y, prepend=0.0)
    return _write(0.2 * y / (np.abs(y).max() + 1e-9))


def test_features_has_L0_floor_and_tempo():
    r = feats.features(_sine([220, 277, 330]), rhythm=True)
    f = r["features"]
    # the robust L0 tier must always be present
    for k in feats.FEATURE_TIERS["L0"]:
        assert k in f, f"missing L0 feature {k}"
    assert r["engine"] == "light" and r["sr"] == SR
    # tempo came through the librosa 0.11 path (feature.tempo, not rhythm.tempo)
    assert "tempo_bpm" in f and f["tempo_bpm"] > 0


def test_features_detects_major_key():
    f = feats.features(_sine([220, 277.18, 329.63]), rhythm=False)["features"]
    assert f.get("key") == "A" and f.get("key_mode") == "maj"


def test_sample_role_is_measured_not_named():
    # a 60 Hz sine is bass by SPECTRUM even if we (mis)name it "lead"
    low = feats.sample_profile(_sine([60]), name="lead_synth")
    assert low["role"] == "bass" and low["centroid"] < 180
    # a bright noise sits in the high register
    hi = feats.sample_profile(_noise(highpass=True), name="")
    assert hi["role"] in {"melodic", "atmos"} and hi["centroid"] > low["centroid"]


def test_sample_bands_sum_to_100():
    sp = feats.sample_profile(_sine([220, 440]), name="")
    total = sum(sp["profile"]["bands"].values())
    assert abs(total - 100.0) < 0.5
    assert "time_activity" in sp and 0.0 <= sp["time_activity"] <= 1.0


def test_engine_pulls_no_torch():
    feats.features(_sine([330]), rhythm=False)
    feats.sample_profile(_sine([330]), name="")
    assert "torch" not in sys.modules     # the light path must never import torch
