"""Gates gegen das mini_repo-Fixture: gültiges Fragment passiert, jede Manipulation
wird mit genau dem erwarteten Fehler gefangen."""
from pathlib import Path

import gates

MINI = Path(__file__).parent / "fixtures" / "mini_repo"

GUT = '''# Einheiten: rechner.py
stand: UNBEKANNT

## rechner.py::addiere
beschreibung: Addiert zwei Zahlen.
input: zwei Zahlen a, b
output: die Summe
entscheidungen:
- Reine Funktion ohne Zustand.
  beleg: "return a + b"
kanten:
- wird-genutzt-von: anzeige.py::zeige

## rechner.py::Speicher
beschreibung: Merkt sich das letzte Ergebnis.
input: Werte über merke()
output: letzter Wert in self.letztes
entscheidungen:
- Startet leer.
  beleg: "self.letztes = None"
kanten:
- nutzt: rechner.py::addiere
'''


def test_gutes_fragment_passiert():
    fehler, warnungen, einheiten = gates.pruefe_fragment(GUT, MINI, "rechner.py")
    assert fehler == []
    assert [e["id"] for e in einheiten] == ["rechner.py::addiere", "rechner.py::Speicher"]


def test_falsches_zitat():
    kaputt = GUT.replace('beleg: "return a + b"', 'beleg: "return a - b"')
    fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py")
    assert any("Zitat nicht wörtlich" in f for f in fehler)


def test_erfundener_anker():
    kaputt = GUT.replace("## rechner.py::addiere", "## rechner.py::subtrahiere")
    fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py")
    assert any("Symbol 'subtrahiere' nicht in" in f for f in fehler)


def test_kaputte_kante():
    kaputt = GUT.replace("- nutzt: rechner.py::addiere",
                         "- nutzt: rechner.py::addiere (wichtig!)")
    fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py")
    assert any("Kante unlesbar" in f for f in fehler)


def test_unbekannter_kanten_typ():
    kaputt = GUT.replace("- nutzt:", "- verwendet:")
    fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py")
    assert any("unbekannter Kanten-Typ" in f for f in fehler)


def test_fremde_datei_abgelehnt():
    fehler, _, _ = gates.pruefe_fragment(GUT, MINI, "anzeige.py")
    assert any("gehört nicht zur gescannten Datei" in f for f in fehler)


def test_leere_antwort():
    fehler, _, _ = gates.pruefe_fragment("", MINI, "rechner.py")
    assert any("keine Einheit geparst" in f for f in fehler)


def test_escaping_normalisierung():
    frag = GUT.replace('beleg: "return a + b"', 'beleg: "return f\\"Ergebnis: {addiere(a, b)}\\""')
    frag = frag.replace("## rechner.py::addiere", "## anzeige.py::zeige").replace(
        "# Einheiten: rechner.py", "# Einheiten: anzeige.py")
    # nur den umgebauten zeige-Block prüfen
    block = frag.split("\n\n")[1]
    fehler, _, _ = gates.pruefe_fragment(block, MINI, "anzeige.py")
    assert not any("Zitat" in f for f in fehler)


def test_fehlende_symbole():
    _, _, einheiten = gates.pruefe_fragment(GUT, MINI, "rechner.py")
    fehlt = gates.fehlende_symbole(["addiere", "skaliere", "Speicher", "BASIS"], einheiten, [])
    assert fehlt == ["skaliere", "BASIS"]
    fehlt2 = gates.fehlende_symbole(["skaliere"], einheiten, ["rechner.py::skaliere"])
    assert fehlt2 == []


def test_repariere_kanten():
    """Erfundene Kanten-Ziele werden gestrichen (Datei erfunden, Symbol erfunden,
    Prefix fehlt) — gültige Kanten und alle anderen Zeilen bleiben unangetastet."""
    kaputt = GUT.replace(
        "- nutzt: rechner.py::addiere",
        "- nutzt: rechner.py::addiere\n- ruft-auf: erfunden.py::x\n"
        "- nutzt: rechner.py::gibtsnicht\n- löst-aus: nur_symbol")
    bereinigt, gestrichen = gates.repariere_kanten(kaputt, MINI, "rechner.py")
    assert len(gestrichen) == 3
    assert "- nutzt: rechner.py::addiere" in bereinigt
    assert "erfunden.py" not in bereinigt and "gibtsnicht" not in bereinigt
    fehler, _, _ = gates.pruefe_fragment(bereinigt, MINI, "rechner.py")
    assert fehler == []


def test_repariere_belege_schnappt_fast_richtige_zitate():
    """Ein-Zeichen-Abweichung (`=` statt Original) wird auf die echte Quellzeile
    geschnappt; frei erfundene Zitate bleiben stehen und fallen im Gate durch."""
    kaputt = GUT.replace('beleg: "return a + b"', 'beleg: "return a+b"')
    repariert, korrigiert = gates.repariere_belege(kaputt, MINI, "rechner.py")
    assert len(korrigiert) == 1
    fehler, _, _ = gates.pruefe_fragment(repariert, MINI, "rechner.py")
    assert fehler == []

    erfunden = GUT.replace('beleg: "return a + b"', 'beleg: "raise TotalErfunden(xyz)"')
    repariert2, korrigiert2 = gates.repariere_belege(erfunden, MINI, "rechner.py")
    assert korrigiert2 == []
    fehler2, _, _ = gates.pruefe_fragment(repariert2, MINI, "rechner.py")
    assert any("Zitat nicht wörtlich" in f for f in fehler2)


def test_qualifizierte_methode_als_symbol():
    frag = GUT.replace("## rechner.py::Speicher", "## rechner.py::Speicher.merke")
    fehler, _, _ = gates.pruefe_fragment(frag, MINI, "rechner.py")
    assert not any("Speicher.merke" in f for f in fehler)


def test_beleg_mit_anfuehrungszeichen():
    """Belege mit eingebetteten Quotes/Regex (Befund 17) parsen und passieren."""
    frag = ('## anzeige.py::zeige\nbeschreibung: Formatiert.\ninput: a, b\noutput: Text\n'
            'entscheidungen:\n- Nutzt einen f-String.\n'
            '  beleg: "return f"Ergebnis: {addiere(a, b)}""\nkanten:\n')
    fehler, _, _ = gates.pruefe_fragment(frag, MINI, "anzeige.py")
    assert fehler == []


def test_datei_einheit_ohne_symbole(tmp_path):
    """Skript ohne def/class: Datei-Einheit `## pfad` (Befund 18)."""
    (tmp_path / "skript.py").write_text('import sys\nprint(sys.argv)\n')
    frag = ('## skript.py\nbeschreibung: Gibt Argumente aus.\ninput: argv\noutput: stdout\n'
            'entscheidungen:\n- Direkter Druck ohne Formatierung.\n  beleg: "print(sys.argv)"\nkanten:\n')
    fehler, _, einheiten = gates.pruefe_fragment(frag, tmp_path, "skript.py")
    assert fehler == [] and einheiten[0]["id"] == "skript.py"


def test_referenz_korrektur_falscher_prefix():
    """Symbol existiert eindeutig unter anderer Datei → ID wird korrigiert (Befund 19)."""
    sicht = "### Kann reparieren\neinheiten: falsch.py::_fix, kaputt.py::gibtsnirgends\n"
    text, gestrichen = gates.repariere_referenzen(sicht, {"richtig.py::_fix", "a.py::x"})
    assert "richtig.py::_fix" in text
    assert gestrichen == ["kaputt.py::gibtsnirgends"]


def test_referenz_integritaet():
    sicht = "### Kann rechnen\neinheiten: rechner.py::addiere, rechner.py::gibtsnicht\n"
    fehler = gates.pruefe_referenzen(sicht, {"rechner.py::addiere"})
    assert fehler == ["unbekannte Einheiten-Referenz: rechner.py::gibtsnicht"]
