"""Alignment contract: mgf.test_supervisor.records ↔ mgf.common.testing.

Per TS-23 the supervisor (this library) MUST be stdlib-only — so
it cannot import from `mgf.common.testing` at runtime. mgf-common
is one of the consumers under test; the supervisor must run even
when mgf-common is broken. The two surfaces therefore carry
parallel copies of the JSONL schema:

  - `mgf.common.testing.TestRecord` / `SCHEMA_VERSION` /
    `MARKER_TIER` / `TIERS` / `SECTION_ORDER` — the importable
    library shape consumers use to read `run.jsonl` and failure
    bundles.
  - `mgf.test_supervisor.records` / `mgf.test_supervisor.tiers` —
    the supervisor's own stdlib-only copies (SECTION_ORDER is
    VENDORED in records.py per the M2 Wave-2 TS-23 decision:
    import would violate the zero-dep law, so the byte-pin lives
    here instead).

These tests assert the two stay aligned. If a field is renamed in
one without the other, this file's tests fail and the mismatch
surfaces immediately — where it can be fixed in one edit.

(The alternative — having the supervisor import from
`mgf.common.testing` — would violate TS-23: a broken mgf-common
runtime would break the test runner that's trying to diagnose
why mgf-common is broken. The duplication is the lesser cost; the
contract test is the alignment guarantee.)

`mgf-common` is declared as a DEV dependency (not a runtime dep)
so the alignment can be checked in CI without coupling the
runtime install footprint.

Release-dance guard: when the installed mgf-common PRE-DATES the
M2 Wave-2 aligned surface (its MARKER_TIER still maps the removed
`slow` marker), the whole module skips loudly instead of failing —
CI stays green through the lockstep window and the tests arm
automatically once the resolver picks up the Wave-2 mgf-common
release (the dev-dep floor allows it). The same guard exists on
mgf-common's side for a pre-Wave-2 supervisor.
"""

from __future__ import annotations

import dataclasses

import pytest

from mgf.test_supervisor import records as sup_records
from mgf.test_supervisor import tiers as sup_tiers

try:
    from mgf.common import testing as public
except ImportError:  # pragma: no cover
    pytest.skip("mgf-common not installed; alignment tests skipped", allow_module_level=True)

if "slow" in public.MARKER_TIER:  # pragma: no cover - release-dance window only
    pytest.skip(
        "installed mgf-common pre-dates the M2 Wave-2 aligned surface "
        "(its MARKER_TIER still maps the removed `slow` marker); the "
        "alignment suite arms once mgf-common >= 0.50 is installed",
        allow_module_level=True,
    )


class TestSchemaVersionAligned:
    def test_schema_versions_match(self) -> None:
        assert public.SCHEMA_VERSION == sup_records.SCHEMA_VERSION, (
            f"Schema-version drift: public {public.SCHEMA_VERSION!r} "
            f"vs supervisor {sup_records.SCHEMA_VERSION!r}. Bump both "
            f"in the same commit."
        )


class TestMarkerTierAligned:
    def test_marker_tier_mappings_match(self) -> None:
        public_mapping = dict(public.MARKER_TIER)
        sup_mapping = dict(sup_tiers.MARKER_TIER)
        assert public_mapping == sup_mapping, (
            f"MARKER_TIER drift:\n"
            f"  public:     {public_mapping}\n"
            f"  supervisor: {sup_mapping}\n"
            f"  diff (public - supervisor): "
            f"{set(public_mapping.items()) - set(sup_mapping.items())}\n"
            f"  diff (supervisor - public): "
            f"{set(sup_mapping.items()) - set(public_mapping.items())}"
        )

    def test_slow_removed_on_both_sides(self) -> None:
        # M2 Wave-2: the deprecated `slow` marker completed removal.
        assert "slow" not in sup_tiers.MARKER_TIER
        assert "slow" not in public.MARKER_TIER

    def test_default_tier_matches(self) -> None:
        assert public.DEFAULT_TIER == sup_tiers.DEFAULT_TIER


class TestTierBudgetsAligned:
    """M2 Wave-2 unified TierBudget to ONE dataclass shape: the
    supervisor adopted mgf-common's field names. Field NAMES and
    VALUES are both pinned now (pre-Wave-2 only values were —
    the NamedTuple/dataclass double-shape was the drift)."""

    def test_field_names_match(self) -> None:
        public_fields = [f.name for f in dataclasses.fields(public.TierBudget)]
        sup_fields = [f.name for f in dataclasses.fields(sup_tiers.TierBudget)]
        assert public_fields == sup_fields, (
            f"TierBudget field drift: public {public_fields} vs supervisor {sup_fields}"
        )

    def test_tier_rows_match_field_by_field(self) -> None:
        assert len(public.TIERS) == len(sup_tiers.TIERS) == 4
        for pub, sup in zip(public.TIERS, sup_tiers.TIERS, strict=True):
            assert pub.name == sup.name
            assert pub.per_test_seconds == sup.per_test_seconds
            assert pub.suite_seconds == sup.suite_seconds
            assert pub.description == sup.description


class TestSeverityMappingAligned:
    def test_severity_sets_match(self) -> None:
        assert {s.value for s in public.Severity} == set(sup_records.SEVERITIES)

    def test_outcome_sets_match(self) -> None:
        assert {o.value for o in public.Outcome} == set(sup_records.OUTCOMES)

    def test_default_severity_mapping_matches(self) -> None:
        # The MAPPING, not just the sets (M2 Wave-2: records.py said
        # quarantined→SKIP while the public schema said WARN — the
        # class of drift this test makes impossible).
        for outcome in sorted(sup_records.OUTCOMES):
            sup_sev = sup_records.severity_for_outcome(outcome)
            pub_sev = public.DEFAULT_SEVERITY[public.Outcome(outcome)].value
            assert sup_sev == pub_sev, (
                f"severity({outcome!r}) drift: supervisor {sup_sev!r} vs public {pub_sev!r}"
            )

    def test_quarantined_is_warn(self) -> None:
        # Pinned explicitly: quarantined = excluded-by-flake-state,
        # which needs ATTENTION (WARN) — it is not a by-design skip.
        assert sup_records.severity_for_outcome("quarantined") == "WARN"


class TestFailureBundleSectionsAligned:
    def test_section_order_byte_pinned(self) -> None:
        # The supervisor VENDORS SECTION_ORDER (TS-23 forbids the
        # runtime import); this byte-pin is the alignment guarantee
        # that keeps the writer's headers greppable by consumers of
        # mgf.common.testing.SECTION_ORDER.
        assert tuple(sup_records.SECTION_ORDER) == tuple(public.SECTION_ORDER)

    def test_section_header_fmt_byte_pinned(self) -> None:
        assert sup_records.SECTION_HEADER_FMT == public.SECTION_HEADER_FMT


class TestTierForMarkersMatchesEffectiveTier:
    """tier_for_markers gained the override/mapping args in M2
    Wave-2 and MUST behave exactly like the public effective_tier."""

    @pytest.mark.parametrize(
        "markers",
        [
            (),
            ("smoke",),
            ("contract", "perf"),
            ("integration",),
            ("unknown_project_marker",),
            ("smoke", "e2e"),
        ],
    )
    def test_same_result_no_override(self, markers: tuple[str, ...]) -> None:
        assert sup_tiers.tier_for_markers(markers) == public.effective_tier(list(markers))

    @pytest.mark.parametrize("override", [0, 1, 2, 3])
    def test_same_result_with_override(self, override: int) -> None:
        assert sup_tiers.tier_for_markers(("e2e",), override=override) == public.effective_tier(
            ["e2e"], override=override
        )

    @pytest.mark.parametrize("bad", [-1, 4, 99])
    def test_invalid_override_raises_on_both_sides(self, bad: int) -> None:
        with pytest.raises(ValueError, match=r"0\.\.3"):
            sup_tiers.tier_for_markers(("smoke",), override=bad)
        with pytest.raises(ValueError, match=r"0\.\.3"):
            public.effective_tier(["smoke"], override=bad)

    def test_project_local_mapping(self) -> None:
        custom = {**dict(public.MARKER_TIER), "my_marker": 2}
        assert sup_tiers.tier_for_markers(("my_marker",), mapping=custom) == 2
        assert public.effective_tier(["my_marker"], mapping=custom) == 2


class TestRecordFieldsAligned:
    def test_field_names_match(self) -> None:
        public_fields = {f.name for f in dataclasses.fields(public.TestRecord)}
        sup_fields = {f.name for f in dataclasses.fields(sup_records.TestRecord)}
        assert public_fields == sup_fields, (
            f"TestRecord field drift:\n"
            f"  public-only:    {public_fields - sup_fields}\n"
            f"  supervisor-only: {sup_fields - public_fields}"
        )

    def test_jsonl_round_trip_through_public_reader(self) -> None:
        sup_rec = sup_records.TestRecord(
            test_id="tests/unit/foo.py::test_bar",
            outcome="passed",
            severity="OK",
            duration_ms=12,
            tier=1,
            markers=("contract",),
            run_id="run-id",
            ts_iso="2026-05-26T01:11:37.000Z",
        )
        line = sup_rec.to_json()
        public_rec = public.TestRecord.from_jsonl_line(line)
        assert public_rec.test_id == sup_rec.test_id
        assert public_rec.outcome is public.Outcome.PASSED
        assert public_rec.tier == sup_rec.tier
        assert public_rec.markers == list(sup_rec.markers)

    def test_quarantined_record_round_trips_as_warn(self) -> None:
        # The quarantine-honesty record (M2 Wave-2): outcome
        # `quarantined`, severity WARN, parseable by the public reader.
        sup_rec = sup_records.TestRecord(
            test_id="tests/unit/foo.py::test_flaky",
            outcome="quarantined",
            severity=sup_records.severity_for_outcome("quarantined"),
            duration_ms=0,
            tier=1,
            code="QUARANTINED",
            run_id="run-id",
            ts_iso="2026-07-04T00:00:00.000Z",
        )
        public_rec = public.TestRecord.from_jsonl_line(sup_rec.to_json())
        assert public_rec.outcome is public.Outcome.QUARANTINED
        assert public_rec.severity is public.Severity.WARN
