"""Guide-QA: Fehler-Injektion auf Mini-Guide-Karten — deterministisch, ohne LLM."""

import guide_qa as gq
import qa


def _card(block, md):
    return {"block": block, "block_norm": block.casefold(), "md": md}


AUSF = ("<!-- section: Alpha -->\n<!-- compact -->\n- m\n<!-- ausführlich -->\n"
        "Einstieg in den Block.\n"
        "<!-- sub: beginner | Kantenzug Definition -->\n"
        "Ein Kantenzug verbindet Knoten über Kanten im Graphen.\n")


def test_ausfuehrlich_extrahiert_lerntext():
    assert gq._ausfuehrlich(AUSF).startswith("\nEinstieg")
    assert gq._ausfuehrlich("nur text") == "nur text"


def test_marker_fehlend():
    cards = [_card("Alpha", AUSF)]
    rel = {"alpha": {"kantenzug definition", "fehlender aspekt"}}
    out = gq.marker_fehlend(cards, rel)
    assert out == ["Alpha · fehlender aspekt"]


def test_ziel_ohne_anker():
    cards = [_card("Alpha", AUSF)]
    ziele = [{"block_norm": "alpha", "ziel_id": "z1", "text": "Kantenzug im Graphen erklären"},
             {"block_norm": "alpha", "ziel_id": "z2", "text": "Adjazenzmatrix aufstellen können"}]
    out = gq.ziel_ohne_anker(cards, ziele)
    assert len(out) == 1 and "z2" in out[0]


def test_laengen_ausreisser():
    """Budget-Band statt Festrahmen: zu dünn und zu dick fallen auf, ohne Budget kein Urteil."""
    duenn = _card("Alpha", "<!-- ausführlich -->\nkurz")
    ok = _card("Beta", "<!-- ausführlich -->\n" + "x" * 500)
    dick = _card("Gamma", "<!-- ausführlich -->\n" + "x" * 2000)
    ohne = _card("Delta", "<!-- ausführlich -->\nkurz")
    budgets = {"alpha": 500, "beta": 500, "gamma": 500}  # delta: kein Inventar → übersprungen
    out = gq.laengen_ausreisser([duenn, ok, dick, ohne], budgets)
    assert [x["block"] for x in out] == ["Alpha", "Gamma"]
    assert out[1]["zeichen"] >= 2000 and out[1]["budget"] == 500


def test_budget_aus_substanz():
    """Dichte Subs bekommen mehr Budget; periphere zählen nicht."""
    dicht = {"relevance": "relevant", "facts": {"key_points": ["a", "b"], "cited_facts": [{}], "example_idea": "x"}}
    duenn = {"relevance": "relevant", "facts": {}}
    peripher = {"relevance": "peripheral", "facts": {"key_points": ["a"] * 9}}
    assert gq.sub_budget(dicht["facts"]) > gq.sub_budget(duenn["facts"])
    assert gq.block_budget([dicht, duenn, peripher]) == gq.block_budget([dicht, duenn])


def test_redundanz_findet_absatz_doppel():
    a = "Der Kantenzug verbindet Knoten über mehrere Kanten und darf Knoten wiederholen. " * 3
    b = "Der Kantenzug verbindet Knoten über mehrere Kanten und darf Knoten wiederholen, genau. " * 3
    c = "Völlig anderes Thema: Matrizen, Determinanten und lineare Abbildungen im Vektorraum. " * 3
    cards = [_card("Alpha", f"<!-- ausführlich -->\n{a}\n\n{c}"),
             _card("Beta", f"<!-- ausführlich -->\n{b}")]
    out = gq.redundanz(cards)
    assert len(out) == 1 and out[0]["a"].startswith("Alpha")


def test_lesbarkeit_fail_open(monkeypatch):
    def kaputt(md_by_num):
        raise RuntimeError("Modell fehlt")
    monkeypatch.setattr(gq.readability, "rate_sections", kaputt)
    assert gq.lesbarkeit([_card("Alpha", AUSF)]) == []


def test_note_guide_kalibrierung():
    """Gewicht = Punktabzug bei 100 %: 10 % fachlich falsch × 3.0 → 7.0; ungemessen zählt nicht."""
    assert qa.note({"fachlich_falsch": 0.1}, gq.NOTE_GEWICHTE_GUIDE) == 7.0
    ohne = {"marker_fehlend": 0.0, "ziel_ohne_anker": 0.0}
    assert qa.note(ohne, gq.NOTE_GEWICHTE_GUIDE) == 10.0


def test_marker_escaping_tolerant():
    """Escapte Titel (h\\~2\\~o) sind kein „Marker fehlt" — Writer und DB escapen verschieden."""
    md = "<!-- ausführlich -->\n<!-- sub: beginner | ^ und ~ müssen escaped werden (h\\\\~2\\\\~o) -->\nText."
    cards = [{"block": "Hoch", "block_norm": "hoch", "md": md}]
    rel = {"hoch": {gq._norm_title("^ und ~ müssen escaped werden (h~2~o)")}}
    assert gq.marker_fehlend(cards, rel) == []


async def test_fachlich_falsch_braucht_beide_bestaetiger(monkeypatch):
    """Befund zählt nur, wenn BEIDE Bestätiger zustimmen — Einzel-/Zweifach-Urteile
    ließen die Note desselben Guides zwischen 2.0 und 6.6 springen."""
    import agents
    calls = {"n": 0}

    async def fake_agent(key, prompt, timeout, **kw):
        calls["n"] += 1
        if "-fakten-3-" in key:    # Bestätiger 2: nur #1 bleibt
            return 0, '{"relevant": {"1": "ja"}}', ""
        if "-fakten-2-" in key:    # Bestätiger 1: #1 und #2
            return 0, '{"relevant": {"1": "ja", "2": "ja"}}', ""
        return 0, '{"relevant": {"1": "ja", "2": "ja", "3": "nein"}}', ""  # Pass 1

    monkeypatch.setattr(agents, "run_agent", fake_agent)
    cards = [_card("Alpha", AUSF), _card("Beta", AUSF), _card("Gamma", AUSF)]
    out = await gq._fachlich_falsch("t", cards)
    assert out == ["Alpha"] and calls["n"] == 3


async def test_fachlich_falsch_ohne_verdacht_kein_zweiter_pass(monkeypatch):
    import agents
    calls = {"n": 0}

    async def fake_agent(key, prompt, timeout, **kw):
        calls["n"] += 1
        return 0, '{"relevant": {"1": "nein", "2": "nein"}}', ""

    monkeypatch.setattr(agents, "run_agent", fake_agent)
    out = await gq._fachlich_falsch("t", [_card("Alpha", AUSF), _card("Beta", AUSF)])
    assert out == [] and calls["n"] == 1
