"""``_zig`` — the Zig toolchain resolver, step enumeration, and the warden adapter.

All zig invocations are mocked: CI carries no Zig toolchain, and real enumeration/
execution is exercised against the live fleet, not here.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from mgf.test_web import _zig

_LIST_STEPS = """\
  install (default)            Copy build artifacts to prefix path
  uninstall                    Remove build artifacts from prefix path
  run                          Build and run the scene
  test                         Run the 33-test roster (plus commons self-tests)
  safety                       Memory-safety job: adversary suite + fuzz
"""


def test_parse_list_steps_names_and_descriptions() -> None:
    steps = _zig.parse_list_steps(_LIST_STEPS)
    assert [n for n, _ in steps] == ["install", "uninstall", "run", "test", "safety"]
    assert dict(steps)["test"] == "Run the 33-test roster (plus commons self-tests)"
    assert dict(steps)["install"] == "Copy build artifacts to prefix path"  # (default) stripped


def test_is_test_step_classifies_tiers_not_build_steps() -> None:
    assert _zig.is_test_step("test") and _zig.is_test_step("test-unit")
    assert _zig.is_test_step("safety") and _zig.is_test_step("conformance") and _zig.is_test_step("fuzz")
    assert not _zig.is_test_step("run") and not _zig.is_test_step("install") and not _zig.is_test_step("docs")


def test_test_steps_includes_known_tiers_excludes_build_steps(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(
        _zig,
        "list_steps",
        lambda root, zig=None: [
            ("install", "x"),
            ("run", "y"),
            ("docs", "z"),
            ("test", "the suite"),
            ("test-unit", "unit"),
            ("safety", "mem-safety"),
            ("conformance", "gate-2"),
            ("fuzz", "fuzzing"),
        ],
    )
    got = [name for name, _ in _zig.test_steps("/proj")]
    assert got == ["test", "test-unit", "safety", "conformance", "fuzz"]  # order preserved
    assert not ({"install", "run", "docs"} & set(got))


def test_zig_cmd_prefers_a_real_binary(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(
        _zig.shutil, "which", lambda name: "/usr/bin/zig" if name == "zig" else None
    )
    monkeypatch.setattr(_zig, "_probe", lambda cmd: cmd[0] == "/usr/bin/zig")
    assert _zig.zig_cmd() == ["/usr/bin/zig"]


def test_zig_cmd_falls_back_to_ziglang(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(_zig.shutil, "which", lambda name: "/py3" if name == "python3" else None)
    # no working zig binary; the python3 candidate carries the ziglang wheel
    monkeypatch.setattr(_zig, "_probe", lambda cmd: cmd[0] == "/py3" and "ziglang" in cmd)
    assert _zig.zig_cmd() == ["/py3", "-m", "ziglang"]


def test_zig_cmd_none_when_no_toolchain(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(_zig.shutil, "which", lambda name: None)
    monkeypatch.setattr(_zig, "_probe", lambda cmd: False)
    assert _zig.zig_cmd() is None


def test_build_step_cmd(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(_zig, "zig_cmd", lambda: ["zig"])
    assert _zig.build_step_cmd("test") == ["zig", "build", "test"]
    monkeypatch.setattr(_zig, "zig_cmd", lambda: None)
    assert _zig.build_step_cmd("test") is None


def test_list_steps_raises_without_a_toolchain(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(_zig, "zig_cmd", lambda: None)
    with pytest.raises(_zig.ZigUnavailable):
        _zig.list_steps("/proj")


class _Req:
    """Minimal stand-in — the runner only reads ``.test_id``."""

    test_id = "test"


def test_zig_runner_maps_exit_code_to_pass_fail(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    from mgf.test_web.warden import runner as rmod

    monkeypatch.setattr(rmod, "build_step_cmd", lambda step: ["true"])
    assert rmod.zig_runner(tmp_path)(_Req(), lambda ev: None) == "pass"
    monkeypatch.setattr(rmod, "build_step_cmd", lambda step: ["false"])
    assert rmod.zig_runner(tmp_path)(_Req(), lambda ev: None) == "fail"


def test_zig_runner_errors_without_a_toolchain(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    from mgf.test_web.warden import runner as rmod

    monkeypatch.setattr(rmod, "build_step_cmd", lambda step: None)
    events: list[dict[str, object]] = []
    assert rmod.zig_runner(tmp_path)(_Req(), events.append) == "error"
    assert any(e.get("kind") == "error" for e in events)
