"""회귀 테스트 12건 — anu_v2.post_merge_smoke_runner (task-2539).

pytest 사용. 외부 부수효과(subprocess / file write / capabilities / clock)는
모두 fake callable로 주입.

⚠️ 테스트 코드 내 "ghp_faketoken123abc" 등 raw token placeholder는
   실제 토큰이 아닌 테스트 fake 값임 — leak detector 오탐 방지를 위해 명시.
"""

from __future__ import annotations

import inspect
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Callable

import pytest

# workspace root → sys.path (anu_v2 패키지를 절대 import 하기 위함)
WORKSPACE_ROOT = Path(__file__).resolve().parents[2]
if str(WORKSPACE_ROOT) not in sys.path:
    sys.path.insert(0, str(WORKSPACE_ROOT))

from anu_v2.post_merge_smoke_runner import (  # noqa: E402
    DEFAULT_CHAT_ID,
    KIND_NAME_POST_MERGE_SMOKE_FAILURE,
    PostMergeSmokeRunner,
)

# ─── 공용 fixture / 상수 ──────────────────────────────────────────────────────
FAKE_SHA40 = "a" * 40                   # 40자 fake full SHA
FAKE_MERGE_SHA = "106e645b479b6fb1888256b0ef4a7b68ed8fad63"  # task-2524 참조
FAKE_MAIN_SHA = "b" * 40

FAKE_TASK_ID = "task-2539"
FIXTURE_DIR = Path(__file__).parent.parent / "fixtures"


def _fake_clock() -> datetime:
    """고정 시각 반환 — 테스트 결정론 보장."""
    return datetime(2026, 5, 10, 21, 32, 30, tzinfo=timezone.utc)


def _make_completed_process(
    returncode: int = 0,
    stdout: str = "ok",
    stderr: str = "",
) -> subprocess.CompletedProcess:
    return subprocess.CompletedProcess(
        args=[],
        returncode=returncode,
        stdout=stdout,
        stderr=stderr,
    )


def _make_git_ok_then_smoke(smoke_returncode: int = 0, smoke_stderr: str = "") -> Callable[..., subprocess.CompletedProcess]:
    """git rev-parse → smoke command 순서로 응답하는 fake runner."""
    call_count = {"n": 0}

    def runner(args, **_):
        call_count["n"] += 1
        if "rev-parse" in args:
            return _make_completed_process(0, FAKE_MAIN_SHA + "\n", "")
        return _make_completed_process(smoke_returncode, "smoke output", smoke_stderr)

    return runner


# task-2561: mis-scoped baseline noise cleanup helper.
# Test 12 (`test_clean_origin_main_base_assertion`) is a one-shot pre-merge
# guard for task-2539+1; without this scope check it deterministically fails
# on every other branch. Returns True only when running inside a
# task-2539+1 worktree / dispatched context, so the gate keeps protecting
# that specific scope without polluting unrelated branches.
_TASK_2539PLUS1_ID = "task-2539+1"
_TASK_2539PLUS1_BRANCH_PREFIX = "task/task-2539+1"


def _is_task_2539plus1_scope() -> bool:
    """task-2539+1 worktree/dispatch 컨텍스트 여부 (OR 매칭)."""
    if os.environ.get("TASK_ID") == _TASK_2539PLUS1_ID:
        return True
    try:
        branch_result = subprocess.run(
            ["git", "rev-parse", "--abbrev-ref", "HEAD"],
            capture_output=True,
            text=True,
            cwd=str(WORKSPACE_ROOT),
            timeout=2,
        )
        if branch_result.returncode == 0:
            branch = branch_result.stdout.strip()
            if branch.startswith(_TASK_2539PLUS1_BRANCH_PREFIX):
                return True
    except (FileNotFoundError, subprocess.TimeoutExpired):
        pass
    try:
        name = getattr(WORKSPACE_ROOT, "name", None)
        if isinstance(name, str) and _TASK_2539PLUS1_ID in name:
            return True
    except (OSError, ValueError, AttributeError):
        pass
    return False


# ─────────────────────────────────────────────────────────────────────────────
# Test 1: smoke PASS → marker 생성
# ─────────────────────────────────────────────────────────────────────────────
def test_smoke_pass_creates_marker(tmp_path: Path) -> None:
    """smoke exit_code=0 → outcome=PASS / .smoke-evidence 파일 생성 / format 일치."""
    runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(0),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    result = runner.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=["anu_v2/post_merge_smoke_runner.py"],
    )

    # outcome / exit_code
    assert result["outcome"] == "PASS"
    assert result["exit_code"] == 0
    assert result["critical_seven_classification"] is None

    # marker 파일 존재
    marker_path = Path(result["smoke_evidence_marker_path"])
    assert marker_path.exists(), "smoke-evidence marker 파일이 없음"

    # 내용 1 line
    lines = marker_path.read_text(encoding="utf-8").strip().splitlines()
    assert len(lines) == 1

    # task-2524 fixture 호환 필드 검증
    record = json.loads(lines[0])
    assert record["task_id"] == FAKE_TASK_ID
    assert record["merge_sha"] == FAKE_SHA40
    assert record["outcome"] == "probe_pass"
    assert "ts" in record
    assert record["build_ok"] is True
    assert record["test_ok"] is True
    assert "command" in record
    assert "exit_code" in record
    assert "duration_seconds" in record
    assert "main_head" in record
    assert "merge_commit" in record
    assert "expected_files_count" in record
    assert record["expected_files_count"] == 1  # expected_files=["anu_v2/post_merge_smoke_runner.py"]


# ─────────────────────────────────────────────────────────────────────────────
# Test 2: smoke FAIL → Critical 7종 #7 분류
# ─────────────────────────────────────────────────────────────────────────────
def test_smoke_fail_classifies_critical_seven(tmp_path: Path) -> None:
    """smoke exit_code=1 → outcome=FAIL / critical_seven_classification=7 / marker 미생성."""
    runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(1, "AssertionError: test failed"),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    result = runner.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=["anu_v2/post_merge_smoke_runner.py"],
    )

    assert result["outcome"] == "FAIL"
    assert result["critical_seven_classification"] == 7
    assert result["smoke_evidence_marker_path"] is None

    # classify_smoke_failure_as_critical_seven 독립 호출 검증
    exec_result = {
        "command": "python3 -m pytest tests/smoke",
        "exit_code": 1,
        "stdout": "",
        "stderr": "AssertionError: test failed",
        "duration_seconds": 1.0,
    }
    critical = runner.classify_smoke_failure_as_critical_seven(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        execution_result=exec_result,
    )
    assert critical["critical_seven_kind"] == 7
    assert critical["kind_name"] == KIND_NAME_POST_MERGE_SMOKE_FAILURE
    assert critical["report_to_chairman_required"] is True

    # marker 파일 미생성 확인
    events_dir = tmp_path / "memory" / "events"
    marker = events_dir / f"{FAKE_TASK_ID}.smoke-evidence"
    assert not marker.exists(), "FAIL 시 marker가 생성되면 안 됨"


# ─────────────────────────────────────────────────────────────────────────────
# Test 3: smoke command 우선순위 4 케이스
# ─────────────────────────────────────────────────────────────────────────────
@pytest.mark.parametrize(
    "smoke_command, caps_command, smoke_profile, expected",
    [
        # 1. 명시 smoke_command 최우선
        ("explicit-cmd", "from-caps", "profile.py", "explicit-cmd"),
        # 2. capabilities loader 반환 (smoke_command 없음)
        (None, "from-caps", "profile.py", "from-caps"),
        # 3. smoke_profile 래핑 (caps 없음)
        (None, None, "tests/smoke/custom.py", "python3 -m pytest tests/smoke/custom.py"),
        # 4. DEFAULT (모두 없음)
        (None, None, None, "python3 -m pytest tests/smoke/test_smoke_baseline.py"),
    ],
)
def test_resolve_command_priority(
    tmp_path: Path,
    smoke_command: str | None,
    caps_command: str | None,
    smoke_profile: str | None,
    expected: str,
) -> None:
    """_resolve_smoke_command 4 케이스 우선순위 검증."""
    caps = {"smoke_command": caps_command} if caps_command else None

    runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(0),
        capabilities_loader=lambda _: caps,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    resolved = runner._resolve_smoke_command(FAKE_TASK_ID, smoke_command, smoke_profile)
    assert resolved == expected


# ─────────────────────────────────────────────────────────────────────────────
# Test 4: idempotent marker append
# ─────────────────────────────────────────────────────────────────────────────
def test_idempotent_marker_append(tmp_path: Path) -> None:
    """동일 task_id 2회 실행 → marker에 2 line. 덮어쓰기 X."""
    runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(0),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )

    # 1회
    r1 = runner.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=[],
    )
    assert r1["outcome"] == "PASS"

    # 2회 (subprocess_runner 재생성 — git rev-parse 포함)
    runner2 = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(0),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    r2 = runner2.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=[],
    )
    assert r2["outcome"] == "PASS"

    marker_path = Path(r1["smoke_evidence_marker_path"])
    lines = marker_path.read_text(encoding="utf-8").strip().splitlines()
    assert len(lines) == 2, f"marker에 2 line이어야 함 (got {len(lines)})"

    rec1 = json.loads(lines[0])
    rec2 = json.loads(lines[1])
    assert rec1["task_id"] == rec2["task_id"] == FAKE_TASK_ID


# ─────────────────────────────────────────────────────────────────────────────
# Test 5: chat 격리 — chat_id != 6937032012 → AssertionError
# ─────────────────────────────────────────────────────────────────────────────
def test_chat_isolation_assertion(tmp_path: Path) -> None:
    """chat_id=999 입력 → AssertionError (chat_id != 6937032012 cross-chat 누출 차단)."""
    runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(0),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    with pytest.raises(AssertionError, match="chat_id != 6937032012"):
        runner.run_post_merge_smoke(
            task_id=FAKE_TASK_ID,
            merge_commit=FAKE_SHA40,
            expected_files=[],
            chat_id=999,
        )


# ─────────────────────────────────────────────────────────────────────────────
# Test 6: token raw 0
# ─────────────────────────────────────────────────────────────────────────────
def test_token_raw_zero(tmp_path: Path) -> None:
    """fake stderr에 raw token 포함 → marker / Critical 7종 본문 모두 ***MASKED*** 처리.

    ⚠️ 아래 "ghp_faketoken123abc"는 테스트 fake 값 (실제 토큰 아님).
    """
    # fake stderr에 가짜 토큰 포함
    # "ghp_faketoken123abc" — 테스트 fake, 실제 토큰 아님
    fake_stderr = "GH_TOKEN=ghp_faketoken123abc x-api-key: secretkey"

    call_count = {"n": 0}

    def runner_with_token_stderr(args, **_):
        call_count["n"] += 1
        if "rev-parse" in args:
            return _make_completed_process(0, FAKE_MAIN_SHA + "\n", "")
        # FAIL (exit 1) — Critical 7종 분류
        return _make_completed_process(1, "", fake_stderr)

    runner = PostMergeSmokeRunner(
        subprocess_runner=runner_with_token_stderr,
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    result = runner.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=[],
    )

    assert result["outcome"] == "FAIL"
    # stderr_summary에 raw token이 없어야 함
    assert "ghp_faketoken123abc" not in result["stderr_summary"], (
        "stderr_summary에 raw token 노출"
    )
    assert "secretkey" not in result["stderr_summary"], (
        "stderr_summary에 raw secret 노출"
    )
    assert "***MASKED***" in result["stderr_summary"], "마스킹 처리되어야 함"

    # classify_smoke_failure_as_critical_seven 본문 검증
    exec_result = {
        "command": "python3 -m pytest tests/smoke",
        "exit_code": 1,
        "stdout": "",
        "stderr": fake_stderr,
        "duration_seconds": 0.5,
    }
    critical = runner.classify_smoke_failure_as_critical_seven(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        execution_result=exec_result,
    )
    assert "ghp_faketoken123abc" not in critical["stderr_summary"], (
        "Critical 7종 stderr_summary에 raw token 노출"
    )
    assert "secretkey" not in critical["stderr_summary"], (
        "Critical 7종 stderr_summary에 raw secret 노출"
    )


# ─────────────────────────────────────────────────────────────────────────────
# Test 7: md/report fallback 금지 + read-only marker dir → EVIDENCE_INCOMPLETE
# ─────────────────────────────────────────────────────────────────────────────
def test_md_report_fallback_forbidden(tmp_path: Path, monkeypatch) -> None:
    """md/report fallback 금지 + OSError 시 EVIDENCE_INCOMPLETE 반환.

    qc-result md에 "smoke PASS" 텍스트가 있어도 run_post_merge_smoke는
    md를 참조하지 않음 — 시그니처에 md 관련 파라미터 0건 검증.
    marker dir read-only 시뮬(monkeypatch open → OSError) → outcome=EVIDENCE_INCOMPLETE.
    """
    runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(0),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )

    # 시그니처 검증 — md 관련 파라미터 0건
    sig = inspect.signature(runner.run_post_merge_smoke)
    md_params = [p for p in sig.parameters if "md" in p.lower() or "report" in p.lower()]
    assert len(md_params) == 0, f"md/report 관련 파라미터가 있음: {md_params}"

    # exit=1이면 md "smoke PASS" 텍스트와 무관하게 FAIL
    fail_runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(1),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    result_fail = fail_runner.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=[],
    )
    assert result_fail["outcome"] == "FAIL", (
        "exit_code=1일 때 md 텍스트 무관 FAIL이어야 함"
    )

    # OSError 시 EVIDENCE_INCOMPLETE
    original_open = open

    def mock_open_oserror(path, mode="r", **kwargs):
        path_str = str(path)
        if ".smoke-evidence" in path_str and "a" in mode:
            raise OSError("read-only filesystem simulation")
        return original_open(path, mode, **kwargs)

    monkeypatch.setattr("builtins.open", mock_open_oserror)

    result_incomplete = runner.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=[],
    )
    assert result_incomplete["outcome"] == "EVIDENCE_INCOMPLETE"
    assert result_incomplete["smoke_evidence_marker_path"] is None
    assert result_incomplete["critical_seven_classification"] is None


# ─────────────────────────────────────────────────────────────────────────────
# Test 8: timeout → FAIL / exit_code=124 / critical=7 / marker 미생성
# ─────────────────────────────────────────────────────────────────────────────
def test_timeout_classifies_critical_seven(tmp_path: Path) -> None:
    """TimeoutExpired raise → outcome=FAIL / exit_code=124 / critical=7 / marker 미생성."""

    def timeout_runner(args, **_):
        if "rev-parse" in args:
            return _make_completed_process(0, FAKE_MAIN_SHA + "\n", "")
        raise subprocess.TimeoutExpired(cmd=args, timeout=300)

    runner = PostMergeSmokeRunner(
        subprocess_runner=timeout_runner,
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    result = runner.run_post_merge_smoke(
        task_id=FAKE_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=[],
    )

    assert result["outcome"] == "FAIL"
    assert result["exit_code"] == 124
    assert result["critical_seven_classification"] == 7
    assert result["smoke_evidence_marker_path"] is None

    # marker 파일 미생성 확인
    events_dir = tmp_path / "memory" / "events"
    marker = events_dir / f"{FAKE_TASK_ID}.smoke-evidence"
    assert not marker.exists(), "timeout 시 marker가 생성되면 안 됨"


# ─────────────────────────────────────────────────────────────────────────────
# Test 9: interface contract
# ─────────────────────────────────────────────────────────────────────────────
def test_interface_contract() -> None:
    """run_post_merge_smoke / classify_smoke_failure_as_critical_seven 시그니처 보존.

    task-2540 critical_escalation_reporter / task-2541 self-resume이 import 가능 형태.
    """
    # run_post_merge_smoke 시그니처
    sig_run = inspect.signature(PostMergeSmokeRunner.run_post_merge_smoke)
    params_run = set(sig_run.parameters.keys())

    assert "task_id" in params_run, "task_id 파라미터 없음"
    assert "merge_commit" in params_run, "merge_commit 파라미터 없음"
    assert "expected_files" in params_run, "expected_files 파라미터 없음"

    # optional params
    assert "smoke_command" in params_run
    assert "smoke_profile" in params_run
    assert "chat_id" in params_run

    # classify_smoke_failure_as_critical_seven 시그니처
    sig_classify = inspect.signature(PostMergeSmokeRunner.classify_smoke_failure_as_critical_seven)
    params_classify = set(sig_classify.parameters.keys())

    assert "task_id" in params_classify, "task_id 파라미터 없음"
    assert "merge_commit" in params_classify, "merge_commit 파라미터 없음"
    assert "execution_result" in params_classify, "execution_result 파라미터 없음"

    # __init__ 주입 파라미터 검증
    sig_init = inspect.signature(PostMergeSmokeRunner.__init__)
    params_init = set(sig_init.parameters.keys())
    assert "subprocess_runner" in params_init
    assert "capabilities_loader" in params_init
    assert "clock" in params_init
    assert "workspace_root" in params_init

    # DEFAULT_CHAT_ID 상수값 검증
    assert DEFAULT_CHAT_ID == 6937032012

    # KIND_NAME 상수 검증
    assert KIND_NAME_POST_MERGE_SMOKE_FAILURE == "POST_MERGE_SMOKE_FAILURE"


# ─────────────────────────────────────────────────────────────────────────────
# Test 10: self-invoke smoke-evidence 생성 검증 (post-merge phase 자기참조)
# ─────────────────────────────────────────────────────────────────────────────
def test_self_invoke_smoke_evidence_for_task_2539p1(tmp_path: Path) -> None:
    """task-2539+1 자기참조 smoke — marker 생성 + task_id / outcome 확인.

    본 모듈이 자기 자신(task-2539+1) 머지 후 호출되어
    task-2539+1.smoke-evidence 박제 가능 여부를 검증한다 (post-merge phase 자기참조).
    """
    SELF_TASK_ID = "task-2539+1"
    EXPECTED_FILES_2539P1 = [
        "anu_v2/post_merge_smoke_runner.py",
        "anu_v2/tests/test_post_merge_smoke_runner_2539.py",
        "anu_v2/fixtures/post_merge_smoke_pass_task_2524.json",
        "anu_v2/fixtures/post_merge_smoke_warn_to_pass_task_2537.json",
    ]

    runner = PostMergeSmokeRunner(
        subprocess_runner=_make_git_ok_then_smoke(0),
        capabilities_loader=lambda _: None,
        clock=_fake_clock,
        workspace_root=tmp_path,
    )
    result = runner.run_post_merge_smoke(
        task_id=SELF_TASK_ID,
        merge_commit=FAKE_SHA40,
        expected_files=EXPECTED_FILES_2539P1,
    )

    # smoke PASS 확인
    assert result["outcome"] == "PASS", f"expected PASS, got {result['outcome']}"
    assert result["exit_code"] == 0
    assert result["critical_seven_classification"] is None

    # marker 파일 생성 확인
    marker_path = Path(result["smoke_evidence_marker_path"])
    assert marker_path.exists(), "task-2539+1.smoke-evidence marker 파일이 없음"

    # marker 파일명 확인 (task-2539+1 포함)
    assert SELF_TASK_ID in marker_path.name, (
        f"marker 파일명에 task_id가 없음: {marker_path.name}"
    )

    # 내용 검증: task_id / outcome 포함
    lines = marker_path.read_text(encoding="utf-8").strip().splitlines()
    assert len(lines) >= 1, "marker에 최소 1 line이 있어야 함"
    record = json.loads(lines[0])

    assert record["task_id"] == SELF_TASK_ID, (
        f"marker task_id mismatch: {record['task_id']!r}"
    )
    assert record["outcome"] == "probe_pass", (
        f"marker outcome mismatch: {record['outcome']!r}"
    )
    assert record["expected_files_count"] == len(EXPECTED_FILES_2539P1), (
        f"expected_files_count mismatch: {record['expected_files_count']}"
    )


# ─────────────────────────────────────────────────────────────────────────────
# Test 11: Pyright 0 errors — anu_v2/post_merge_smoke_runner.py 정적 타입 검사
# ─────────────────────────────────────────────────────────────────────────────
def test_pyright_zero_errors_on_runner_module() -> None:
    """pyright로 post_merge_smoke_runner.py 정적 타입 검사 — 0 errors 어설션.

    pyright가 환경에 없으면 pytest.skip.
    """
    runner_module = str(WORKSPACE_ROOT / "anu_v2" / "post_merge_smoke_runner.py")

    # pyright 실행 시도 (pyright 직접 또는 python3 -m pyright)
    result = None
    for cmd in [["pyright", runner_module], ["python3", "-m", "pyright", runner_module]]:
        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                cwd=str(WORKSPACE_ROOT),
                timeout=60,
            )
            # "command not found" 등 실행 자체 실패 시 다음 명령 시도
            combined = (result.stdout + result.stderr).lower()
            if result.returncode != 0 and (
                "command not found" in combined
                or "no module named" in combined
                or "not recognized" in combined
            ):
                result = None
                continue
            break
        except FileNotFoundError:
            result = None
            continue
        except subprocess.TimeoutExpired:
            pytest.skip("pyright timed out")

    if result is None:
        pytest.skip("pyright unavailable")

    # returncode == 0 어설션
    combined_output = result.stdout + result.stderr
    assert result.returncode == 0, (
        f"pyright returncode={result.returncode}\n{combined_output[:2000]}"
    )

    # "0 errors" 패턴 또는 "error" 키워드 부재 확인
    output_lower = combined_output.lower()
    has_zero_errors = "0 errors" in output_lower
    has_error_keyword = (
        "error" in output_lower
        and "0 errors" not in output_lower
    )
    assert not has_error_keyword or has_zero_errors, (
        f"pyright reported errors:\n{combined_output[:2000]}"
    )


# ─────────────────────────────────────────────────────────────────────────────
# Test 12: clean origin/main base 어설션 — task-2539 사고 재발 방지
# ─────────────────────────────────────────────────────────────────────────────
def test_clean_origin_main_base_assertion() -> None:
    """git diff origin/main..HEAD 변경 파일이 allowed_resources.paths 화이트리스트만 포함.

    task-2539 사고 재발 방지 gate: effective diff == expected_files (4개) + 메타파일.

    task-2561 scope-guard: task-2539+1 worktree/dispatch 컨텍스트가 아니면
    deterministic skip — mis-scoped baseline noise 0 (회장 §명시 2026-05-12 Track B).
    """
    if not _is_task_2539plus1_scope():
        pytest.skip(
            "out-of-scope: task-2539+1 worktree/dispatch 컨텍스트 아님 "
            "(task-2561 scope-guard, deterministic)"
        )

    ALLOWED_PATHS: set[str] = {
        "anu_v2/post_merge_smoke_runner.py",
        "anu_v2/tests/test_post_merge_smoke_runner_2539.py",
        "anu_v2/fixtures/post_merge_smoke_pass_task_2524.json",
        "anu_v2/fixtures/post_merge_smoke_warn_to_pass_task_2537.json",
        "memory/reports/task-2539+1.md",
        "memory/events/task-2539+1.done",
        "memory/capabilities/task-2539+1.json",
    }

    # git diff origin/main..HEAD 실행
    try:
        result = subprocess.run(
            ["git", "diff", "--name-only", "origin/main..HEAD"],
            capture_output=True,
            text=True,
            cwd=str(WORKSPACE_ROOT),
            timeout=30,
        )
    except FileNotFoundError:
        pytest.skip("git unavailable or not a worktree")
    except subprocess.TimeoutExpired:
        pytest.skip("git timed out")

    if result.returncode != 0:
        pytest.skip("git unavailable or not a worktree")

    # 변경 파일 목록 파싱
    changed_files = [
        line.strip()
        for line in result.stdout.strip().splitlines()
        if line.strip()
    ]

    # 모든 변경 파일이 화이트리스트에 속하는지 검증
    for path in changed_files:
        assert path in ALLOWED_PATHS, (
            f"FORBIDDEN_PATH or SCOPE_EXPANSION detected: {path}"
        )


# ─────────────────────────────────────────────────────────────────────────────
# Test 13~16 (task-2561 신규 회귀): mis-scoped baseline noise cleanup 박제
# 회장 §명시 2026-05-12 Track B (2순위) — Option A primary + Option B 보조 fixture.
# baseline-noise-diagnosis.20260512T142519Z.json 근거.
# ─────────────────────────────────────────────────────────────────────────────
_BASELINE_NOISE_FIXTURE_2561 = (
    Path(__file__).parent / "fixtures" / "baseline_noise_repro_2561.json"
)


def test_baseline_noise_fixture_2561_present_and_well_formed() -> None:
    """fixture 파일 존재 + 필수 키 구조 검증."""
    assert _BASELINE_NOISE_FIXTURE_2561.exists(), (
        f"baseline noise fixture 누락: {_BASELINE_NOISE_FIXTURE_2561}"
    )
    data = json.loads(_BASELINE_NOISE_FIXTURE_2561.read_text(encoding="utf-8"))
    assert data["task_ref"] == "task-2561"
    assert data["track"] == "B"
    assert data["classification"] == "MIS_SCOPED_TEST"
    assert (
        data["source_test"]["file"]
        == "anu_v2/tests/test_post_merge_smoke_runner_2539.py"
    )
    assert (
        data["source_test"]["function"] == "test_clean_origin_main_base_assertion"
    )
    assert data["source_test"]["introducing_task"] == "task-2539+1"
    allowed = set(data["allowed_paths_hardcoded"])
    assert len(allowed) == 7
    assert "anu_v2/post_merge_smoke_runner.py" in allowed
    assert "memory/capabilities/task-2539+1.json" in allowed
    repro = {case["name"]: case for case in data["repro_cases"]}
    assert "in_scope_clean_diff" in repro
    assert "out_of_scope_arbitrary_branch" in repro
    assert "in_scope_forbidden_path" in repro
    assert repro["in_scope_clean_diff"]["expected_outcome"] == "PASS"
    assert repro["out_of_scope_arbitrary_branch"]["expected_outcome"] == "SKIP"
    assert (
        repro["in_scope_forbidden_path"]["expected_outcome"] == "AssertionError"
    )


def test_clean_origin_main_skipped_outside_task_2539plus1_scope(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """out-of-scope (env / branch / worktree 셋 다 false) 면 deterministic skip.

    PR #110 / task-2550+1 같은 후속 브랜치에서 false failure 0 보장.
    """
    monkeypatch.delenv("TASK_ID", raising=False)

    def fake_run(args, **_):
        if "rev-parse" in args and "--abbrev-ref" in args:
            return _make_completed_process(0, "task/task-2561-dev2\n", "")
        if "diff" in args:
            raise AssertionError(
                "out-of-scope 분기에서 git diff 가 호출되면 안 됨 "
                "(task-2561 scope-guard 위반)"
            )
        return _make_completed_process(0, "", "")

    monkeypatch.setattr(subprocess, "run", fake_run)
    # WORKSPACE_ROOT 가 task-2539+1 worktree 안에서 실행되면 본 케이스 의미 상실 → skip.
    # (assert 대신 skip — Gemini medium 권고 반영, CI / dev 환경 폭 보장)
    if _TASK_2539PLUS1_ID in (getattr(WORKSPACE_ROOT, "name", "") or ""):
        pytest.skip(
            "WORKSPACE_ROOT name contains task-2539+1 — out-of-scope 시나리오 검증 불가"
        )

    with pytest.raises(pytest.skip.Exception) as skip_info:
        test_clean_origin_main_base_assertion()
    assert "out-of-scope" in str(skip_info.value)


def test_clean_origin_main_runs_under_task_2539plus1_scope_with_clean_diff(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """in-scope (TASK_ID=task-2539+1) + clean diff → 기존 보호 의도 정상 PASS.

    task-2539+1 fixture context 기존 검증 유지 확인.
    """
    monkeypatch.setenv("TASK_ID", "task-2539+1")

    clean_diff_paths = [
        "anu_v2/post_merge_smoke_runner.py",
        "anu_v2/tests/test_post_merge_smoke_runner_2539.py",
        "memory/reports/task-2539+1.md",
        "memory/events/task-2539+1.done",
        "memory/capabilities/task-2539+1.json",
    ]

    def fake_run(args, **_):
        if "diff" in args:
            return _make_completed_process(0, "\n".join(clean_diff_paths) + "\n", "")
        return _make_completed_process(0, "", "")

    monkeypatch.setattr(subprocess, "run", fake_run)
    # in-scope 에서는 skip 도 AssertionError 도 없이 정상 완료
    test_clean_origin_main_base_assertion()


def test_clean_origin_main_in_scope_detects_forbidden_path(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """in-scope + forbidden path → AssertionError (task-2539+1 보호 의도 유지)."""
    monkeypatch.setenv("TASK_ID", "task-2539+1")

    forbidden_diff_paths = [
        "anu_v2/post_merge_smoke_runner.py",
        "anu_v2/executor_scheduler.py",  # forbidden — ALLOWED_PATHS 밖
    ]

    def fake_run(args, **_):
        if "diff" in args:
            return _make_completed_process(
                0, "\n".join(forbidden_diff_paths) + "\n", ""
            )
        return _make_completed_process(0, "", "")

    monkeypatch.setattr(subprocess, "run", fake_run)
    with pytest.raises(AssertionError) as exc_info:
        test_clean_origin_main_base_assertion()
    assert "FORBIDDEN_PATH or SCOPE_EXPANSION detected" in str(exc_info.value)
    assert "anu_v2/executor_scheduler.py" in str(exc_info.value)
