"""tests/state_machine/test_recoverable.py — task-2471 회귀 테스트.

토르가 commit 4795c5e2 에서 추가한 RECOVERABLE_BLOCKED 상태 + 전이 규칙을
영구 차단한다.

- ``RECOVERABLE_BLOCKED`` 가 ``STATES`` tuple 에 포함
- ``ALLOWED_TRANSITIONS["MERGING"]`` 에 ``"RECOVERABLE_BLOCKED"`` 포함
- ``ALLOWED_TRANSITIONS["RECOVERABLE_BLOCKED"]`` 가
  MERGING/FAILED/ESCALATED/PR_OPEN 모두 포함
- subprocess taskctl CLI 호출 없이 import + 정적 검증만 수행

worktree 경로 hardcode 절대 금지 — ``importlib.util.spec_from_file_location``
로 동적 로드.

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

import importlib.util
import sys
from pathlib import Path

import pytest

# Worktree 루트 동적 해소 (tests/state_machine/<file>.py → 2단 위)
WORKSPACE = Path(__file__).resolve().parents[2]
TASKCTL_PATH = WORKSPACE / "scripts" / "taskctl.py"


@pytest.fixture(scope="module")
def taskctl_mod():
    """worktree 의 ``scripts/taskctl.py`` 를 절대 경로로 로드.

    sys.path 조작 없이 importlib 로 직접 로드하여 다른 워크트리의 동명 파일과
    충돌하지 않게 한다.
    """
    assert TASKCTL_PATH.exists(), f"taskctl.py not found: {TASKCTL_PATH}"
    # taskctl 은 같은 디렉토리의 lifecycle_guards 등을 import 하므로
    # scripts/ 를 sys.path 에 잠시 추가.
    scripts_dir = str(WORKSPACE / "scripts")
    added = False
    if scripts_dir not in sys.path:
        sys.path.insert(0, scripts_dir)
        added = True
    try:
        spec = importlib.util.spec_from_file_location(
            "taskctl_recoverable_test_alias", str(TASKCTL_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)
    finally:
        if added:
            try:
                sys.path.remove(scripts_dir)
            except ValueError:
                pass
    return mod


# ---------------------------------------------------------------------------
# STATES enum 검증
# ---------------------------------------------------------------------------


def test_recoverable_blocked_in_states(taskctl_mod):
    """``RECOVERABLE_BLOCKED`` 가 STATES tuple 에 존재."""
    assert "RECOVERABLE_BLOCKED" in taskctl_mod.STATES, (
        f"RECOVERABLE_BLOCKED 미정의: STATES={taskctl_mod.STATES}"
    )


def test_recoverable_blocked_not_terminal(taskctl_mod):
    """``RECOVERABLE_BLOCKED`` 는 terminal 이 아니어야 함 (transient block)."""
    assert "RECOVERABLE_BLOCKED" not in taskctl_mod.TERMINAL_STATES


# ---------------------------------------------------------------------------
# ALLOWED_TRANSITIONS 검증
# ---------------------------------------------------------------------------


def test_merging_can_transition_to_recoverable_blocked(taskctl_mod):
    """MERGING 의 허용 전이에 RECOVERABLE_BLOCKED 포함."""
    merging_targets = taskctl_mod.ALLOWED_TRANSITIONS.get("MERGING", set())
    assert "RECOVERABLE_BLOCKED" in merging_targets, (
        f"MERGING -> RECOVERABLE_BLOCKED 미허용: targets={sorted(merging_targets)}"
    )


def test_merging_targets_set_complete(taskctl_mod):
    """MERGING 의 허용 전이는 {MERGED, FAILED, RECOVERABLE_BLOCKED} 를 포함."""
    merging_targets = taskctl_mod.ALLOWED_TRANSITIONS.get("MERGING", set())
    expected = {"MERGED", "FAILED", "RECOVERABLE_BLOCKED"}
    missing = expected - merging_targets
    assert not missing, f"MERGING 의 누락된 전이: {missing}"


def test_recoverable_blocked_targets(taskctl_mod):
    """RECOVERABLE_BLOCKED 는 {MERGING, FAILED, ESCALATED, PR_OPEN} 모두 포함."""
    rb_targets = taskctl_mod.ALLOWED_TRANSITIONS.get("RECOVERABLE_BLOCKED", set())
    expected = {"MERGING", "FAILED", "ESCALATED", "PR_OPEN"}
    missing = expected - rb_targets
    assert not missing, (
        f"RECOVERABLE_BLOCKED 의 누락된 전이: {missing} (실제: {sorted(rb_targets)})"
    )


def test_recoverable_blocked_can_return_to_merging(taskctl_mod):
    """RECOVERABLE_BLOCKED -> MERGING 전이 허용 (cmd_recover 자동 복귀)."""
    rb_targets = taskctl_mod.ALLOWED_TRANSITIONS.get("RECOVERABLE_BLOCKED", set())
    assert "MERGING" in rb_targets


def test_recoverable_blocked_can_escalate(taskctl_mod):
    """RECOVERABLE_BLOCKED -> ESCALATED 전이 허용."""
    rb_targets = taskctl_mod.ALLOWED_TRANSITIONS.get("RECOVERABLE_BLOCKED", set())
    assert "ESCALATED" in rb_targets


def test_recoverable_blocked_key_exists_in_transitions(taskctl_mod):
    """ALLOWED_TRANSITIONS dict 에 키로 존재 (entry 자체가 등록되어 있어야 함)."""
    assert "RECOVERABLE_BLOCKED" in taskctl_mod.ALLOWED_TRANSITIONS


# ---------------------------------------------------------------------------
# cmd_recover 등록 검증
# ---------------------------------------------------------------------------


def test_cmd_recover_function_exists(taskctl_mod):
    """``cmd_recover`` 함수가 모듈에 정의되어 있어야 함."""
    assert hasattr(taskctl_mod, "cmd_recover"), (
        "cmd_recover 함수 미정의 (토르 commit 4795c5e2 누락 의심)"
    )
    assert callable(taskctl_mod.cmd_recover)
