"""``fedres session-close`` — the end-of-session hygiene gate + state-at-exit scaffold.

The git probes are faked (``monkeypatch`` on ``session_close._git``) so these run
anywhere — including the CI test containers (``python:X-slim``), which carry no ``git``
binary. One real-``git`` integration test is guarded by ``skipif`` for local runs.
"""

from __future__ import annotations

import json
import shutil
import subprocess
from pathlib import Path

import pytest

import mgf.fedres.session_close as sc
from mgf.fedres.cli import main


def _fake_git(
    *,
    inside: str | None = "true",
    branch: str | None = "main",
    porcelain: str = "",
    ahead: str | None = None,
):
    """A stand-in for ``session_close._git`` — canned answers, no subprocess."""

    def fake(root: str, *args: str, strip: bool = True) -> str | None:
        key = " ".join(args)
        if key == "rev-parse --is-inside-work-tree":
            return inside
        if key == "rev-parse --abbrev-ref HEAD":
            return branch
        if key.startswith("status --porcelain"):
            return porcelain
        if key.startswith("rev-list --count"):
            return ahead
        return None

    return fake


def test_check_clean_repo_passes(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git())  # clean tree, no upstream
    assert main(["-C", str(tmp_path), "session-close"]) == 0
    assert "PASS" in capsys.readouterr().out


def test_check_dirty_repo_fails(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git(porcelain=" M wip.txt\n?? new.py\n"))
    assert main(["-C", str(tmp_path), "session-close"]) == 1
    out = capsys.readouterr().out
    assert "uncommitted" in out and "FAIL" in out


def test_allow_dirty_downgrades_to_warning(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git(porcelain=" M wip.txt\n"))
    assert main(["-C", str(tmp_path), "session-close", "--allow-dirty"]) == 0


def test_branch_ahead_of_upstream_warns_not_fails(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git(ahead="3"))  # 3 unpushed commits
    assert main(["-C", str(tmp_path), "session-close"]) == 0  # warn, not fail
    assert "ahead of upstream" in capsys.readouterr().out


def test_live_session_open_is_a_failure(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git())
    live = tmp_path / "fed-resources" / "live"
    live.mkdir(parents=True)
    (live / "CURRENT").write_text("pytest-20260706\n", encoding="utf-8")
    assert main(["-C", str(tmp_path), "session-close"]) == 1
    assert "live session" in capsys.readouterr().out


def test_json_format(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git())
    assert main(["-C", str(tmp_path), "--format", "json", "session-close"]) == 0
    payload = json.loads(capsys.readouterr().out)
    assert payload["ok"] is True and payload["command"] == "session-close"
    assert isinstance(payload["findings"], list)


def test_no_git_is_skip_not_fail(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git(inside=None))  # not a git work tree
    assert main(["-C", str(tmp_path), "session-close"]) == 0
    assert "skipped" in capsys.readouterr().out


def test_init_scaffold_is_prefilled(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    # a MODIFIED tracked file (` M ` leading space) guards the porcelain parse
    monkeypatch.setattr(sc, "_git", _fake_git(porcelain=" M README.md\n?? wip.txt\n"))
    assert main(["-C", str(tmp_path), "session-close", "--init"]) == 0
    out = capsys.readouterr().out
    assert "SESSION CLOSE" in out and "[MUST]" in out
    assert "README.md" in out and "wip.txt" in out  # full names, leading space not eaten


def test_init_write_to_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(sc, "_git", _fake_git())
    dest = tmp_path / "close.md"
    assert main(["-C", str(tmp_path), "session-close", "--init", "--write", str(dest)]) == 0
    assert "SESSION CLOSE" in dest.read_text(encoding="utf-8")


def test_doc_prints_canonical_standard(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    assert main(["-C", str(tmp_path), "session-close", "--doc"]) == 0
    out = capsys.readouterr().out
    assert "# Session-Close Standard" in out
    assert "fedres session-close --check" in out


@pytest.mark.skipif(shutil.which("git") is None, reason="needs the real git binary")
def test_real_git_repo_roundtrip(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    # exercises the real `_git` subprocess wrapper end-to-end (skipped in slim CI)
    def g(*a: str) -> None:
        subprocess.run(["git", "-C", str(tmp_path), *a], check=True, capture_output=True)  # noqa: S603,S607

    g("init", "-b", "main")
    g("config", "user.email", "t@example.com")
    g("config", "user.name", "Test")
    (tmp_path / "README.md").write_text("hi\n", encoding="utf-8")
    g("add", "-A")
    g("commit", "-m", "init")
    assert main(["-C", str(tmp_path), "session-close"]) == 0  # clean real repo → PASS
    assert "PASS" in capsys.readouterr().out
