"""Unit tests for quality-depth gate helpers."""

from __future__ import annotations

import json
from pathlib import Path
from types import SimpleNamespace

from scripts import check_mutation_score
from scripts import check_mutation_targets
from scripts import run_repeat_gate


def test_mutation_score_accepts_exact_floor_and_rejects_regression():
    stats = {"killed": 70, "total": 100, "check_was_interrupted_by_user": 0}
    assert check_mutation_score.validate_mutation_stats(stats, minimum=70) == []
    assert check_mutation_score.validate_mutation_stats(stats, minimum=70.01) == [
        "mutation score 70.00% (70/100) is below the 70.01% floor",
    ]


def test_mutation_score_validator_default_matches_immutable_policy_floor():
    parser = check_mutation_score._parser()

    assert check_mutation_score.DEFAULT_MUTATION_SCORE_MINIMUM == 85.0
    assert parser.parse_args(["stats.json"]).minimum == 85.0


def test_mutation_score_rejects_malformed_and_interrupted_runs():
    assert check_mutation_score.validate_mutation_stats(
        {"killed": True, "total": 1}, minimum=70,
    ) == ["mutation statistics require a non-negative integer 'killed'"]
    assert check_mutation_score.validate_mutation_stats(
        {"killed": 1, "total": 1, "check_was_interrupted_by_user": 1}, minimum=70,
    ) == ["mutation run was interrupted"]


def test_mutation_target_guard_invalidates_stale_copied_targets(tmp_path):
    pyproject = tmp_path / "pyproject.toml"
    pyproject.write_text(
        """
[tool.mutmut]
only_mutate = [
    "obliteratus/config.py",
    "obliteratus/analysis/whitened_svd.py",
]
required_mutation_targets = [
    "obliteratus/analysis/whitened_svd.py",
]
""".lstrip(),
        encoding="utf-8",
    )
    for target in (
        "obliteratus/config.py",
        "obliteratus/analysis/whitened_svd.py",
    ):
        source = tmp_path / target
        source.parent.mkdir(parents=True, exist_ok=True)
        source.write_text("pass\n", encoding="utf-8")

    valid_meta = tmp_path / "mutants/obliteratus/config.py.meta"
    valid_meta.parent.mkdir(parents=True, exist_ok=True)
    valid_meta.write_text('{"exit_code_by_key": {"obliteratus.config.x__mutmut_1": null}}')

    stale_copy = tmp_path / "mutants/obliteratus/analysis/whitened_svd.py"
    stale_copy.parent.mkdir(parents=True, exist_ok=True)
    stale_copy.write_text("pass\n", encoding="utf-8")

    stale = check_mutation_targets.prepare_required_targets(pyproject)

    assert stale == [Path("obliteratus/analysis/whitened_svd.py")]
    assert not stale_copy.exists()
    assert check_mutation_targets.validate_required_targets(pyproject) == [
        "configured mutation target produced zero mutants: obliteratus/analysis/whitened_svd.py",
    ]


def test_mutation_target_guard_passes_when_each_exact_target_has_mutants(tmp_path):
    pyproject = tmp_path / "pyproject.toml"
    pyproject.write_text(
        """
[tool.mutmut]
only_mutate = [
    "obliteratus/analysis/*.py",
    "obliteratus/analysis/numerical_contracts.py",
]
required_mutation_targets = [
    "obliteratus/analysis/numerical_contracts.py",
]
""".lstrip(),
        encoding="utf-8",
    )
    source = tmp_path / "obliteratus/analysis/numerical_contracts.py"
    source.parent.mkdir(parents=True, exist_ok=True)
    source.write_text("pass\n", encoding="utf-8")
    meta = tmp_path / "mutants/obliteratus/analysis/numerical_contracts.py.meta"
    meta.parent.mkdir(parents=True, exist_ok=True)
    meta.write_text(
        '{"exit_code_by_key": {"obliteratus.analysis.numerical_contracts.x__mutmut_1": null}}',
        encoding="utf-8",
    )

    assert check_mutation_targets.validate_required_targets(pyproject) == []


def test_repeat_orders_are_distinct_and_deterministic():
    paths = ["a", "b", "c", "d"]
    assert run_repeat_gate.test_orders(paths) == [
        ["a", "b", "c", "d"],
        ["d", "c", "b", "a"],
        ["a", "c", "b", "d"],
    ]


def test_repeat_gate_records_each_pass(monkeypatch, tmp_path):
    calls = []

    def fake_run(command, **kwargs):
        calls.append((command, kwargs["env"]["PYTHONHASHSEED"]))
        return SimpleNamespace(returncode=0, stdout="2 passed\n", stderr="")

    monkeypatch.setattr(run_repeat_gate.subprocess, "run", fake_run)
    monkeypatch.setattr(
        run_repeat_gate,
        "junit_snapshot",
        lambda _path: {"tests": 2, "failed_nodeids": [], "skipped_nodeids": []},
    )
    output = tmp_path / "repeat.json"
    assert run_repeat_gate.run_repeat_gate(
        ["first.py", "second.py"], output=output, python="python-fixture",
    ) == 0
    evidence = json.loads(output.read_text())
    assert evidence["status"] == "passed"
    assert [entry["python_hash_seed"] for entry in evidence["passes"]] == [
        "0", "1", "8675309",
    ]
    assert calls[0][0] == [
        "python-fixture",
        "-m",
        "pytest",
        "--no-cov",
        "-q",
        f"--junitxml={tmp_path / 'repeat-pass-1.xml'}",
        "first.py",
        "second.py",
    ]


def test_repeat_gate_completes_all_passes_and_preserves_failure_output(monkeypatch, tmp_path):
    def fake_run(*_args, **_kwargs):
        return SimpleNamespace(returncode=3, stdout="failed output", stderr="failure detail")

    monkeypatch.setattr(run_repeat_gate.subprocess, "run", fake_run)
    monkeypatch.setattr(
        run_repeat_gate,
        "junit_snapshot",
        lambda _path: {
            "tests": 1,
            "failed_nodeids": ["tests.test_example::test_failure"],
            "skipped_nodeids": [],
        },
    )
    output = tmp_path / "repeat.json"
    assert run_repeat_gate.run_repeat_gate(["test.py"], output=output) == 3
    evidence = json.loads(output.read_text())
    assert evidence["status"] == "failed"
    assert len(evidence["passes"]) == 3
    assert evidence["passes"][0]["stderr"] == "failure detail"
    assert evidence["consistent_failures"] == [{
        "nodeid": "tests.test_example::test_failure",
        "occurrences": 3,
    }]


def test_repeat_gate_identifies_intermittent_failures(monkeypatch, tmp_path):
    results = iter([
        SimpleNamespace(returncode=1, stdout="failed", stderr=""),
        SimpleNamespace(returncode=0, stdout="passed", stderr=""),
        SimpleNamespace(returncode=1, stdout="failed", stderr=""),
    ])
    snapshots = iter([
        {"tests": 1, "failed_nodeids": ["tests.test_example::test_flake"], "skipped_nodeids": []},
        {"tests": 1, "failed_nodeids": [], "skipped_nodeids": []},
        {"tests": 1, "failed_nodeids": ["tests.test_example::test_flake"], "skipped_nodeids": []},
    ])
    monkeypatch.setattr(run_repeat_gate.subprocess, "run", lambda *_args, **_kwargs: next(results))
    monkeypatch.setattr(run_repeat_gate, "junit_snapshot", lambda _path: next(snapshots))

    output = tmp_path / "repeat.json"
    assert run_repeat_gate.run_repeat_gate(["test.py"], output=output) == 1
    evidence = json.loads(output.read_text())
    assert evidence["flake_candidates"] == [{
        "nodeid": "tests.test_example::test_flake",
        "occurrences": 2,
    }]
