"""Deterministic UTs for the feature MDA logic (feature_eda).

We don't need the real corpus to trust the math: build a tiny synthetic matrix with
KNOWN structure (a redundant pair, a known intrinsic dimensionality, two separable
clusters) and assert each lens recovers it. Real-data validation is the `all` run;
this guards the analysis itself against silent regressions.
"""
import numpy as np
import pytest

import feature_eda as FE


def test_correlate_finds_and_prunes_redundant_pair():
    rng = np.random.RandomState(0)
    a = rng.randn(200)
    X = np.column_stack([a, a * 2 + 1e-6 * rng.randn(200),   # b ≈ 2a  → redundant
                         rng.randn(200), rng.randn(200)])     # c, d independent
    names = ["a", "b", "c", "d"]
    r = FE.correlate(X, names, thresh=0.9)
    assert r["n_correlated_pairs"] == 1
    # exactly one of {a,b} pruned, the other kept; c,d both survive
    pruned = {p["drop"] for p in r["pruned"]}
    assert pruned in ({"a"}, {"b"})
    assert "c" in r["kept"] and "d" in r["kept"]
    assert r["n_kept"] == 3


def test_pca_recovers_low_intrinsic_dim():
    rng = np.random.RandomState(1)
    # 5 columns but only 2 real latent factors → ~2 intrinsic dims
    f1, f2 = rng.randn(300), rng.randn(300)
    X = np.column_stack([f1, f2, f1 + f2, f1 - f2, 0.5 * f1 + 0.3 * f2])
    r = FE.pca(X, [f"x{i}" for i in range(5)])
    assert r["intrinsic_dim_90pct"] <= 2
    assert r["components"][0]["explained"] > 0


def test_cluster_separates_two_known_groups():
    rng = np.random.RandomState(2)
    n = 60
    g0 = rng.randn(n, 4) + np.array([5, 5, 0, 0])
    g1 = rng.randn(n, 4) + np.array([-5, -5, 0, 0])
    X = np.vstack([g0, g1])
    names = ["s0", "s1", "noise0", "noise1"]
    rows = ([{"folder": "f", "file": str(i), "family": "kick"} for i in range(n)] +
            [{"folder": "f", "file": str(i), "family": "bass"} for i in range(n)])
    r = FE.cluster(X, names, rows)
    assert r["n_labelled"] == 2 * n
    assert r["ari_vs_resolver"] > 0.8            # cleanly separable → high agreement
    # the separating features (s0/s1) must dominate importance over the noise dims
    top2 = {x["f"] for x in r["top_family_features"][:2]}
    assert top2 == {"s0", "s1"}


@pytest.mark.skipif(not FE.FEATURES.exists(),
                    reason="sample_features.json not built yet (run sample_features.py)")
def test_load_matrix_on_real_features():
    X, names, rows = FE.load_matrix()
    assert X.shape[0] > 0 and X.shape[1] > 0
    assert not np.isnan(X).any()                 # median-imputation leaves no NaNs
    assert len(names) == X.shape[1] and len(rows) == X.shape[0]
