"""Инварианты git для манифеста (R03.1).

Файл лежит внутри каталога прогона и попадает в тот же коммит, что и состояние.
Отдельного шага коммита у CLI нет, поэтому единственное, что здесь может сломаться, —
правило игнорирования. Проверяется настоящим git, а не рассуждением о содержимом
`.gitignore`: важно эффективное поведение, включая глобальные правила пользователя.
"""

from __future__ import annotations

import subprocess
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from test_core import repo, run  # noqa: F401,E402  фикстуры общие, свои не заводим

PROJECT_ROOT = Path(__file__).resolve().parent.parent

# Коды возврата `git check-ignore`. Различать 1 и 128 обязательно: 128 — «git не смог»
# (не репозиторий, битый путь), и принять его за «не игнорируется» значит получить тест,
# который зеленеет там, где проверять нечего.
IGNORED = 0
NOT_IGNORED = 1


def check_ignore(root: Path, rel: str) -> int:
    return subprocess.run(["git", "check-ignore", "-q", "--", rel], cwd=root).returncode


def staged(root: Path) -> list[str]:
    out = subprocess.run(
        ["git", "diff", "--cached", "--name-only"],
        cwd=root,
        capture_output=True,
        text=True,
        check=True,
    )
    return out.stdout.split("\n")


def test_manifest_ne_ignoriruetsya(run):  # noqa: F811
    rel = f".chekan/{run.slug}/manifest.md"
    assert (run.root / rel).is_file()
    assert check_ignore(run.root, rel) == NOT_IGNORED


def test_manifest_popadaet_v_indeks(run):  # noqa: F811
    # check=False: на игнорируемом каталоге `git add` падает сам, и это ровно та
    # регрессия, которую ловим, — она должна читаться как «файла нет в индексе».
    added = subprocess.run(
        ["git", "add", ".chekan"], cwd=run.root, check=False, capture_output=True, text=True
    )
    assert f".chekan/{run.slug}/manifest.md" in staged(run.root), added.stderr


def test_pravilo_ignora_lomaet_oba_invarianta(run):  # noqa: F811
    """Проверка проверки: с `.chekan/` в `.gitignore` оба инварианта обязаны падать.

    Без этого теста два предыдущих неотличимы от тестов, которые проходят при любом
    раскладе и ничего не утверждают.
    """
    (run.root / ".gitignore").write_text(".chekan/\n", encoding="utf-8")
    rel = f".chekan/{run.slug}/manifest.md"
    assert check_ignore(run.root, rel) == IGNORED
    # git отказывается добавлять целиком игнорируемый путь — отсюда check=False.
    subprocess.run(["git", "add", ".chekan"], cwd=run.root, check=False, capture_output=True)
    assert rel not in staged(run.root)


def _is_git_checkout(path: Path) -> bool:
    """Поставка из архива — не репозиторий: там git-инвариант проверять не на чем.

    Без этой оговорки `git check-ignore` возвращает 128 («not a git repository»),
    тест читает это как «не игнорируется» и краснеет на ровном месте.
    """
    return subprocess.run(
        ["git", "rev-parse", "--git-dir"], cwd=path, capture_output=True
    ).returncode == 0


@pytest.mark.skipif(
    not _is_git_checkout(PROJECT_ROOT),
    reason="поставка из архива не репозиторий — git-инвариант проверять не на чем",
)
def test_koren_proekta_ne_ignoriruet_katalog_progona():
    """Действующие правила самого chekan не отсекают каталог прогона."""
    # Синтетический путь: правило может появиться раньше, чем первый прогон нового слага.
    assert check_ignore(PROJECT_ROOT, ".chekan/probe/manifest.md") == NOT_IGNORED
    for path in sorted(PROJECT_ROOT.glob(".chekan/*/manifest.md")):
        rel = path.relative_to(PROJECT_ROOT).as_posix()
        assert check_ignore(PROJECT_ROOT, rel) == NOT_IGNORED, rel
