"""Tests for the scaffold runner (apply the canonical template's byte-exact files)."""

from __future__ import annotations

import json
import shutil
from pathlib import Path

import pytest

from mgf.fed._roster import members
from mgf.fed._scaffold import run_scaffold
from mgf.fed._woodpecker import render_woodpecker

_REAL_TEMPLATES = Path(__file__).resolve().parents[3] / "mgf-standard" / "templates"

# Federation-INTEGRATION tests: read the REAL mgf-standard/templates, present only
# when the sibling repo is checked out alongside (federation-root layout). A
# single-repo CI clone has no sibling mgf-standard, so skip there — scaffold is still
# validated by `mgf-fed drift-check` (supervisor) and federation-root/local runs.
pytestmark = pytest.mark.skipif(
    not _REAL_TEMPLATES.exists(),
    reason="needs sibling mgf-standard/templates (federation-root); validated by drift-check",
)

_POLICY = """\
schema = 1
present = []
[exact]
"scripts/verify_codeberg.py" = "verify_codeberg.py"
[pyproject]
"""

# Policy that turns on generated-.woodpecker.yml (the Phase-1 wiring).
_POLICY_GEN = """\
schema = 1
present = []
woodpecker_generated = true
woodpecker_required_steps = ["lint", "publish"]
[exact]
[pyproject]
"""


def _root(tmp_path: Path) -> Path:
    reg = {
        "release_waves": [["mgf-a"]],
        "libraries": [{"repo": "mgf-a", "lang": "python", "version": "1.0.0"}],
    }
    std = tmp_path / "mgf-standard"
    (std / "templates").mkdir(parents=True)
    (std / "MGF_LIBRARIES.json").write_text(json.dumps(reg), encoding="utf-8")
    (std / "templates" / "policy.toml").write_text(_POLICY, encoding="utf-8")
    (std / "templates" / "verify_codeberg.py").write_text("# canonical verify\n", encoding="utf-8")
    repo = tmp_path / "mgf-a"
    repo.mkdir()
    (repo / "pyproject.toml").write_text(
        '[project]\nname = "mgf-a"\nversion = "1.0.0"\n'
    )  # keep pyproject check clean
    return tmp_path


def test_scaffold_applies_exact_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    root = _root(tmp_path)
    rc = run_scaffold(["mgf-a"], root=root)
    dest = root / "mgf-a" / "scripts" / "verify_codeberg.py"
    assert dest.is_file() and dest.read_text(encoding="utf-8") == "# canonical verify\n"
    assert "applied 1 byte-exact" in capsys.readouterr().out
    assert rc == 0  # no residual drift (present=[], empty pyproject policy)


def test_scaffold_dry_run_writes_nothing(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    root = _root(tmp_path)
    run_scaffold(["mgf-a"], dry_run=True, root=root)
    assert not (root / "mgf-a" / "scripts" / "verify_codeberg.py").exists()
    assert "would apply 1" in capsys.readouterr().out


def test_scaffold_noop_when_current(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    root = _root(tmp_path)
    run_scaffold(["mgf-a"], root=root)  # apply once
    capsys.readouterr()
    run_scaffold(["mgf-a"], root=root)  # second run: already current
    assert "already current" in capsys.readouterr().out


def test_scaffold_unknown_repo(tmp_path: Path) -> None:
    assert run_scaffold(["nope"], root=_root(tmp_path)) == 1


# --------------------------------------------------------------------------------
# generated .woodpecker.yml (Phase-1 wiring)
# --------------------------------------------------------------------------------


def _gen_root(tmp_path: Path) -> Path:
    """A root whose policy generates .woodpecker.yml, with a renderable member."""
    reg = {
        "release_waves": [["mgf-a"]],
        "libraries": [{"repo": "mgf-a", "lang": "python", "version": "1.0.0", "extras": ["dev"]}],
    }
    std = tmp_path / "mgf-standard"
    std.mkdir(parents=True)
    (std / "MGF_LIBRARIES.json").write_text(json.dumps(reg), encoding="utf-8")
    shutil.copytree(_REAL_TEMPLATES, std / "templates")
    (std / "templates" / "policy.toml").write_text(_POLICY_GEN, encoding="utf-8")

    repo = tmp_path / "mgf-a"
    (repo / "src" / "mgf" / "a").mkdir(parents=True)
    (repo / "src" / "mgf" / "a" / "__init__.py").write_text("__version__ = '1.0.0'\n")
    (repo / "pyproject.toml").write_text(
        '[project]\nname = "mgf-a"\nversion = "1.0.0"\n[project.optional-dependencies]\ndev = []\n'
    )
    return tmp_path


def test_scaffold_writes_generated_woodpecker(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    root = _gen_root(tmp_path)
    m = next(x for x in members(root) if x.repo == "mgf-a")
    rc = run_scaffold(["mgf-a"], root=root)
    wp = root / "mgf-a" / ".woodpecker.yml"
    assert wp.is_file()
    assert wp.read_text(encoding="utf-8") == render_woodpecker(m, root)
    out = capsys.readouterr().out
    assert ".woodpecker.yml (generated)" in out
    assert rc == 0  # nothing residual once the pipeline is written


def test_scaffold_woodpecker_dry_run_writes_nothing(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    root = _gen_root(tmp_path)
    run_scaffold(["mgf-a"], dry_run=True, root=root)
    assert not (root / "mgf-a" / ".woodpecker.yml").exists()
    out = capsys.readouterr().out
    assert "would apply" in out and ".woodpecker.yml (generated)" in out


def test_scaffold_woodpecker_noop_when_current(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    root = _gen_root(tmp_path)
    run_scaffold(["mgf-a"], root=root)  # write once
    capsys.readouterr()
    run_scaffold(["mgf-a"], root=root)  # already current
    out = capsys.readouterr().out
    assert ".woodpecker.yml (generated)" not in out
    assert "already current" in out


def test_scaffold_woodpecker_render_error_does_not_crash(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """An unrenderable repo (no src/**/__init__.py) reports the failure, no crash/write."""
    root = _gen_root(tmp_path)
    shutil.rmtree(root / "mgf-a" / "src")  # derive_params -> import-name lookup raises
    run_scaffold(["mgf-a"], root=root)
    out = capsys.readouterr().out
    assert "cannot render .woodpecker.yml" in out
    assert not (root / "mgf-a" / ".woodpecker.yml").exists()


# Generation-on policy with mgf-a excused from .woodpecker.yml generation (WF-13),
# still applying the byte-exact verify_codeberg.py.
_POLICY_GEN_EXEMPT = """\
schema = 1
present = []
woodpecker_generated = true
woodpecker_required_steps = ["lint", "publish"]
[exact]
"scripts/verify_codeberg.py" = "verify_codeberg.py"
[pyproject]
[exempt]
woodpecker = ["mgf-a"]
"""


def test_scaffold_exempt_leaves_woodpecker_but_applies_exact(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """An [exempt].woodpecker repo: bespoke .woodpecker.yml untouched, [exact] files applied."""
    root = _gen_root(tmp_path)
    (root / "mgf-standard" / "templates" / "policy.toml").write_text(
        _POLICY_GEN_EXEMPT, encoding="utf-8"
    )
    (root / "mgf-standard" / "templates" / "verify_codeberg.py").write_text(
        "# canonical verify\n", encoding="utf-8"
    )
    # A bespoke pipeline that carries every required step (so the name-list fallback passes).
    bespoke = "steps:\n  - name: lint\n  - name: slow-suite\n  - name: publish\n"
    wp = root / "mgf-a" / ".woodpecker.yml"
    wp.write_text(bespoke, encoding="utf-8")

    rc = run_scaffold(["mgf-a"], root=root)
    out = capsys.readouterr().out
    # The bespoke pipeline is left exactly as-is (never rendered/rewritten).
    assert wp.read_text(encoding="utf-8") == bespoke
    assert ".woodpecker.yml (generated)" not in out
    # The other [exact] file was still applied.
    dest = root / "mgf-a" / "scripts" / "verify_codeberg.py"
    assert dest.read_text(encoding="utf-8") == "# canonical verify\n"
    assert rc == 0  # no residual: name-list fallback satisfied, exact applied
