"""Viewer-Service: Parser, Drift, Doku-Ordnung, Projektliste."""
from pathlib import Path

import config
import projekte
import viewer

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


def test_lese_einheiten_mit_entscheidungen():
    text = """# Einheiten: a.py
stand: abc

## a.py::eins
beschreibung: Macht eins.
input: x
output: y
entscheidungen:
- Erste Entscheidung.
  beleg: "code hier"
- Zweite mit Umbruch
  und Fortsetzung.
kanten:
- ruft-auf: a.py::zwei

## b.py
beschreibung: Datei-Einheit.
"""
    e = viewer.lese_einheiten(text)
    eins = e["a.py::eins"]
    assert eins["entscheidungen"] == ["Erste Entscheidung.", "Zweite mit Umbruch und Fortsetzung."]
    assert eins["kanten"] == [{"typ": "ruft-auf", "ziel": "a.py::zwei"}]
    assert (eins["input"], eins["output"]) == ("x", "y")
    assert e["b.py"]["beschreibung"] == "Datei-Einheit."


def test_lese_kern_und_pruefung():
    kern = viewer.lese_kern("# Kern: demo\nZwei Sätze.\n\ntragende-einheiten: a.py::x, b.py::y\n")
    assert kern["titel"] == "demo" and kern["tragende_einheiten"] == ["a.py::x", "b.py::y"]
    pr = viewer.lese_pruefung("# Prüfung: demo\ntests: make test\nlint: (keins vorhanden)\n"
                              "start: make dev\nhinweise:\n- Vorsicht.\n")
    assert pr["tests"] == "make test" and pr["hinweise"] == ["Vorsicht."]


def test_lese_features_architektur_flows():
    b = viewer.lese_features("# Bereich: rechnen\nbeschreibung: Rechnet.\n\n"
                             "## Feature: Addieren [kern]\nbeschreibung: Summen.\n\n"
                             "### Kann addieren\neinheiten: a.py::eins\n")
    assert b["features"][0]["flag"] == "kern"
    assert b["features"][0]["teilfeatures"][0]["einheiten"] == ["a.py::eins"]
    a = viewer.lese_architektur("# Architektur: demo\n\n## Komponente: kernstück\n"
                                "beschreibung: Trägt.\neinheiten: a.py::eins\n"
                                "beziehungen:\n- nutzt: anzeige\n")
    assert a[0]["beziehungen"] == [{"typ": "nutzt", "ziel": "anzeige"}]
    f = viewer.lese_flows("# Flows: demo\n\n## Flow: ablauf\n1. Schritt eins — a.py::eins\n"
                          "2. Schritt ohne Referenz\n")
    assert f[0]["schritte"][0]["einheiten"] == ["a.py::eins"]
    assert f[0]["schritte"][1]["einheiten"] == []


def test_code_extrahieren():
    assert "return a + b" in viewer.code_extrahieren(MINI, "rechner.py::addiere")
    assert viewer.code_extrahieren(MINI, "rechner.py::BASIS").startswith("BASIS = 10")
    assert viewer.code_extrahieren(MINI, "rechner.py::gibtsnicht") == ""
    assert viewer.code_extrahieren(MINI, "wegdatei.py::x") == ""
    assert "def merke" in viewer.code_extrahieren(MINI, "rechner.py::Speicher.merke")


async def test_lade_projekt_und_drift(gescanntes_repo):
    wurzel, git = gescanntes_repo
    projekt = viewer.lade_projekt(wurzel)
    assert "rechner.py::addiere" in projekt["einheiten"]
    assert projekt["kern"]["titel"] == "mini"
    assert projekt["staende"]["rechner.py"]
    assert viewer.drift(wurzel, projekt["staende"])["veraltet"] == []

    (wurzel / "rechner.py").write_text((wurzel / "rechner.py").read_text() + "\nX2 = 2\n")
    git("add", "-A")
    git("commit", "-m", "aenderung")
    d = viewer.drift(wurzel, projekt["staende"])
    assert d["veraltet"] == ["rechner.py"]  # anzeige.py unverändert


def test_doku_ordnung_mit_zyklus():
    projekt = {
        "bereiche": [
            {"name": "oben", "features": [{"teilfeatures": [{"einheiten": ["a.py::x"]}]}]},
            {"name": "unten", "features": [{"teilfeatures": [{"einheiten": ["b.py::y"]}]}]},
        ],
        "einheiten": {
            "a.py::x": {"kanten": [{"typ": "ruft-auf", "ziel": "b.py::y"}]},
            "b.py::y": {"kanten": []},
        },
    }
    assert viewer.doku_ordnung(projekt) == ["unten", "oben"]  # Fundament zuerst
    # Zyklus: beide nutzen einander → deterministisch, beide enthalten
    projekt["einheiten"]["b.py::y"]["kanten"] = [{"typ": "nutzt", "ziel": "a.py::x"}]
    ordnung = viewer.doku_ordnung(projekt)
    assert sorted(ordnung) == ["oben", "unten"] and len(ordnung) == 2


def test_projektliste(tmp_path, monkeypatch):
    echt = tmp_path / "echt"
    echt.mkdir()
    (tmp_path / "kopie").mkdir()
    datei = tmp_path / "projekte.txt"
    datei.write_text(f"# Kommentar\n{echt}\n{tmp_path}/weg\n{tmp_path}/kopie\n{echt}\n")
    monkeypatch.setattr(config, "PROJEKTE_DATEI", datei)
    liste = projekte.lade()
    assert [p.get("fehler") is None for p in liste] == [True, False, True, False]
    assert "existiert nicht" in liste[1]["fehler"]
    assert "Namenskollision" in liste[3]["fehler"]
    assert projekte.finde("echt") == echt and projekte.finde("weg") is None
