"""Integration test on the REAL corpus: structural invariants + coverage
regression guards. This is the mechanical answer to 'metadata prior; nothing
verified' — the agreement numbers are now asserted, so they can't silently rot.

Ranges (not exact equality) absorb honest content drift while still catching a
regression (e.g. the parser breaking and conflicts spiking)."""
import re


# ── structural invariants (must hold regardless of content) ──────────────────
def test_every_cited_score_parses(view):
    """Corner A: all 73 cited .tidal exist AND parse to a non-empty score.
    Guards the do-block / col-0 parser regression catalog-wide."""
    s = view["stats"]
    assert s["score_present"] == s["tracks_total"]
    assert s["score_parsed"] == s["tracks_total"]
    assert s["ac_unparsed"] == 0


def test_conflict_and_divergent_require_a_real_score(view):
    """The cardinal rule: a parser miss is NEVER reported as a disagreement."""
    for r in view["tracks"]:
        if r["ac"]["level"] in ("conflict", "divergent", "partial", "agree"):
            assert r["n_orbits"] > 0, f"{r['name']} flagged {r['ac']['level']} on empty score"


def test_metadata_only_is_truly_disjoint_from_score(view):
    """metadata_only must never contain a sound that's also in the score."""
    for r in view["tracks"]:
        a = set(r["score_sounds"])
        assert not (set(r["ac"]["metadata_only"]) & a), r["name"]


def test_candidate_takes_are_real(view):
    for r in view["tracks"]:
        for t in r["takes"]:
            assert re.fullmatch(r"Take\d+", t["take"]), t["take"]


def test_eda_coverage_is_honest(view):
    """The whole point of surfacing the gap: EDA is sparse and we don't hide it."""
    s = view["stats"]
    assert 0 < s["takes_with_eda"] <= s["takes_total"]
    assert s["with_eda"] <= s["recorded"]


# ── coverage regression guards (catch silent rot in the agreement numbers) ───
def test_agreement_distribution_within_bounds(view):
    s = view["stats"]
    assert s["tracks_total"] == 73                 # update consciously if catalog grows
    assert s["ac_agree"] >= 45                     # was 48 — alarm if it craters
    assert s["ac_conflict"] <= 4                   # was 2 — alarm if conflicts spike
    assert s["ac_divergent"] <= 6                  # was 3 — wrong-link suspects


# ── specific cases that taught us the lessons ────────────────────────────────
def _row(view, track):
    return next(r for r in view["tracks"] if r["track"] == track)


def test_burn_this_book_now_agrees(view):
    r = _row(view, "live/techno/noir/burn_this_book.tidal")
    assert r["ac"]["level"] == "agree"             # was a false conflict


def test_blue_gold_ccc0_is_divergent_not_conflict(view):
    r = _row(view, "live/collab/ccc/ccc0.tidal")
    assert r["ac"]["level"] == "divergent"         # wrong file link, properly labelled
    assert r["alias_siblings"], "should share the name with the real blue_gold.tidal"


# ── DRY contract: the emitted view validates against the pydantic model ───────
def test_view_validates_against_model(view):
    """models.py is the single source of truth; the build must conform to it
    (and so must the generated TS, which is derived from the same schema)."""
    from models import CatalogView
    cv = CatalogView.model_validate(view)
    assert cv.stats.tracks_total == len(cv.tracks)
