"""API-Routen: Lesen + Scan-Start über httpx/ASGITransport (gleicher Event-Loop wie
create_task und der DB-Write-Lock — fastapi.TestClient wäre die Loop-Falle)."""
import asyncio

import httpx
import pytest

import config
import scan_dienst


@pytest.fixture
async def client(gescanntes_repo, monkeypatch, tmp_path):
    wurzel, git = gescanntes_repo
    datei = tmp_path / "projekte.txt"
    datei.write_text(f"{wurzel}\n{tmp_path}/tot\n")
    monkeypatch.setattr(config, "PROJEKTE_DATEI", datei)
    scan_dienst._laeufe.clear()
    from server import app
    transport = httpx.ASGITransport(app=app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as c:
        yield c, wurzel, git


async def test_projektliste(client):
    c, wurzel, _ = client
    r = await c.get("/api/projekte")
    assert r.status_code == 200
    eintraege = {p["name"]: p for p in r.json()}
    assert eintraege["mini"]["hat_planer"] is True
    assert eintraege["mini"]["drift"] == []
    assert eintraege["mini"]["scan_laeuft"] is False
    assert "fehler" in eintraege["tot"]


async def test_projekt_detail(client):
    c, _, _ = client
    r = await c.get("/api/projekte/mini")
    daten = r.json()
    assert daten["kern"]["titel"] == "mini"
    assert daten["bereiche"] and daten["bereiche"][0]["features"]
    assert "rechner.py::addiere" in daten["einheiten"]
    assert daten["doku_ordnung"]
    assert (await c.get("/api/projekte/gibtsnicht")).status_code == 404


async def test_einheit_mit_code(client):
    c, _, _ = client
    r = await c.get("/api/projekte/mini/einheit", params={"id": "rechner.py::addiere"})
    e = r.json()
    assert "return a + b" in e["code"]
    assert e["veraltet"] is False
    assert (await c.get("/api/projekte/mini/einheit", params={"id": "x.py::y"})).status_code == 404


async def test_drift_nach_commit(client):
    c, wurzel, git = client
    (wurzel / "rechner.py").write_text((wurzel / "rechner.py").read_text() + "\nX9 = 9\n")
    git("add", "-A")
    git("commit", "-m", "drift")
    r = await c.get("/api/projekte")
    mini = next(p for p in r.json() if p["name"] == "mini")
    assert mini["drift"] == ["rechner.py"]
    e = (await c.get("/api/projekte/mini/einheit", params={"id": "rechner.py::addiere"})).json()
    assert e["veraltet"] is True


async def test_scan_starten_und_pollen(client):
    c, _, _ = client
    r = await c.post("/api/projekte/mini/scan", json={"art": "rescan"})
    assert r.json()["status"] == "gestartet"
    doppel = await c.post("/api/projekte/mini/scan", json={"art": "rescan"})
    assert doppel.json()["status"] in ("laeuft", "gestartet")  # idempotent, nie Doppellauf
    for _ in range(100):
        s = (await c.get("/api/projekte/mini/scan-status")).json()
        if not s["laeuft"]:
            break
        await asyncio.sleep(0.1)
    assert s["ergebnis"]["status"] in ("ok", "unveraendert")


async def test_scan_409_bei_dirty_tree(client):
    c, wurzel, _ = client
    (wurzel / "schmutz.py").write_text("X = 1\n")
    r = await c.post("/api/projekte/mini/scan", json={"art": "scan"})
    assert r.status_code == 409
    assert "uncommittete" in r.json()["detail"]


async def test_fs_browse(client, tmp_path):
    c, _, _ = client
    (tmp_path / "unter" / ".planer").mkdir(parents=True)
    (tmp_path / ".versteckt").mkdir()
    r = await c.get("/api/fs", params={"pfad": str(tmp_path)})
    daten = r.json()
    assert daten["pfad"] == str(tmp_path.resolve())
    assert daten["eltern"] == str(tmp_path.resolve().parent)
    ordner = {o["name"]: o for o in daten["ordner"]}
    assert ordner["unter"]["hat_planer"] is True
    assert ".versteckt" not in ordner
    assert (await c.get("/api/fs", params={"pfad": str(tmp_path / "fehlt")})).status_code == 404


async def test_projekt_hinzufuegen_und_entfernen(client, tmp_path):
    c, _, _ = client
    neu = tmp_path / "neues-repo"
    (neu / ".planer").mkdir(parents=True)

    assert (await c.post("/api/projekte", json={"pfad": str(neu)})).status_code == 200
    namen = [p["name"] for p in (await c.get("/api/projekte")).json()]
    assert "neues-repo" in namen and "mini" in namen
    assert (await c.post("/api/projekte", json={"pfad": str(neu)})).status_code == 409

    assert (await c.delete("/api/projekte/neues-repo")).status_code == 200
    namen = [p["name"] for p in (await c.get("/api/projekte")).json()]
    assert "neues-repo" not in namen and "mini" in namen
    assert (neu / ".planer").is_dir()  # Artefakte bleiben erhalten
    assert (await c.delete("/api/projekte/neues-repo")).status_code == 404
