"""tests/regression/test_p0_6_fetch_race.py — task-2471 회귀 테스트.

토르가 commit 2be9a181 에서 ``scripts/lifecycle_guards.py`` 의 P0-6 SHA fetch
race 결함 수정한 동작을 영구 차단한다.

- ``_safe_git_fetch(base_ref, cwd) -> bool`` 시그니처
- ``_rev_parse_origin(base_ref, cwd) -> str | None`` 시그니처
- ``fetch_origin_head_sha(base_ref, *, cwd=None, force_fetch=True)`` 시그니처
  (``force_fetch`` 파라미터 존재 + 기본 True)
- ``check_merge_commit_sha`` 시그니처 보존 (기존 호출자 호환)
- subprocess 호출 mock 으로 정상 동작 검증

헤임달(개발2팀 테스터) 작성.
"""
from __future__ import annotations

import importlib.util
import inspect
import sys
from pathlib import Path

import pytest

WORKSPACE = Path(__file__).resolve().parents[2]
SCRIPTS_DIR = WORKSPACE / "scripts"


@pytest.fixture(scope="module")
def lg():
    """``scripts/lifecycle_guards.py`` 절대 경로 로드."""
    file_path = SCRIPTS_DIR / "lifecycle_guards.py"
    assert file_path.exists()
    # scripts/ 를 sys.path 에 추가 (lifecycle_guards 가 같은 디렉토리의
    # 다른 모듈 import 시 필요).
    if str(SCRIPTS_DIR) not in sys.path:
        sys.path.insert(0, str(SCRIPTS_DIR))
    if str(WORKSPACE) not in sys.path:
        sys.path.insert(0, str(WORKSPACE))
    spec = importlib.util.spec_from_file_location(
        "lifecycle_guards_p06_test_alias", str(file_path)
    )
    assert spec is not None and spec.loader is not None
    mod = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = mod
    spec.loader.exec_module(mod)
    return mod


# ---------------------------------------------------------------------------
# 시그니처 검증
# ---------------------------------------------------------------------------


def test_safe_git_fetch_signature(lg):
    """``_safe_git_fetch(base_ref, cwd) -> bool``."""
    assert hasattr(lg, "_safe_git_fetch")
    sig = inspect.signature(lg._safe_git_fetch)
    params = list(sig.parameters)
    assert "base_ref" in params
    assert "cwd" in params


def test_rev_parse_origin_signature(lg):
    """``_rev_parse_origin(base_ref, cwd) -> str | None``."""
    assert hasattr(lg, "_rev_parse_origin")
    sig = inspect.signature(lg._rev_parse_origin)
    params = list(sig.parameters)
    assert "base_ref" in params
    assert "cwd" in params


def test_fetch_origin_head_sha_has_force_fetch_param(lg):
    """``fetch_origin_head_sha`` 의 시그니처에 ``force_fetch`` 파라미터 존재."""
    assert hasattr(lg, "fetch_origin_head_sha")
    sig = inspect.signature(lg.fetch_origin_head_sha)
    params = sig.parameters

    assert "base_ref" in params
    assert "cwd" in params
    assert "force_fetch" in params, (
        f"force_fetch 파라미터 없음 (P0-6 race fix 누락 의심): {list(params)}"
    )
    # 기본값 True
    assert params["force_fetch"].default is True


def test_check_merge_commit_sha_signature_preserved(lg):
    """``check_merge_commit_sha`` 시그니처 보존 (기존 호출자 호환).

    토르가 P0-6 race-fix 진행하면서 호출자 호환성을 깨면 안 됨.
    """
    assert hasattr(lg, "check_merge_commit_sha")
    sig = inspect.signature(lg.check_merge_commit_sha)
    params = sig.parameters

    assert "pr_number" in params
    assert "repo" in params
    assert "base_branch" in params
    assert "cwd" in params


# ---------------------------------------------------------------------------
# fetch_origin_head_sha 동작: force_fetch=False 에서도 호출 가능
# ---------------------------------------------------------------------------


def test_fetch_origin_head_sha_force_fetch_false_no_fetch_call(lg, monkeypatch):
    """``force_fetch=False`` 시 ``_safe_git_fetch`` 가 호출되지 않아야 함."""
    fetch_called = {"flag": False}

    def _fake_fetch(_base, _cwd):
        del _base, _cwd
        fetch_called["flag"] = True
        return True

    def _fake_rev_parse(_base, _cwd):
        del _base, _cwd
        return "stable_sha_aaaaaa"

    monkeypatch.setattr(lg, "_safe_git_fetch", _fake_fetch)
    monkeypatch.setattr(lg, "_rev_parse_origin", _fake_rev_parse)
    # sleep 단축
    import time as _t
    monkeypatch.setattr(_t, "sleep", lambda _: None)

    sha = lg.fetch_origin_head_sha("main", force_fetch=False)
    assert sha == "stable_sha_aaaaaa"
    assert fetch_called["flag"] is False, "force_fetch=False 였는데 fetch 호출됨"


def test_fetch_origin_head_sha_force_fetch_true_calls_fetch(lg, monkeypatch):
    """``force_fetch=True`` (기본) 시 ``_safe_git_fetch`` 호출."""
    fetch_count = {"n": 0}

    def _fake_fetch(_base, _cwd):
        del _base, _cwd
        fetch_count["n"] += 1
        return True

    def _fake_rev_parse(_base, _cwd):
        del _base, _cwd
        return "consistent_sha"

    monkeypatch.setattr(lg, "_safe_git_fetch", _fake_fetch)
    monkeypatch.setattr(lg, "_rev_parse_origin", _fake_rev_parse)
    import time as _t
    monkeypatch.setattr(_t, "sleep", lambda _: None)

    sha = lg.fetch_origin_head_sha("main", force_fetch=True)
    assert sha == "consistent_sha"
    assert fetch_count["n"] >= 1


def test_fetch_origin_head_sha_unstable_returns_none(lg, monkeypatch):
    """SHA 가 2회 fetch 모두에서 흔들리면 None (race detected)."""
    seq = iter([
        "sha_a",  # 1차
        "sha_b",  # 2차 (불일치 → 재시도)
        "sha_c",  # 재시도 1차
        "sha_d",  # 재시도 2차 (또 불일치)
    ])

    def _fake_rev_parse(_base, _cwd):
        del _base, _cwd
        return next(seq, "sha_z")

    def _fetch_ok(_b, _c):
        del _b, _c
        return True

    monkeypatch.setattr(lg, "_safe_git_fetch", _fetch_ok)
    monkeypatch.setattr(lg, "_rev_parse_origin", _fake_rev_parse)
    import time as _t
    monkeypatch.setattr(_t, "sleep", lambda _: None)

    sha = lg.fetch_origin_head_sha("main", force_fetch=True)
    assert sha is None


def test_fetch_origin_head_sha_consistent_after_retry(lg, monkeypatch):
    """1차/2차 불일치 후 재 fetch + 재 조회에서 일치하면 그 값 반환."""
    seq = iter([
        "x", "y",  # 1차 / 2차 (불일치)
        "z", "z",  # 재시도 (일치)
    ])

    def _fake_rev_parse(_base, _cwd):
        del _base, _cwd
        return next(seq, None)

    def _fetch_ok(_b, _c):
        del _b, _c
        return True

    monkeypatch.setattr(lg, "_safe_git_fetch", _fetch_ok)
    monkeypatch.setattr(lg, "_rev_parse_origin", _fake_rev_parse)
    import time as _t
    monkeypatch.setattr(_t, "sleep", lambda _: None)

    sha = lg.fetch_origin_head_sha("main", force_fetch=True)
    assert sha == "z"


def test_fetch_origin_head_sha_rev_parse_failure(lg, monkeypatch):
    """``_rev_parse_origin`` 가 None 반환하면 결과도 None."""
    def _fetch_ok(_b, _c):
        del _b, _c
        return True

    def _rev_none(_b, _c):
        del _b, _c
        return None

    monkeypatch.setattr(lg, "_safe_git_fetch", _fetch_ok)
    monkeypatch.setattr(lg, "_rev_parse_origin", _rev_none)
    import time as _t
    monkeypatch.setattr(_t, "sleep", lambda _: None)

    sha = lg.fetch_origin_head_sha("main", force_fetch=False)
    assert sha is None


# ---------------------------------------------------------------------------
# check_merge_commit_sha boundary
# ---------------------------------------------------------------------------


def test_check_merge_commit_sha_missing_args_fails(lg):
    """``pr_number=None`` / ``repo=None`` 이면 즉시 fail-closed."""
    result = lg.check_merge_commit_sha(None, repo=None)
    assert result.ok is False
    assert "필요" in result.reason or "required" in result.reason.lower() or "P0-6" in result.reason
