"""Invarianten nach einem (Fake-)E2E-Lauf: was IMMER gelten muss, egal welches Szenario.

Nutzt bewusst eigene, schlichte Prüfungen statt Pipeline-Heuristiken (Muster qa.py) —
geteilte blinde Flecken machen den Check wertlos. Rückgabe: Liste von Verstößen,
leer = alles konsistent.
"""

import json

import database as db
from textkit import _norm_title

_LEVELS_OK = {"beginner", "advanced", "expert"}
_RELEVANZ_OK = {"relevant", "peripheral"}


def _json(path):
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except (OSError, ValueError):
        return None


async def pruefe_invarianten(topic: str, files: dict | None = None,
                             mit_artefakten: bool = True) -> list[str]:
    fehler: list[str] = []
    subs = [dict(r) for r in await db.list_subblocks(topic)]
    cons = [r for r in subs if r["status"] == "consensus"]

    for r in cons:
        wo = f"{r['block']}/{r['sub_title']}"
        fk = None
        try:
            fk = json.loads(r["facts"]) if r["facts"] else None
        except ValueError:
            fehler.append(f"facts unparsebar: {wo}")
        if not (isinstance(fk, dict) and (fk.get("key_points") or fk.get("cited_facts"))):
            fehler.append(f"consensus-Sub ohne facts: {wo}")
        if r["level"] not in _LEVELS_OK:
            fehler.append(f"consensus-Sub ohne gültiges level: {wo}")
        if r["relevance"] not in _RELEVANZ_OK:
            fehler.append(f"consensus-Sub ohne relevance: {wo}")

    if mit_artefakten:
        art = [dict(r) for r in await db.get_sub_artefakte(topic)]
        fragen = [dict(r) for r in await db.list_question_pattern(topic)]
        versorgt = {(r["block_norm"], r["sub_norm"]) for r in art}
        versorgt |= {(r["block_norm"], r["sub_norm"]) for r in fragen}
        lebend = {(r["block_norm"], r["sub_norm"]) for r in subs if r["status"] in ("consensus", "variant")}
        for r in cons:
            if r["relevance"] == "relevant" and (r["block_norm"], r["sub_norm"]) not in versorgt:
                fehler.append(f"relevanter Sub ohne Frage/Artefakt: {r['block']}/{r['sub_title']}")
        for bn, sn in sorted({(r["block_norm"], r["sub_norm"]) for r in art} |
                             {(r["block_norm"], r["sub_norm"]) for r in fragen}):
            if (bn, sn) not in lebend:
                fehler.append(f"Waise (Ziel-Sub existiert nicht): {bn}/{sn}")

    # keine hängengebliebenen Karten
    for c in await db.kanban_cards(topic):
        if c["stage"] == "dead":
            fehler.append(f"dead-Karte: {c['board']}/{c['card_id']}")

    if files is not None:
        if not files["final"].exists():
            fehler.append("blocks.md fehlt")
        sc = _json(files["sidecar"])
        if not isinstance(sc, dict):
            fehler.append("sidecar-Datei fehlt/unparsebar")
        else:  # Sidecar und DB-consensus müssen dieselbe Sub-Menge tragen
            db_menge = {(r["block_norm"], r["sub_norm"]) for r in cons}
            sc_menge = {(_norm_title(bt), _norm_title(str(s.get("title", ""))))
                        for bt, ss in sc.items() for s in ss if isinstance(s, dict)}
            for extra in sorted(sc_menge - db_menge):
                fehler.append(f"Sidecar-Sub fehlt in DB: {extra}")
            for extra in sorted(db_menge - sc_menge):
                fehler.append(f"DB-consensus fehlt im Sidecar: {extra}")
    return fehler


async def pruefe_guide_invarianten(topic: str, format_name: str = "Guide") -> list[str]:
    """Jeder relevante consensus-Sub trägt einen Sub-Marker im Guide (Muster
    guide_qa.marker_fehlend, ohne LLM)."""
    import guide_qa
    fehler: list[str] = []
    cards = [dict(r) for r in await db.list_guide_cards(topic, format_name)]
    if not cards:
        return ["keine Guide-Karten"]
    for c in cards:
        if c["status"] != "ok" or not (c.get("md") or "").strip():
            fehler.append(f"Guide-Karte nicht ok: {c['block']} ({c['status']})")
    subs_rel: dict[str, set] = {}
    for r in await db.list_subblocks(topic):
        if r["status"] == "consensus" and r["relevance"] != "peripheral":
            subs_rel.setdefault(r["block_norm"], set()).add(r["sub_norm"])
    fehler += [f"Sub-Marker fehlt: {m}" for m in guide_qa.marker_fehlend(cards, subs_rel)]
    return fehler
