"""tests/regression/test_dispatch_smoke_2569_plus_1.py

회귀 테스트 — task-2569+1 dispatch.py UnboundLocalError hotfix.

배경 (회장 §명시, 2026-05-13):
  task-2569 RC-4 영역에서 dispatch/__init__.py 에 4 위치
  `git update-index --add --intent-to-add` 코드를 추가하면서
  함수 내부에 `import subprocess` local import 가 들어갔다.

  Python LEGB rule:
    - 함수 안에 `import X` 가 있으면 함수 전체에서 X 는 local variable.
    - top-level `import subprocess` (line 30) 가 함수 내부에서 무시됨.
    - 함수 안 line 3508 `subprocess.run(timer_cmd, ...)` 이 local
      import (line 3590, 4101) 보다 위에 있어 UnboundLocalError 발생.

  결과: dispatch.py 모든 호출 broken. 위임 시스템 자체 마비.

본 회귀 (3 카운트):
  1. dispatch 모듈 import smoke — `from dispatch import dispatch` 가
     예외 없이 통과해야 한다. (top-level import subprocess 정상 결합)
  2. 함수 내부 `import subprocess` 패턴 0건 박제 — local import 가
     재발하면 본 테스트가 실패하면서 UnboundLocalError 의 근본 원인을
     선제적으로 차단한다.
  3. dispatch 함수 내부에서 subprocess 가 LEGB 의 module-level 로
     해석되는지 확인 — `dispatch.subprocess` 가 표준 stdlib subprocess
     모듈과 동일한 객체여야 한다.
"""

from __future__ import annotations

import ast
import importlib.util
import subprocess as _stdlib_subprocess
import sys
from pathlib import Path

import pytest


# 본 테스트가 실행되는 working tree (main repo / worktree) 의 진짜
# dispatch/__init__.py 를 동적으로 해석한다. 절대경로 하드코딩 시
# worktree 에서 main repo 파일을 잘못 읽는 사고를 차단.
_TEST_FILE = Path(__file__).resolve()
WORKSPACE = _TEST_FILE.parents[2]
DISPATCH_INIT = WORKSPACE / "dispatch" / "__init__.py"


@pytest.fixture(scope="module")
def dispatch_module():
    """`/home/jay/workspace/dispatch/__init__.py` 를 파일 경로로 직접 로드.

    pytest 의 rootdir 가 `tests/` 일 때 `tests/dispatch/__init__.py`
    빈 패키지가 진짜 dispatch 패키지를 가리는 사고를 막기 위해
    importlib.util.spec_from_file_location 으로 강제 결합한다.
    """
    sys.modules.pop("dispatch", None)
    spec = importlib.util.spec_from_file_location(
        "dispatch", DISPATCH_INIT, submodule_search_locations=[str(DISPATCH_INIT.parent)]
    )
    assert spec is not None and spec.loader is not None, (
        f"dispatch spec 로드 실패: {DISPATCH_INIT}"
    )
    module = importlib.util.module_from_spec(spec)
    sys.modules["dispatch"] = module
    spec.loader.exec_module(module)
    return module


def test_regression_1_dispatch_import_smoke(dispatch_module):
    """`from dispatch import dispatch` 가 예외 없이 통과해야 한다.

    UnboundLocalError 가 재발하면 함수 정의 자체는 통과하지만 실제
    호출 직전까지 잠복한다. 본 smoke 는 적어도 모듈 로드 + 함수
    심볼 노출까지가 정상임을 박제한다.
    """
    assert hasattr(dispatch_module, "dispatch"), (
        "dispatch 모듈에서 dispatch 함수 심볼이 노출되지 않았다"
    )
    assert callable(dispatch_module.dispatch), (
        "dispatch.dispatch 가 호출 가능한 객체가 아니다"
    )


def test_regression_2_no_local_subprocess_import_in_functions():
    """dispatch/__init__.py 함수 내부 local `import subprocess` 0건 박제.

    Python LEGB 규칙 상 함수 내부 `import X` 는 X 를 함수 local 로
    바인딩한다. top-level `import subprocess` (line 30) 가 있는 상태
    에서 함수 내부에 동일한 import 가 추가되면, 함수 안에서 해당 import
    행 보다 앞에 있는 `subprocess.run(...)` 호출이 모두 UnboundLocalError
    로 깨진다 — task-2569 RC-4 가 정확히 이 패턴이었다.

    본 테스트는 AST 를 사용해 모든 함수/메서드 내부에서
    `import subprocess` 노드를 0건으로 박제한다. (top-level 은 허용)
    """
    source = DISPATCH_INIT.read_text(encoding="utf-8")
    tree = ast.parse(source, filename=str(DISPATCH_INIT))

    offenders: list[tuple[str, int]] = []

    class _LocalSubprocessImportVisitor(ast.NodeVisitor):
        def __init__(self):
            self._func_stack: list[str] = []

        def _visit_function(self, node):
            self._func_stack.append(node.name)
            self.generic_visit(node)
            self._func_stack.pop()

        visit_FunctionDef = _visit_function
        visit_AsyncFunctionDef = _visit_function

        def visit_Import(self, node):
            if not self._func_stack:
                return
            for alias in node.names:
                if alias.name == "subprocess":
                    offenders.append((self._func_stack[-1], node.lineno))

    _LocalSubprocessImportVisitor().visit(tree)

    assert offenders == [], (
        "dispatch/__init__.py 함수 내부에서 local `import subprocess` 가 "
        f"재발했다 (UnboundLocalError 재현 위험): {offenders}. "
        "top-level import subprocess (line 30) 만 사용해야 한다."
    )


def test_regression_3_subprocess_resolves_to_stdlib_at_module_level(dispatch_module):
    """dispatch 모듈에서 노출되는 subprocess 는 stdlib subprocess 와 동일해야 한다.

    함수 내부에 local import 가 잠입하면 모듈 attribute 자체는 영향이
    없지만, 모듈 attribute 와 stdlib subprocess 의 동일성을 박제하면
    누군가 실수로 top-level `import subprocess` 를 alias 로 바꾸거나
    제거했을 때 빠르게 잡힌다.
    """
    assert getattr(dispatch_module, "subprocess", None) is _stdlib_subprocess, (
        "dispatch.subprocess 가 stdlib subprocess 모듈과 다르다 — "
        "top-level import subprocess (line 30) 를 확인할 것"
    )
