"""PAPER-08 — a red run must be diagnosable from the CI log alone.

``mtest`` writes rich per-failure bundles to
``.gates/last-run/failures/*.txt`` — which CI never uploads. The
pipeline log therefore carried a single ``[mtest] FAIL: … 5 failed …``
line and no way to learn WHICH tests failed or why (this is why
mgf-hrb's main sat red for weeks).

Pins here:
  * every failing node-id reaches stdout — even past the detail cap;
  * the captured detail (assertion / traceback tail) reaches stdout;
  * bounds: detail expanded for the first ``PRINT_FAILURES_MAX_TESTS``
    failures, tail-truncated to ``PRINT_FAILURES_TAIL_LINES`` lines;
  * mode selection: ``never`` suppresses, ``always`` forces, ``auto``
    prints in CI (``CI=`` truthy / non-TTY stdout) and only there;
  * the printed bundle pointer names the REAL gates dir (an
    ``MGF_GATES_DIR`` run used to be told to look in ``.gates/``).
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from mgf.test_supervisor.records import TestRecord, now_iso
from mgf.test_supervisor.supervisor import (
    PRINT_FAILURES_MAX_TESTS,
    PRINT_FAILURES_TAIL_LINES,
    RunConfig,
    Supervisor,
    _ci_env_truthy,
    _parse_bundle_sections,
)

if TYPE_CHECKING:
    from pathlib import Path


def _sup(tmp_path: Path, *, mode: str = "always", gates: Path | None = None) -> Supervisor:
    cfg = RunConfig(
        repo_root=tmp_path,
        gates_dir=gates or (tmp_path / ".gates"),
        test_ids=[],
        test_markers={},
        run_id="print-run",
        pytest_cmd=["true"],
        print_failures=mode,
    )
    return Supervisor(cfg)


def _rec(test_id: str, detail: str, *, outcome: str = "failed") -> TestRecord:
    return TestRecord(
        test_id=test_id,
        outcome=outcome,
        severity="FAIL",
        duration_ms=1,
        tier=1,
        markers=(),
        code="AssertionError",
        summary="assert 1 == 2",
        detail=detail,
        captured_stdout="",
        captured_stderr="",
        perf=None,
        run_id="print-run",
        ts_iso=now_iso(),
    )


def _seed(sup: Supervisor, recs: list[TestRecord]) -> None:
    for rec in recs:
        sup._outcomes[rec.test_id] = rec.outcome
        sup._failure_records[rec.test_id] = rec


def test_nodeid_and_detail_reach_stdout(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    sup = _sup(tmp_path)
    _seed(sup, [_rec("tests/unit/test_a.py::test_boom", "E   AssertionError: assert 1 == 2")])
    sup._print_failure_details()
    out = capsys.readouterr().out
    assert "tests/unit/test_a.py::test_boom" in out
    assert "E   AssertionError: assert 1 == 2" in out
    assert ".gates/last-run/failures/" in out


def test_green_run_prints_nothing(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
    sup = _sup(tmp_path)
    sup._outcomes["tests/unit/test_a.py::test_ok"] = "passed"
    sup._print_failure_details()
    assert capsys.readouterr().out == ""


def test_never_suppresses_even_with_failures(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    sup = _sup(tmp_path, mode="never")
    _seed(sup, [_rec("tests/unit/test_a.py::test_boom", "E   AssertionError")])
    sup._print_failure_details()
    assert capsys.readouterr().out == ""


def test_auto_mode_prints_in_ci(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setenv("CI", "true")
    sup = _sup(tmp_path, mode="auto")
    _seed(sup, [_rec("tests/unit/test_a.py::test_boom", "E   AssertionError: nope")])
    sup._print_failure_details()
    out = capsys.readouterr().out
    assert "tests/unit/test_a.py::test_boom" in out
    assert "E   AssertionError: nope" in out


@pytest.mark.parametrize(
    ("value", "expected"),
    [
        ("true", True),
        ("1", True),
        ("woodpecker", True),
        ("", False),
        ("false", False),
        ("0", False),
    ],
)
def test_ci_env_truthiness(value: str, expected: bool, monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setenv("CI", value)
    assert _ci_env_truthy() is expected


def test_auto_mode_is_quiet_on_an_interactive_tty(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.delenv("CI", raising=False)
    sup = _sup(tmp_path, mode="auto")

    class _Tty:
        def isatty(self) -> bool:
            return True

    monkeypatch.setattr("mgf.test_supervisor.supervisor.sys.stdout", _Tty())
    assert sup._should_print_failures() is False


def test_detail_is_tail_truncated_but_nodeid_survives(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    detail = "\n".join(f"line-{i}" for i in range(PRINT_FAILURES_TAIL_LINES + 20))
    sup = _sup(tmp_path)
    _seed(sup, [_rec("tests/unit/test_long.py::test_verbose", detail)])
    sup._print_failure_details()
    out = capsys.readouterr().out
    assert "tests/unit/test_long.py::test_verbose" in out
    assert "line-0" not in out  # elided head
    assert f"line-{PRINT_FAILURES_TAIL_LINES + 19}" in out  # kept tail
    assert "elided" in out


def test_every_nodeid_prints_past_the_detail_cap(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    n = PRINT_FAILURES_MAX_TESTS + 5
    recs = [_rec(f"tests/unit/test_m.py::test_{i:03d}", f"E   boom {i}") for i in range(n)]
    sup = _sup(tmp_path)
    _seed(sup, recs)
    sup._print_failure_details()
    out = capsys.readouterr().out
    for rec in recs:  # NEVER hide a node-id
        assert rec.test_id in out
    assert "E   boom 0" in out  # expanded (within cap)
    assert f"E   boom {n - 1}" not in out  # past cap → pointer only
    assert "detail withheld" in out


def test_bundle_pointer_names_the_real_gates_dir(
    tmp_path: Path, capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch
) -> None:
    """MGF_GATES_DIR runs used to be pointed at a `.gates/` that does not exist."""
    monkeypatch.chdir(tmp_path)
    sup = _sup(tmp_path, gates=tmp_path / "ci-gates")
    _seed(sup, [_rec("tests/unit/test_a.py::test_boom", "E   AssertionError")])
    sup._print_failure_details()
    out = capsys.readouterr().out
    assert "ci-gates/last-run/failures/tests_unit_test_a.py__test_boom.txt" in out
    assert ".gates/last-run" not in out


def test_detail_falls_back_to_the_bundle_on_disk(
    tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    """A failure inherited from an interrupted run (--resume) has no
    in-memory record — its detail is read back out of the bundle."""
    sup = _sup(tmp_path)
    tid = "tests/unit/test_prior.py::test_boom"
    bundle = (
        tmp_path / ".gates" / "last-run" / "failures" / "tests_unit_test_prior.py__test_boom.txt"
    )
    bundle.write_text(
        "=== TEST ===\n"
        f"{tid}\n\n"
        "=== OUTCOME ===\n"
        "failed (AssertionError)\n"
        "summary: prior-run summary\n\n"
        "=== TRACEBACK ===\n"
        "E   AssertionError: prior-run boom\n\n"
        "=== STDOUT ===\n"
        "(empty)\n",
        encoding="utf-8",
    )
    sup._outcomes[tid] = "failed"  # seeded from state.json on --resume
    sup._print_failure_details()
    out = capsys.readouterr().out
    assert tid in out
    assert "prior-run summary" in out
    assert "E   AssertionError: prior-run boom" in out


def test_parse_bundle_sections_ignores_lookalike_headers_in_captured_output() -> None:
    text = (
        "=== TEST ===\ntests/x.py::t\n\n"
        "=== TRACEBACK ===\nE   AssertionError: boom\n\n"
        "=== STDOUT ===\n=== not a section ===\nstill stdout\n"
    )
    sections = _parse_bundle_sections(text)
    assert sections["TRACEBACK"] == "E   AssertionError: boom"
    assert sections["STDOUT"] == "=== not a section ===\nstill stdout"
