"""Unit tests for ``mgf-fed ci-heal`` (PAPER-118 — clone-flake auto-restart)."""

from __future__ import annotations

import argparse
from typing import Any

import pytest

from mgf.fed.cli import _ci_heal
from mgf.fed.cli._ci_heal import (
    _already_retried,
    _heal_repo,
    _is_clone_only_failure,
    add_ci_heal_args,
    run_ci_heal,
)


def _detail(*steps: tuple[str, str]) -> dict[str, Any]:
    return {"workflows": [{"children": [{"name": n, "state": s} for n, s in steps]}]}


class TestSignature:
    def test_clone_only_red_matches(self) -> None:
        assert _is_clone_only_failure(_detail(("clone", "failure"), ("lint", "skipped")))

    def test_real_failure_never_matches(self) -> None:
        assert not _is_clone_only_failure(_detail(("clone", "success"), ("test", "failure")))
        # clone AND test red — a human's problem, not the flake
        assert not _is_clone_only_failure(_detail(("clone", "failure"), ("test", "failure")))

    def test_all_green_never_matches(self) -> None:
        assert not _is_clone_only_failure(_detail(("clone", "success"), ("test", "success")))

    def test_older_procs_shape_supported(self) -> None:
        detail = {"procs": [{"children": [{"name": "clone", "state": "failure"}]}]}
        assert _is_clone_only_failure(detail)


class TestOnceBound:
    def test_later_pipeline_same_commit_blocks(self) -> None:
        pipes = [
            {"number": 5, "commit": "abc", "status": "failure"},
            {"number": 6, "commit": "abc", "status": "running"},
        ]
        assert _already_retried(pipes, pipes[0])

    def test_no_later_pipeline_allows(self) -> None:
        pipes = [
            {"number": 5, "commit": "abc", "status": "failure"},
            {"number": 4, "commit": "abc", "status": "failure"},
        ]
        assert not _already_retried(pipes, pipes[0])
        assert _already_retried(pipes, pipes[1])  # #4 has a later sibling (#5)


class _FakeApi:
    """Scripted request_json keyed on (method, url suffix)."""

    def __init__(self, listing: list[dict[str, Any]], details: dict[int, dict[str, Any]]) -> None:
        self.listing = listing
        self.details = details
        self.restarted: list[int] = []

    def __call__(self, url: str, *, method: str = "GET", **kw: Any) -> Any:
        if url.endswith("/api/repos/lookup/magogi-admin/mgf-x"):
            return {"id": 42}
        if "pipelines?perPage=" in url:
            return self.listing
        number = int(url.rsplit("/", 1)[1])
        if method == "POST":
            self.restarted.append(number)
            return {"number": max(p["number"] for p in self.listing) + 1}
        return self.details[number]


def _run(fake: _FakeApi, monkeypatch: pytest.MonkeyPatch, *, dry_run: bool = False) -> list[str]:
    monkeypatch.setattr(_ci_heal, "request_json", fake)
    return _heal_repo("https://wp", "magogi-admin", "mgf-x", "tok", 10, dry_run=dry_run)


def test_heal_restarts_clone_only_red_once(monkeypatch: pytest.MonkeyPatch) -> None:
    fake = _FakeApi(
        listing=[{"number": 5, "commit": "abc", "status": "failure"}],
        details={5: _detail(("clone", "failure"), ("test", "skipped"))},
    )
    lines = _run(fake, monkeypatch)
    assert fake.restarted == [5]
    assert any(line.startswith("HEAL") and "#5" in line for line in lines)


def test_skips_when_already_retried(monkeypatch: pytest.MonkeyPatch) -> None:
    fake = _FakeApi(
        listing=[
            {"number": 6, "commit": "abc", "status": "running"},
            {"number": 5, "commit": "abc", "status": "failure"},
        ],
        details={5: _detail(("clone", "failure"))},
    )
    lines = _run(fake, monkeypatch)
    assert fake.restarted == []  # bounded: one restart per commit, ever
    assert any(line.startswith("SKIP") for line in lines)


def test_ignores_genuine_failures(monkeypatch: pytest.MonkeyPatch) -> None:
    fake = _FakeApi(
        listing=[{"number": 5, "commit": "abc", "status": "failure"}],
        details={5: _detail(("clone", "success"), ("test", "failure"))},
    )
    lines = _run(fake, monkeypatch)
    assert fake.restarted == []
    assert lines == []  # a real red stays a human's problem, silently untouched


def test_dry_run_previews_without_post(monkeypatch: pytest.MonkeyPatch) -> None:
    fake = _FakeApi(
        listing=[{"number": 5, "commit": "abc", "status": "failure"}],
        details={5: _detail(("clone", "failure"))},
    )
    lines = _run(fake, monkeypatch, dry_run=True)
    assert fake.restarted == []
    assert any(line.startswith("WOULD") for line in lines)


def test_run_ci_heal_requires_token(monkeypatch: pytest.MonkeyPatch, capsys: Any) -> None:
    monkeypatch.setattr(_ci_heal, "_token", lambda explicit: None)
    parser = argparse.ArgumentParser()
    add_ci_heal_args(parser)
    args = parser.parse_args(["--repo", "mgf-x"])
    assert run_ci_heal(args) == 2
    assert "no token" in capsys.readouterr().out


def test_run_ci_heal_end_to_end(monkeypatch: pytest.MonkeyPatch, capsys: Any) -> None:
    fake = _FakeApi(
        listing=[{"number": 5, "commit": "abc", "status": "failure"}],
        details={5: _detail(("clone", "failure"))},
    )
    monkeypatch.setattr(_ci_heal, "request_json", fake)
    monkeypatch.setattr(_ci_heal, "_token", lambda explicit: "tok")
    parser = argparse.ArgumentParser()
    add_ci_heal_args(parser)
    args = parser.parse_args(["--repo", "mgf-x"])
    assert run_ci_heal(args) == 0
    out = capsys.readouterr().out
    assert "HEAL" in out and "restarted as #6" in out
