"""Tests for `mgf.common.testing._markers` — TS-22 tiered runs."""

from __future__ import annotations

from dataclasses import FrozenInstanceError

import pytest

from mgf.common.testing import (
    DEFAULT_TIER,
    MARKER_TIER,
    TIERS,
    effective_tier,
)


class TestCanonicalMarkerTier:
    @pytest.mark.parametrize(
        "marker,expected_tier",
        [
            # Tier 0 — build-broken signal
            ("smoke", 0),
            # Tier 1 — daily contract
            ("contract", 1),
            ("property", 1),
            ("regression", 1),
            ("fuzz", 1),
            # Tier 2 — promise pins
            ("conformance", 2),
            ("use_case", 2),
            ("concurrency", 2),
            ("integration", 2),  # PAPER-62 (v0.41.6+)
            # Tier 3 — long signal
            ("perf", 3),
            ("stress", 3),
            ("e2e", 3),
        ],
    )
    def test_canonical_mapping_pinned(self, marker: str, expected_tier: int) -> None:
        # If this fails the federation tier contract has shifted —
        # update consumers that rely on `mtest --tier N` semantics
        # before bumping the mapping.
        assert MARKER_TIER[marker] == expected_tier

    def test_mapping_is_exactly_the_canonical_twelve(self) -> None:
        # The taxonomy is twelve markers + the explicit tier override
        # (M2 Wave-2 tier sitting). Pinning the exact key set stops
        # zombie markers from creeping back in unnoticed.
        assert set(MARKER_TIER) == {
            "smoke",
            "contract",
            "property",
            "regression",
            "fuzz",
            "conformance",
            "use_case",
            "concurrency",
            "integration",
            "perf",
            "stress",
            "e2e",
        }

    def test_slow_removed_from_mapping(self) -> None:
        # `slow` (legacy TS-18) completed its deprecation cycle in
        # M2 Wave-2 (2026-07): a test is slow BECAUSE of what it does
        # (perf / stress / e2e), or auto-tier promotes it from
        # observed durations. The marker must not return.
        assert "slow" not in MARKER_TIER

    def test_functional_was_never_a_marker(self) -> None:
        # TESTING.md once listed a phantom `functional` marker that no
        # code or consumer ever registered (H1 recon, M2 Wave-1). It
        # was struck from the doc; pin that it never enters the code.
        assert "functional" not in MARKER_TIER

    def test_all_mapped_tiers_in_0_to_3_range(self) -> None:
        for marker, tier in MARKER_TIER.items():
            assert 0 <= tier <= 3, f"{marker!r} → {tier} out of range"

    def test_default_tier_for_unmarked_is_1(self) -> None:
        assert DEFAULT_TIER == 1


class TestTierBudgets:
    def test_exactly_four_tiers(self) -> None:
        assert len(TIERS) == 4

    def test_tier_indices_in_order(self) -> None:
        for i, tier in enumerate(TIERS):
            assert tier.name == f"tier-{i}"

    def test_per_test_budgets_increase_monotonically(self) -> None:
        per_test = [t.per_test_seconds for t in TIERS]
        assert per_test == sorted(per_test), f"non-monotonic per_test: {per_test}"

    def test_suite_budgets_increase_monotonically(self) -> None:
        suite = [t.suite_seconds for t in TIERS]
        assert suite == sorted(suite), f"non-monotonic suite: {suite}"

    def test_tier_budget_dataclass_frozen(self) -> None:
        budget = TIERS[0]
        with pytest.raises(FrozenInstanceError):
            budget.per_test_seconds = 99.0  # type: ignore[misc]


class TestEffectiveTier:
    def test_unmarked_test_is_default_tier(self) -> None:
        assert effective_tier([]) == DEFAULT_TIER

    def test_single_marker_inherits_its_tier(self) -> None:
        assert effective_tier(["smoke"]) == 0
        assert effective_tier(["contract"]) == 1
        assert effective_tier(["e2e"]) == 3

    def test_max_marker_wins(self) -> None:
        # A test tagged BOTH `smoke` (tier-0) AND `e2e` (tier-3) is
        # tier-3 — the slower marker wins so it doesn't sneak into a
        # tier-0 budget run.
        assert effective_tier(["smoke", "e2e"]) == 3
        assert effective_tier(["contract", "perf"]) == 3
        assert effective_tier(["regression", "conformance"]) == 2

    def test_unknown_markers_are_ignored(self) -> None:
        # A project-specific marker that isn't in MARKER_TIER doesn't
        # break selection — just doesn't contribute to the tier.
        assert effective_tier(["xfail_known", "smoke"]) == 0
        assert effective_tier(["xfail_known"]) == DEFAULT_TIER

    def test_explicit_override_wins(self) -> None:
        # @pytest.mark.tier(N) takes precedence over marker inheritance.
        assert effective_tier(["e2e"], override=0) == 0
        assert effective_tier(["smoke"], override=3) == 3
        assert effective_tier([], override=2) == 2

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

    def test_project_local_mapping_override(self) -> None:
        # A consumer can pass its own marker→tier mapping (e.g., to add
        # a project-specific marker without forking the federation
        # default).
        custom = {**MARKER_TIER, "my_project_marker": 2}
        assert effective_tier(["my_project_marker"], mapping=custom) == 2
        # Without the mapping the marker is unknown → DEFAULT_TIER.
        assert effective_tier(["my_project_marker"]) == DEFAULT_TIER

    def test_tuple_markers_also_work(self) -> None:
        # pytest sometimes hands a tuple — accept both.
        assert effective_tier(("smoke", "regression")) == 1
