#!/usr/bin/env python3
"""
test_stash_lifecycle_fail_stop_bash.py
task: task-2571+2
작성자: 카르티케야 (개발4팀 백엔드)

해소 대상 findings:
- Gemini HIGH: stash-lifecycle dispatch heredoc python failure가 bash에서
  fatal로 전파되지 않아 다음 stash 처리가 계속될 수 있음.
  → Fix B (라인 1175): `|| echo "[WARN]..."` → `|| { echo "[ERROR]..." >&2; exit 1; }`
- Gemini MEDIUM: stash_audit.py가 execute bit 없어도 `-x` 가드에 막혀 실행 경로를
  통과하지 못함.
  → Fix A (라인 1166): `if [ -x ... ]` → `if [ -f ... ]`

검증 목표:
1. bash 계층에서 python heredoc sys.exit(1) 이 non-zero exit code로 전파되는가
   (기존 test_stash_lifecycle_failstop.py 는 python 본문 직접 실행 → bash 계층 갭)
2. stash_audit.py execute bit 없어도 -f 가드 기준 실행 경로가 유지되는가
   (-x 가드는 통과 못 함 → -f 가드만 통과 → fix 효용 입증)
"""

import stat
import subprocess
import textwrap
from pathlib import Path

WORKTREE_ROOT = Path(__file__).resolve().parents[2]
FINISH_TASK_SH = WORKTREE_ROOT / "scripts" / "finish-task.sh"

# stash lifecycle dispatch 컨텍스트 마커 (finish-task.sh 내부)
LIFECYCLE_DISPATCH_MARKER = "task-2571: stash lifecycle dispatch"


# ---------------------------------------------------------------------------
# Test #1: bash 계층에서 python heredoc sys.exit(1) → non-zero exit 전파
# ---------------------------------------------------------------------------

def test_bash_heredoc_python_failure_propagates_nonzero_exit(tmp_path: Path) -> None:
    """
    Gemini HIGH 해소: bash heredoc 내 python이 sys.exit(1) 할 때,
    Fix B (|| { echo "[ERROR]..." >&2; exit 1; }) 덕분에 bash exit code가 non-zero가 되어야 한다.

    검증 방식:
    - fix 전 패턴: `<<'PYEOF' || echo "[WARN]..."` → bash exit code = 0 (non-fatal)
    - fix 후 패턴: `<<'PYEOF' || { echo "[ERROR]..." >&2; exit 1; }` → bash exit code != 0

    임시 bash 스크립트에 두 패턴을 각각 삽입하여 실제 실행 결과를 비교한다.
    """
    # python heredoc 본문: 항상 sys.exit(1) 하도록 mock
    mock_python_body = textwrap.dedent("""\
        import sys
        sys.exit(1)
    """)

    # Fix 후 패턴 (task-2571+2 적용)
    fixed_script = tmp_path / "test_fixed.sh"
    fixed_script.write_text(
        textwrap.dedent(f"""\
            #!/usr/bin/env bash
            python3 - <<'PYEOF' || {{ echo "[ERROR] stash-lifecycle dispatch failed (fatal)" >&2; exit 1; }}
{mock_python_body}
PYEOF
        """),
        encoding="utf-8",
    )
    fixed_script.chmod(fixed_script.stat().st_mode | stat.S_IEXEC)

    result_fixed = subprocess.run(
        ["bash", str(fixed_script)],
        capture_output=True,
        text=True,
    )
    assert result_fixed.returncode != 0, (
        "Fix B (fatal exit 전파) 실패: python sys.exit(1) 후 bash exit code가 0임.\n"
        f"stdout: {result_fixed.stdout!r}\n"
        f"stderr: {result_fixed.stderr!r}"
    )
    assert "fatal" in result_fixed.stderr.lower() or "ERROR" in result_fixed.stderr, (
        "Fix B: [ERROR] fatal 메시지가 stderr에 없음.\n"
        f"stderr: {result_fixed.stderr!r}"
    )

    # 대조: Fix 전 패턴 (warn only, exit 0)
    warn_script = tmp_path / "test_warn.sh"
    warn_script.write_text(
        textwrap.dedent(f"""\
            #!/usr/bin/env bash
            python3 - <<'PYEOF' || echo "[WARN] stash-lifecycle dispatch failed (non-fatal)"
{mock_python_body}
PYEOF
        """),
        encoding="utf-8",
    )
    warn_script.chmod(warn_script.stat().st_mode | stat.S_IEXEC)

    result_warn = subprocess.run(
        ["bash", str(warn_script)],
        capture_output=True,
        text=True,
    )
    # fix 전 패턴은 exit 0 이었음 (이 테스트가 그 갭을 문서화)
    assert result_warn.returncode == 0, (
        "대조 검증 이상: WARN only 패턴이 exit 0이 아님.\n"
        f"returncode: {result_warn.returncode}\n"
        f"stdout: {result_warn.stdout!r}"
    )


# ---------------------------------------------------------------------------
# Test #2: execute bit 없는 stash_audit.py에 대해 -f 가드는 통과, -x 가드는 차단
# ---------------------------------------------------------------------------

def test_file_guard_passes_without_execute_bit_x_guard_fails(tmp_path: Path) -> None:
    """
    Gemini MEDIUM 해소: stash_audit.py가 execute bit 없이 chmod 644로 존재할 때,
    Fix A (-f 가드)는 통과하고 구 패턴 (-x 가드)는 통과하지 못함을 검증한다.

    검증 방식:
    - tmp_path/scripts/stash_audit.py 생성 (chmod 644, execute bit 없음)
    - `-f` 조건 bash 가드 스크립트 실행 → then 블록 진입 확인
    - `-x` 조건 bash 가드 스크립트 실행 → then 블록 미진입 확인
    """
    # 더미 stash_audit.py 생성 (execute bit 없음)
    scripts_dir = tmp_path / "scripts"
    scripts_dir.mkdir(parents=True, exist_ok=True)
    dummy_py = scripts_dir / "stash_audit.py"
    dummy_py.write_text("# dummy stash_audit.py\n", encoding="utf-8")
    # 명시적으로 execute bit 제거 (chmod 644)
    dummy_py.chmod(0o644)

    # 실제로 execute bit가 없는지 확인
    mode = dummy_py.stat().st_mode
    assert not (mode & stat.S_IXUSR), (
        "전제 실패: execute bit 제거 안 됨 (chmod 644 이후에도 IXUSR 존재)"
    )

    workspace = str(tmp_path)

    # -f 가드 스크립트: Fix A 적용 후 패턴
    f_guard_script = tmp_path / "guard_f.sh"
    f_guard_script.write_text(
        textwrap.dedent(f"""\
            #!/usr/bin/env bash
            WORKSPACE="{workspace}"
            if [ -f "$WORKSPACE/scripts/stash_audit.py" ]; then
                echo "ENTERED_THEN"
            else
                echo "SKIPPED_ELSE"
            fi
        """),
        encoding="utf-8",
    )
    f_guard_script.chmod(f_guard_script.stat().st_mode | stat.S_IEXEC)

    result_f = subprocess.run(
        ["bash", str(f_guard_script)],
        capture_output=True,
        text=True,
    )
    assert result_f.returncode == 0, (
        f"-f 가드 스크립트 실행 실패: returncode={result_f.returncode}\n"
        f"stderr: {result_f.stderr!r}"
    )
    assert "ENTERED_THEN" in result_f.stdout, (
        "Fix A (-f 가드): execute bit 없는 stash_audit.py에 대해 then 블록 미진입.\n"
        f"stdout: {result_f.stdout!r}"
    )

    # -x 가드 스크립트: Fix A 적용 전 패턴 (회귀 대조)
    x_guard_script = tmp_path / "guard_x.sh"
    x_guard_script.write_text(
        textwrap.dedent(f"""\
            #!/usr/bin/env bash
            WORKSPACE="{workspace}"
            if [ -x "$WORKSPACE/scripts/stash_audit.py" ]; then
                echo "ENTERED_THEN"
            else
                echo "SKIPPED_ELSE"
            fi
        """),
        encoding="utf-8",
    )
    x_guard_script.chmod(x_guard_script.stat().st_mode | stat.S_IEXEC)

    result_x = subprocess.run(
        ["bash", str(x_guard_script)],
        capture_output=True,
        text=True,
    )
    assert result_x.returncode == 0, (
        f"-x 가드 스크립트 실행 실패: returncode={result_x.returncode}\n"
        f"stderr: {result_x.stderr!r}"
    )
    # execute bit 없으므로 -x는 else 블록으로 빠져야 함 (fix 전 문제 재현)
    assert "SKIPPED_ELSE" in result_x.stdout, (
        "대조 검증 이상: -x 가드가 execute bit 없는 파일에 대해 then 블록 진입.\n"
        f"stdout: {result_x.stdout!r}"
    )

    # 최종: -f 통과 / -x 차단 으로 두 동작 차이가 확인됨
    assert "ENTERED_THEN" in result_f.stdout and "SKIPPED_ELSE" in result_x.stdout, (
        "Fix A 효용 미입증: -f 통과 XOR -x 차단 패턴이 성립하지 않음."
    )


# ---------------------------------------------------------------------------
# 정적 검증: finish-task.sh 라인 1166에 -f 가드가 있고, -x 가드는 정확히 2건
# ---------------------------------------------------------------------------

def test_static_fix_a_line1166_uses_f_guard() -> None:
    """
    Fix A 정적 검증: finish-task.sh 라인 1166 영역(lifecycle dispatch 컨텍스트)에
    `-f` 가드가 존재하고, 해당 컨텍스트에 `-x` 가드가 없어야 한다.
    """
    text = FINISH_TASK_SH.read_text(encoding="utf-8")
    lines = text.splitlines()

    # lifecycle dispatch 마커 이후 첫 번째 if 가드 라인 찾기
    marker_idx: int | None = None
    for i, line in enumerate(lines):
        if LIFECYCLE_DISPATCH_MARKER in line:
            marker_idx = i
            break
    assert marker_idx is not None, f"마커 미발견: {LIFECYCLE_DISPATCH_MARKER!r}"

    guard_line: str | None = None
    for i in range(marker_idx, min(marker_idx + 10, len(lines))):
        if "stash_audit.py" in lines[i] and lines[i].strip().startswith("if ["):
            guard_line = lines[i]
            break
    assert guard_line is not None, (
        f"lifecycle dispatch 마커({marker_idx}) 이후 10라인 내 stash_audit.py 가드 미발견"
    )
    assert "[ -f " in guard_line, (
        f"Fix A 실패: lifecycle dispatch 가드에 `-f` 없음.\n"
        f"실제 가드 라인: {guard_line!r}"
    )
    assert "[ -x " not in guard_line, (
        f"Fix A 실패: lifecycle dispatch 가드에 여전히 `-x` 존재.\n"
        f"실제 가드 라인: {guard_line!r}"
    )


def test_static_fix_b_line1175_uses_fatal_exit() -> None:
    """
    Fix B 정적 검증: finish-task.sh lifecycle dispatch heredoc 호출 라인에
    fatal exit 1 패턴이 있어야 한다 (WARN echo 패턴은 없어야 함).
    """
    text = FINISH_TASK_SH.read_text(encoding="utf-8")
    marker_pos = text.find(LIFECYCLE_DISPATCH_MARKER)
    assert marker_pos != -1, f"마커 미발견: {LIFECYCLE_DISPATCH_MARKER!r}"

    # 마커 이후 ~200자 구간에서 heredoc 호출 라인 확인
    region = text[marker_pos: marker_pos + 1000]

    assert "exit 1" in region, (
        "Fix B 실패: lifecycle dispatch 영역에 `exit 1` 미존재.\n"
        f"region: {region[:300]!r}"
    )
    assert '[WARN] stash-lifecycle dispatch failed (non-fatal)' not in region, (
        "Fix B 실패: 구 WARN echo 패턴이 lifecycle dispatch 영역에 여전히 존재.\n"
        f"region: {region[:300]!r}"
    )


def test_static_x_guards_preserved_exactly_twice() -> None:
    """
    task-2576 TODO-4 완료 후: `-x ... stash_audit.py` 패턴이 0건이어야 한다.
    (라인 42, 1145 모두 -f 로 교체 완료 — -x는 완전 제거)
    -f 가드는 정확히 3건이어야 한다 (line 42, 1145, 1166).
    """
    import re
    content = FINISH_TASK_SH.read_text(encoding="utf-8")
    x_pattern = re.compile(r'if\s+\[\s+-x\s+.*stash_audit\.py')
    x_matches = x_pattern.findall(content)
    assert len(x_matches) == 0, (
        f"TODO-4 fix 후 `-x stash_audit.py` 가드가 0건이어야 하나 {len(x_matches)}건 잔존.\n"
        f"matches: {x_matches}"
    )
    f_pattern = re.compile(r'if\s+\[\s+-f\s+.*stash_audit\.py')
    f_matches = f_pattern.findall(content)
    assert len(f_matches) == 3, (
        f"`if [ -f ... stash_audit.py ]` 가드 수 기대 3건, 실제 {len(f_matches)}건.\n"
        f"matches: {f_matches}"
    )
