"""Re-Scan über git-Diff: nur berührte Dateien werden neu gescannt, Löschungen räumen
auf, sauberer Tree ist Pflicht. Läuft komplett in der Fake-Welt."""
import shutil
import subprocess
from pathlib import Path

import pytest

import fake_agents
import kanban
import repo as repo_mod

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


def _git(wurzel, *args):
    subprocess.run(["git", "-C", str(wurzel), *args], check=True, capture_output=True,
                   env={"PATH": "/usr/bin:/bin", "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t",
                        "GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t",
                        "HOME": str(wurzel)})


def _repo(tmp_path: Path) -> Path:
    ziel = tmp_path / "repo"
    shutil.copytree(MINI, ziel)
    _git(ziel, "init", "-b", "main")
    _git(ziel, "add", "-A")
    _git(ziel, "commit", "-m", "start")
    return ziel


@pytest.fixture
def cli(monkeypatch):
    import cli as cli_mod
    monkeypatch.setattr(kanban, "RETRY_BACKOFF", 0.05)
    return cli_mod


async def test_scan_dann_rescan_nur_geaenderte(cli, fake_welt, testdb, tmp_path):
    fake_agents.standard_antworten(fake_welt)
    wurzel = _repo(tmp_path)
    assert await cli._scan(wurzel) == 0
    stand1 = repo_mod.head_hash(wurzel)
    assert f"stand: {stand1}" in (wurzel / ".planer" / "einheiten" / "rechner.py.md").read_text()

    _git(wurzel, "add", "-A")
    _git(wurzel, "commit", "-m", "scan-artefakte")
    (wurzel / "rechner.py").write_text((wurzel / "rechner.py").read_text() + "\n\ndef neu():\n    return 1\n")
    _git(wurzel, "add", "-A")
    _git(wurzel, "commit", "-m", "neue funktion")
    stand2 = repo_mod.head_hash(wurzel)

    fake_welt.calls.clear()
    assert await cli._rescan(wurzel) == 0
    # Nur rechner.py wurde neu gescannt …
    assert all("anzeige" not in k for k in fake_welt.calls if "-schneiden-" in k)
    rechner = (wurzel / ".planer" / "einheiten" / "rechner.py.md").read_text()
    anzeige = (wurzel / ".planer" / "einheiten" / "anzeige.py.md").read_text()
    assert f"stand: {stand2}" in rechner and "## rechner.py::neu" in rechner
    assert f"stand: {stand1}" in anzeige  # unverändert übernommen
    # … aber die Sichten wurden neu aggregiert und referenzieren auch den Bestand
    assert any("-sichten-" in k for k in fake_welt.calls)
    kern = (wurzel / ".planer" / "kern.md").read_text()
    assert kern.startswith("# Kern:")


async def test_rescan_ohne_aenderung_ruft_kein_llm(cli, fake_welt, testdb, tmp_path):
    fake_agents.standard_antworten(fake_welt)
    wurzel = _repo(tmp_path)
    assert await cli._scan(wurzel) == 0
    _git(wurzel, "add", "-A")
    _git(wurzel, "commit", "-m", "scan-artefakte")
    fake_welt.calls.clear()
    assert await cli._rescan(wurzel) == 0
    assert fake_welt.calls == []


async def test_rescan_geloeschte_datei_raeumt_auf(cli, fake_welt, testdb, tmp_path):
    fake_agents.standard_antworten(fake_welt)
    wurzel = _repo(tmp_path)
    assert await cli._scan(wurzel) == 0
    _git(wurzel, "add", "-A")
    _git(wurzel, "commit", "-m", "scan-artefakte")
    (wurzel / "anzeige.py").unlink()
    _git(wurzel, "add", "-A")
    _git(wurzel, "commit", "-m", "anzeige weg")
    assert await cli._rescan(wurzel) == 0
    assert not (wurzel / ".planer" / "einheiten" / "anzeige.py.md").exists()


async def test_unsauberer_tree_bricht_ab(cli, fake_welt, testdb, tmp_path):
    wurzel = _repo(tmp_path)
    (wurzel / "schmutz.py").write_text("X = 1\n")
    with pytest.raises(SystemExit):
        await cli._scan(wurzel)


async def test_kein_git_repo_bricht_ab(cli, fake_welt, testdb, tmp_path):
    kein_repo = tmp_path / "kein-repo"
    kein_repo.mkdir()
    with pytest.raises(SystemExit):
        await cli._scan(kein_repo)
