"""Tests for the local-release runner."""

from __future__ import annotations

import json
import zipfile
from pathlib import Path

import pytest

from mgf.fed import _release
from mgf.fed._release import (
    ReleaseResult,
    _validate_wheel,
    _version,
    release_local,
    release_remote,
)


def test_version_reads_pyproject(tmp_path: Path) -> None:
    (tmp_path / "pyproject.toml").write_text('[project]\nname = "x"\nversion = "1.2.3"\n')
    assert _version(tmp_path) == "1.2.3"


def test_validate_wheel(tmp_path: Path) -> None:
    good = tmp_path / "mgf_x-1.2.3-py3-none-any.whl"
    with zipfile.ZipFile(good, "w") as z:
        z.writestr("mgf/x/py.typed", "")
    assert _validate_wheel(good, "1.2.3") == (True, "")
    ok, detail = _validate_wheel(good, "9.9.9")
    assert not ok and "version" in detail

    no_typed = tmp_path / "mgf_y-1.0.0-py3-none-any.whl"
    with zipfile.ZipFile(no_typed, "w") as z:
        z.writestr("mgf/y/__init__.py", "")
    ok, detail = _validate_wheel(no_typed, "1.0.0")
    assert not ok and "py.typed" in detail


def _root(tmp_path: Path) -> Path:
    reg = {
        "release_waves": [["mgf-a"], ["mgf-b"]],
        "libraries": [
            {"repo": "mgf-a", "lang": "python", "version": "1.0.0"},
            {"repo": "mgf-b", "lang": "python", "version": "2.0.0"},
        ],
    }
    (tmp_path / "mgf-standard").mkdir()
    (tmp_path / "mgf-standard" / "MGF_LIBRARIES.json").write_text(json.dumps(reg), encoding="utf-8")
    for r in ("mgf-a", "mgf-b"):
        (tmp_path / r).mkdir()
    return tmp_path


def test_release_local_runs_in_wave_order(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    monkeypatch.setattr(_release.shutil, "which", lambda _n: "/usr/bin/uv")
    monkeypatch.setattr(_release, "_find_ruff", lambda: None)
    seen: list[str] = []

    def _fake_one(m: object, uv: str, ruff: str | None) -> ReleaseResult:
        repo = m.repo  # type: ignore[attr-defined]
        seen.append(repo)
        return ReleaseResult(repo, "1.0.0", "publish", True, "ok")

    monkeypatch.setattr(_release, "_release_one", _fake_one)
    rc = release_local(["mgf-b", "mgf-a"], root=_root(tmp_path))  # arg order reversed
    assert rc == 0
    assert seen == ["mgf-a", "mgf-b"]  # processed in WAVE order, not arg order
    assert "2/2 published" in capsys.readouterr().out


def test_release_local_no_uv(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(_release.shutil, "which", lambda _n: None)
    assert release_local(["mgf-a"]) == 2


def test_release_remote_dry_run(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    monkeypatch.setattr(_release.shutil, "which", lambda _n: "/usr/bin/uv")
    monkeypatch.setattr(_release, "_local_wheel", lambda repo, ver: tmp_path / f"{repo}.whl")
    monkeypatch.setattr(_release, "_git", lambda args, cwd: (True, ""))
    monkeypatch.setattr(_release, "_codeberg_has", lambda pkg, ver, tok: False)
    rc = release_remote(["mgf-a"], dry_run=True, root=_root(tmp_path))
    assert rc == 0 and "would tag" in capsys.readouterr().out


def test_release_remote_already_published_is_noop(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    monkeypatch.setattr(_release.shutil, "which", lambda _n: "/usr/bin/uv")
    monkeypatch.setattr(_release, "_local_wheel", lambda repo, ver: tmp_path / f"{repo}.whl")
    monkeypatch.setattr(_release, "_git", lambda args, cwd: (True, ""))
    monkeypatch.setattr(_release, "_codeberg_has", lambda pkg, ver, tok: True)
    monkeypatch.setenv("MGF_CODEBERG_TOKEN", "tok")
    rc = release_remote(["mgf-a"], root=_root(tmp_path))
    assert rc == 0 and "already published" in capsys.readouterr().out


def test_release_remote_requires_local_release_first(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
    monkeypatch.setattr(_release.shutil, "which", lambda _n: "/usr/bin/uv")
    monkeypatch.setattr(_release, "_local_wheel", lambda repo, ver: None)
    monkeypatch.setenv("MGF_CODEBERG_TOKEN", "tok")
    rc = release_remote(["mgf-a"], root=_root(tmp_path))
    assert rc == 1 and "run `release-local" in capsys.readouterr().out


def test_release_remote_no_token_no_dry_run(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(_release.shutil, "which", lambda _n: "/usr/bin/uv")
    monkeypatch.delenv("MGF_CODEBERG_TOKEN", raising=False)
    monkeypatch.delenv("CODEBERG_TOKEN", raising=False)
    assert release_remote(["mgf-a"], dry_run=False, root=_root(tmp_path)) == 2


def _member(tmp_path: Path, repo: str = "mgf-x", version: str = "1.0.0"):
    from mgf.fed._roster import Member

    d = tmp_path / repo
    d.mkdir()
    (d / "pyproject.toml").write_text(f'[project]\nname = "{repo}"\nversion = "{version}"\n')
    return Member(repo=repo, lang="python", registry_version=version, root=tmp_path)


def test_release_one_publishes(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    m = _member(tmp_path)
    monkeypatch.setattr(_release, "_run", lambda *a, **k: (True, ""))
    monkeypatch.setattr(
        _release, "_newest_wheel", lambda d, v: tmp_path / "mgf_x-1.0.0-py3-none-any.whl"
    )
    monkeypatch.setattr(_release, "_validate_wheel", lambda w, v: (True, ""))
    monkeypatch.setattr(_release._index, "publish", lambda w: tmp_path / "idx" / Path(w).name)
    res = _release._release_one(m, "/usr/bin/uv", None)  # ruff=None → gate skipped
    assert res.ok and res.reached == "publish"


def test_release_one_build_fail(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    m = _member(tmp_path)
    monkeypatch.setattr(
        _release, "_run", lambda args, cwd=None: (False, "boom") if "build" in args else (True, "")
    )
    res = _release._release_one(m, "/usr/bin/uv", None)
    assert not res.ok and res.reached == "build"


def test_release_remote_one_publishes_and_verifies(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    m = _member(tmp_path)
    monkeypatch.setattr(
        _release, "_local_wheel", lambda r, v: tmp_path / "mgf_x-1.0.0-py3-none-any.whl"
    )
    monkeypatch.setattr(_release, "_git", lambda a, c: (True, ""))
    seen: list[int] = []

    def _has(_p: str, _v: str, _t: str | None) -> bool:
        seen.append(1)
        return len(seen) > 1  # 1st (pre-req) absent → publish; 2nd (verify) present

    monkeypatch.setattr(_release, "_codeberg_has", _has)
    monkeypatch.setattr(_release, "_run", lambda *a, **k: (True, ""))  # uv publish
    res = _release._release_remote_one(m, "tok", "/usr/bin/uv", dry_run=False)
    assert res.ok and res.reached == "publish"


# --- SC-11: the publish token must never reach a log/argv ---------------------
def test_redact_cmd_strips_secret_flag_values() -> None:
    leaked = (
        "Command '['uv', 'publish', '--password', 'SECRET_TOKEN', 'x.whl']' "
        "timed out after 600 seconds"
    )
    out = _release._redact_cmd(leaked)
    assert "SECRET_TOKEN" not in out
    assert "<redacted>" in out


def test_run_does_not_leak_secret_on_subprocess_failure(monkeypatch: pytest.MonkeyPatch) -> None:
    import subprocess

    def _boom(*_a: object, **_k: object) -> None:
        raise subprocess.TimeoutExpired(["uv", "publish", "--password", "SECRET_TOKEN"], 600)

    monkeypatch.setattr(_release.subprocess, "run", _boom)
    ok, detail = _release._run(["uv", "publish", "--password", "SECRET_TOKEN"])
    assert ok is False
    assert "SECRET_TOKEN" not in detail
