"""PAPER-01 / PAPER-07 — deselection round-trip regression test.

PAPER-01 (filed by mgf-vm): a fully green pytest run was reported as
thousands of failures — every "failure" carried ``chunk exited with 0``
+ ``MISSING_FROM_XML``. Its prime suspect #1 was deselection
accounting: a project default ``addopts = -m "not <marker>"`` deselects
tests INSIDE a chunk's pytest subprocess while the supervisor still
expects them, so they come back missing from the chunk's JUnit XML and
get scored as failures. The fix blanks the project addopts in the chunk
runner (``-o addopts=`` in ``Supervisor._run_chunk``) and in the
collect-only paths, so the supervisor's expected set and the chunk's
executed set always agree.

This is the round-trip self-test PAPER-01 itself asked for: a known
suite whose project config deselects a marker, run CHUNKED through the
supervisor, asserting mtest's verdict agrees with raw pytest's exactly
— every collected test gets a real XML-backed outcome, zero
``MISSING_FROM_XML``. If the ``addopts=`` blank ever regresses, the
marker-excluded test is deselected inside its chunk and this goes red.

Suspect #2 (node-id ↔ JUnit ``classname``/``name`` keying) is pinned
separately by tests/unit/test_junit_reconciliation.py; the class-based
and parametrised tests below exercise it end-to-end as well.
"""

from __future__ import annotations

import subprocess
import sys
from typing import TYPE_CHECKING

import pytest

from mgf.test_supervisor.records import read_jsonl
from mgf.test_supervisor.supervisor import RunConfig, Supervisor, collect_tests

if TYPE_CHECKING:
    from pathlib import Path

_PYPROJECT = """\
[tool.pytest.ini_options]
addopts = ["-m", "not perf"]
markers = ["perf: performance pin (deselected by the project default)"]
"""

_SUITE = '''\
"""Known suite: 5 plain-green tests + 1 perf-marked test the project
default addopts deselects. Covers module-level, parametrised and
class-based node-id shapes (PAPER-01 suspect #2 end-to-end)."""

import pytest


def test_alpha():
    assert True


def test_beta():
    assert True


@pytest.mark.parametrize("v", [1, 2])
def test_param(v):
    assert v > 0


class TestGroup:
    def test_in_class(self):
        assert True


@pytest.mark.perf
def test_perf_pin():
    assert True
'''


def _write_consumer_project(root: Path) -> None:
    (root / "pyproject.toml").write_text(_PYPROJECT, encoding="utf-8")
    tests_dir = root / "tests"
    tests_dir.mkdir()
    (tests_dir / "test_sample.py").write_text(_SUITE, encoding="utf-8")


@pytest.mark.integration
@pytest.mark.regression
@pytest.mark.timeout(120)
def test_deselected_marker_suite_round_trips_under_chunking(tmp_path: Path) -> None:
    """Regression pin for PAPER-01 suspect #1 (fixed by the
    ``-o addopts=`` blank — initial extraction 846c6a2; suspect #2
    fixed by 1fd845c). mtest's chunked verdict must agree with raw
    pytest: all 6 collected tests pass, none MISSING_FROM_XML."""
    _write_consumer_project(tmp_path)
    pytest_cmd = [sys.executable, "-m", "pytest"]

    # Ground truth: raw pytest, project addopts ACTIVE → 5 passed,
    # 1 deselected (the perf-marked test), rc=0.
    raw = subprocess.run(
        [*pytest_cmd, "-q"],
        cwd=tmp_path,
        capture_output=True,
        text=True,
        timeout=60,
        check=False,
    )
    assert raw.returncode == 0, raw.stdout + raw.stderr
    assert "5 passed" in raw.stdout, raw.stdout
    assert "1 deselected" in raw.stdout, raw.stdout

    # Supervisor collection blanks the project addopts: ALL 6 tests
    # (including the deselected-by-default perf pin) are its expected set.
    collected = collect_tests(pytest_cmd, tmp_path)
    test_ids = [tid for tid, _ in collected]
    assert len(test_ids) == 6, test_ids
    assert "tests/test_sample.py::test_perf_pin" in test_ids

    # Chunked run (chunk_size=2 → 3 chunks, so the perf test shares a
    # chunk with deselectable-by-default siblings).
    cfg = RunConfig(
        repo_root=tmp_path,
        gates_dir=tmp_path / ".gates",
        test_ids=test_ids,
        test_markers={tid: mk for tid, mk in collected if mk},
        run_id="round-trip",
        pytest_cmd=pytest_cmd,
        chunk_size=2,
    )
    rc = Supervisor(cfg).run()
    assert rc == 0

    records = {r["test_id"]: r for r in read_jsonl(tmp_path / ".gates" / "last-run" / "run.jsonl")}
    assert set(records) == set(test_ids)
    # The PAPER-01 failure mode: rc=0 chunks + MISSING_FROM_XML records.
    missing = [tid for tid, r in records.items() if r.get("code") == "MISSING_FROM_XML"]
    assert missing == [], f"supervisor↔XML desync (PAPER-01 regressed): {missing}"
    outcomes = {tid: r["outcome"] for tid, r in records.items()}
    assert outcomes == dict.fromkeys(test_ids, "passed"), outcomes

    # Count agreement with raw pytest: its 5 passed are exactly mtest's
    # passes minus the marker-deselected test mtest runs by design.
    not_perf_passed = sum(
        1
        for tid, o in outcomes.items()
        if o == "passed" and tid != "tests/test_sample.py::test_perf_pin"
    )
    assert not_perf_passed == 5
