"""
task-2576 TODO-4 신규 회귀: stash_audit.py existence guard 정합화 (-x → -f, 3 위치).

배경:
- python3 invoke이므로 execute bit (-x) 의미 없음. `-f` (file 존재) 가드가 정합.
- PR #125 (attempt-3) 에서 line 1166만 -f 로 수정. line 42, 1145 는 -x 잔존.
- task-2576 TODO-4: 3 위치 모두 -f 로 통일.

검증:
1. -x stash_audit.py 패턴 0건
2. -f stash_audit.py 패턴 정확히 3건 (line 42, 1145, 1166)
3. (선택) 동적: 실행 권한 없는 stash_audit.py 도 -f 분기 통과
"""
from __future__ import annotations

import re
import stat
import subprocess
from pathlib import Path

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

# 예상 -f stash_audit.py 가드 수
EXPECTED_F_GUARD_COUNT = 3

# 가드 라인 번호 허용 범위 (방어적 검증)
LINE_RANGE_MIN = 30
LINE_RANGE_MAX = 1200


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _read_sh() -> str:
    return FINISH_TASK_SH.read_text(encoding="utf-8")


def _read_sh_lines() -> list[str]:
    return FINISH_TASK_SH.read_text(encoding="utf-8").splitlines()


def _find_pattern_lines(pattern: str, content_lines: list[str]) -> list[int]:
    """주어진 패턴과 일치하는 라인 번호(1-based) 목록 반환."""
    compiled = re.compile(pattern)
    return [i + 1 for i, line in enumerate(content_lines) if compiled.search(line)]


# ---------------------------------------------------------------------------
# Test 1: -x stash_audit.py 패턴 0건
# ---------------------------------------------------------------------------

class TestNoMinusXGuard:
    """TODO-4 회귀 — -x stash_audit.py 패턴이 0건이어야 한다."""

    def test_minus_x_stash_audit_absent(self):
        """
        finish-task.sh 전체에서 '[ -x ... stash_audit.py' 패턴이 0건이어야 한다.
        python3 로 invoke 하는 스크립트이므로 execute bit 의미 없음.
        """
        content = _read_sh()
        matches = re.findall(r'\[\s+-x\s+.*stash_audit\.py', content)
        assert len(matches) == 0, (
            f"TODO-4 회귀 감지: -x stash_audit.py 패턴 {len(matches)}건 발견.\n"
            f"발견 내용: {matches}\n"
            "fix: -x → -f 로 교체 필수 (python3 invoke이므로 execute bit 불필요)"
        )

    def test_minus_x_guard_not_in_any_if_block(self):
        """
        'if [ -x' 형태로 stash_audit.py 가드를 체크하는 패턴이 없어야 한다.
        """
        content = _read_sh()
        # if [ -x "$..." ] 형태도 포함
        matches = re.findall(r'if\s+\[\s+-x\s+["\']?\$.*stash_audit\.py', content)
        assert len(matches) == 0, (
            f"TODO-4 회귀: if [ -x ] stash_audit.py 패턴 {len(matches)}건 발견: {matches}"
        )


# ---------------------------------------------------------------------------
# Test 2: -f stash_audit.py 패턴 정확히 3건
# ---------------------------------------------------------------------------

class TestExactlyThreeMinusFGuards:
    """TODO-4 fix 박제 — -f stash_audit.py 가드가 정확히 3건이어야 한다."""

    def test_minus_f_stash_audit_count_is_three(self):
        """
        finish-task.sh 에서 '[ -f ... stash_audit.py' 패턴이 정확히 3건이어야 한다.
        (line 42, 1145, 1166 인근 3개 위치)
        """
        content = _read_sh()
        matches = re.findall(r'\[\s+-f\s+["\']?\$?.*stash_audit\.py', content)
        assert len(matches) == EXPECTED_F_GUARD_COUNT, (
            f"TODO-4 fix 위반: -f stash_audit.py 패턴이 {len(matches)}건 (기대: {EXPECTED_F_GUARD_COUNT}건).\n"
            f"발견 내용: {matches}\n"
            f"예상 위치: line 42, 1145, 1166 인근"
        )

    def test_specific_f_guard_string_appears_thrice(self):
        """
        '[ -f \"$WORKSPACE/scripts/stash_audit.py\" ]' 문자열이 정확히 3번 나타나야 한다.
        (TODO-4 fix 정확한 문자열 박제)
        """
        content = _read_sh()
        # 가능한 변형 허용: $WORKSPACE 또는 다른 변수명
        pattern = r'if\s+\[\s+-f\s+"?\$\w+/scripts/stash_audit\.py"?\s*\]'
        matches = re.findall(pattern, content)
        assert len(matches) == EXPECTED_F_GUARD_COUNT, (
            f"TODO-4 fix 위반: if [ -f .../stash_audit.py ] 패턴이 {len(matches)}건 (기대: {EXPECTED_F_GUARD_COUNT}건).\n"
            f"발견: {matches}"
        )


# ---------------------------------------------------------------------------
# Test 3: 라인 번호 박제 (3 위치 모두 허용 범위 내)
# ---------------------------------------------------------------------------

class TestFGuardLineNumbers:
    """TODO-4 fix 박제 — -f 가드 3건의 라인 번호가 예상 범위 내에 있어야 한다."""

    def test_f_guard_lines_in_expected_range(self):
        """
        -f stash_audit.py 가드의 라인 번호가 모두 LINE_RANGE_MIN < line < LINE_RANGE_MAX
        범위 내에 있어야 한다.
        """
        lines = _read_sh_lines()
        guard_lines = _find_pattern_lines(
            r'\[\s+-f\s+["\']?\$?.*stash_audit\.py',
            lines,
        )
        assert len(guard_lines) == EXPECTED_F_GUARD_COUNT, (
            f"TODO-4: -f 가드 라인 수 불일치 ({len(guard_lines)}건, 기대 {EXPECTED_F_GUARD_COUNT}건). "
            f"라인: {guard_lines}"
        )
        for lineno in guard_lines:
            assert LINE_RANGE_MIN < lineno < LINE_RANGE_MAX, (
                f"TODO-4: -f 가드 라인 {lineno} 이 예상 범위({LINE_RANGE_MIN}~{LINE_RANGE_MAX}) 밖.\n"
                "finish-task.sh 구조 변경 확인 필요."
            )

    def test_f_guard_lines_cover_early_and_late_positions(self):
        """
        -f 가드 3건이 스크립트 앞부분(< 100) 과 뒷부분(> 1000) 에 분포해야 한다.
        (line 42 는 early, line 1145/1166 은 late — 분산 검증)
        """
        lines = _read_sh_lines()
        guard_lines = _find_pattern_lines(
            r'\[\s+-f\s+["\']?\$?.*stash_audit\.py',
            lines,
        )
        assert len(guard_lines) == EXPECTED_F_GUARD_COUNT

        early = [l for l in guard_lines if l < 200]
        late = [l for l in guard_lines if l > 1000]

        assert len(early) >= 1, (
            f"TODO-4: line 42 인근 early -f 가드 없음. 발견 라인: {guard_lines}"
        )
        assert len(late) >= 2, (
            f"TODO-4: line 1145/1166 인근 late -f 가드 2건 미만. 발견 라인: {guard_lines}"
        )


# ---------------------------------------------------------------------------
# Test 4: execute-bit 의존 제거 박제 (메타 검증)
# ---------------------------------------------------------------------------

class TestExecuteBitDependencyRemoved:
    """TODO-4 doctrine 박제 — python3 invoke이므로 execute bit 의존 제거 확인."""

    def test_no_chmod_x_stash_audit_dependency(self):
        """
        finish-task.sh 내에서 stash_audit.py 에 chmod +x 를 부여하거나
        execute bit 를 요구하는 패턴이 없어야 한다.
        """
        content = _read_sh()
        # chmod +x stash_audit.py 형태의 명령이 없어야 함
        pattern = r'chmod\s+\+x.*stash_audit\.py'
        matches = re.findall(pattern, content)
        assert len(matches) == 0, (
            f"TODO-4 의존 잔존: chmod +x stash_audit.py 패턴 {len(matches)}건 발견: {matches}\n"
            "python3 로 invoke 하므로 execute bit 불필요."
        )

    def test_python3_invoke_pattern_present(self):
        """
        finish-task.sh 내에서 'python3 ... stash_audit.py' 형태의 invoke 가
        존재해야 한다 (execute bit 없이 직접 호출 방식 확인).
        """
        content = _read_sh()
        pattern = r'python3\s+["\']?\$?\w+.*stash_audit\.py'
        matches = re.findall(pattern, content)
        assert len(matches) >= 1, (
            "TODO-4: python3 stash_audit.py invoke 패턴 없음.\n"
            "stash_audit.py 는 python3 로 invoke 되어야 함."
        )

    def test_stash_audit_not_invoked_directly_as_executable(self):
        """
        './stash_audit.py' 또는 '$WORKSPACE/scripts/stash_audit.py' 를 python3 없이
        직접 실행하는 패턴이 없어야 한다.
        """
        content = _read_sh()
        # 더 단순한 패턴으로 검증
        lines = content.splitlines()
        violations = []
        for i, line in enumerate(lines, 1):
            stripped = line.strip()
            # 'stash_audit.py' 가 있는 라인에서 python3 없이 시작하는 경우
            if 'stash_audit.py' in stripped:
                if stripped.startswith('$') and 'stash_audit.py' in stripped:
                    violations.append((i, stripped))
        assert len(violations) == 0, (
            f"TODO-4 경고: stash_audit.py 직접 실행 패턴 발견: {violations}"
        )


# ---------------------------------------------------------------------------
# Test 5: 동적 검증 — 실행 권한 없는 stash_audit.py 도 -f 분기 통과
# ---------------------------------------------------------------------------

class TestMinusFPassesWithoutExecuteBit:
    """
    TODO-4 동적 회귀: 실행 권한 없는 stash_audit.py 를 -f 로 체크하면 통과해야 한다.
    """

    def test_f_check_passes_without_execute_permission(self, tmp_path):
        """
        임시 디렉토리에 chmod 644 (실행 권한 없음) 의 stash_audit.py 생성 후
        bash -c '[ -f path ]' 가 성공(exit 0) 해야 한다.
        """
        fake_script = tmp_path / "stash_audit.py"
        fake_script.write_text("# fake stash_audit.py\n", encoding="utf-8")
        # 실행 권한 제거 (rw-r--r--)
        fake_script.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)

        # -f 체크: 실행 권한 없어도 통과해야 함
        result_f = subprocess.run(
            ["bash", "-c", f'[ -f "{fake_script}" ] && echo "PASS" || echo "FAIL"'],
            capture_output=True,
            text=True,
        )
        assert "PASS" in result_f.stdout, (
            f"-f 체크가 실행 권한 없는 파일에 대해 실패함. stdout={result_f.stdout!r}"
        )

    def test_x_check_fails_without_execute_permission(self, tmp_path):
        """
        대조군: -x 체크는 실행 권한 없는 파일에 대해 실패해야 한다.
        (따라서 -x 는 python3 invoke 스크립트 가드에 부적합함을 실증)
        """
        fake_script = tmp_path / "stash_audit.py"
        fake_script.write_text("# fake stash_audit.py\n", encoding="utf-8")
        # 실행 권한 제거
        fake_script.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)

        # -x 체크: 실행 권한 없으면 실패
        result_x = subprocess.run(
            ["bash", "-c", f'[ -x "{fake_script}" ] && echo "PASS" || echo "FAIL"'],
            capture_output=True,
            text=True,
        )
        assert "FAIL" in result_x.stdout, (
            f"-x 체크가 실행 권한 없는 파일에 대해 통과함 — 예상과 다름. stdout={result_x.stdout!r}"
        )

    def test_finish_task_uses_f_not_x_for_stash_audit_guard(self):
        """
        finish-task.sh 이 실제로 -f 를 사용하므로, 실행 권한 없는 stash_audit.py 도
        분기를 탈 수 있음을 정적 + 동적 조합으로 최종 박제.
        """
        content = _read_sh()
        # -f 가드 존재 확인 (정적)
        f_count = len(re.findall(r'\[\s+-f\s+["\']?\$?.*stash_audit\.py', content))
        x_count = len(re.findall(r'\[\s+-x\s+["\']?\$?.*stash_audit\.py', content))

        assert f_count == EXPECTED_F_GUARD_COUNT, (
            f"TODO-4: -f 가드 {f_count}건 (기대 {EXPECTED_F_GUARD_COUNT}건)"
        )
        assert x_count == 0, (
            f"TODO-4: -x 가드 {x_count}건 (기대 0건)"
        )
