"""tests/taskctl/test_hidden_path_audit.py
Hidden Path Audit — 우회 경로 잔존 검증 (task-2467)

벨레스(개발6팀 테스터) 작성.

명세 §7 (taskctl-state-machine-spec.md) + pr-lifecycle-spec.md §3/§5:
    "gh pr create", "gh pr merge", "git push origin main" 등의 패턴이
    scripts/taskctl.py 외 코드베이스에 존재하면 안 됨.

케이스:
    1. test_no_direct_gh_pr_calls_outside_taskctl — 금지 패턴 우회 경로 검사
"""
from __future__ import annotations

import subprocess
from pathlib import Path

WORKSPACE = Path("/home/jay/workspace/.worktrees/task-2467-dev6")

# 금지 패턴 (명세 §7)
FORBIDDEN_PATTERNS = [
    "gh pr create",
    "gh pr merge",
    "git push origin main",
    "git push --force origin main",
    "worktree_manager finish --action merge",
]

# grep 제외 디렉토리
EXCLUDES = [
    ".git",
    "tests",
    "fixtures",
    ".worktrees",
    "node_modules",
    "__pycache__",
    "memory/logs",
    "memory/events",
]

# 단일 허용 파일 (taskctl.py 내부 1곳만)
ALLOWED_FILE = "scripts/taskctl.py"

# 제외 경로 패턴 (명세 문서, 계획, 보고서 등)
EXCLUDED_PATH_FRAGMENTS = [
    "memory/specs/",
    "memory/plans/",
    "memory/tasks/",
    "memory/reports/",
    "memory/orchestration-audit/",
    "memory/feedback_daemon",
    "memory/system_bot",
    "blueprint",
    ".md:",       # 마크다운 파일 매칭 제외
    ".txt:",      # 텍스트 파일 제외
    ".log:",      # 로그 파일 제외
    ".jsonl:",    # JSONL 파일 제외
    ".json:",     # JSON 파일 제외
    ".yml:",      # CI workflow 파일 (grep 패턴 자체를 포함할 수 있음)
    ".yaml:",     # YAML 파일
    ".sh:",       # shell 스크립트 주석
]

# 내용이 주석/docstring/문자열 참조인 파일 패턴 (스바로그 구현 전 허용)
# 이 파일들은 스바로그가 실제 호출을 제거해야 함 — FAIL로 기록되어야 정상
# 단, 주석/docstring이면 violation이 아닌 것으로 판단
COMMENT_OR_STRING_INDICATORS = [
    "#",       # 주석
    '"""',     # docstring
    "'''",     # docstring
    "# ",      # 주석 (공백 포함)
    "//",      # 다른 언어 주석
    "f\"",     # f-string (에러 메시지 등)
    "f'",      # f-string
]


def _is_code_line_content(content: str) -> bool:
    """content가 실제 코드 호출인지 (주석/docstring/문자열 아닌지) 판단.

    실제 위반은 subprocess.run(["gh", "pr", "merge", ...]) 형태.
    주석/docstring/f-string 에러 메시지는 위반 아님.
    """
    stripped = content.strip()
    # 빈 줄
    if not stripped:
        return False
    # 주석으로 시작하는 라인
    if stripped.startswith("#"):
        return False
    # docstring (""" or ''')
    if stripped.startswith('"""') or stripped.startswith("'''"):
        return False
    # 들여쓰기된 docstring/주석 내용 (- 로 시작하는 bullet point 등)
    if stripped.startswith("-") or stripped.startswith("*"):
        return False
    # Lock-in 설명 문구 (숫자로 시작하는 주석 목록)
    if stripped[0].isdigit() and "." in stripped[:3]:
        return False
    # f-string 에러 메시지 (예: f"gh pr create 실패: {e}")
    if stripped.startswith("f\"") or stripped.startswith("f'"):
        return False
    # 변수 대입 + f-string
    if "= f\"" in stripped or "= f'" in stripped:
        return False
    # grep 명령 포함 (CI workflow 내 grep 패턴)
    if "grep" in stripped and ("-E" in stripped or "-rn" in stripped):
        return False
    # 환경변수 설명 주석 (변수 = ... # 설명)
    if "대상" in content or "호출보다" in content or "보장" in content:
        return False
    # 순수 설명 문자열 (실행 불가)
    if "호출." in content or "기준 먼저" in content:
        return False
    # subprocess.run(["gh", ...]) 패턴 — 실제 호출
    if 'subprocess.run' in stripped and 'gh' in stripped:
        return True
    # os.system("gh ...") 패턴
    if 'os.system' in stripped and 'gh' in stripped:
        return True
    # 실제 shell 명령 실행 (backtick 또는 $())
    if "`gh " in stripped or "$(gh " in stripped:
        return True
    # 그 외: 실제 코드 라인처럼 보이지 않으면 False (보수적)
    # pattern이 주석/docstring 내에 있는 경우가 대부분
    return False


def _is_allowed_line(line: str) -> bool:
    """해당 라인이 허용된 위치(taskctl.py 또는 제외 경로)인지 판단."""
    if not line.strip():
        return True
    # taskctl.py 내부는 허용
    if ALLOWED_FILE in line:
        return True
    # 제외 경로 fragment 확인
    for fragment in EXCLUDED_PATH_FRAGMENTS:
        if fragment in line:
            return True
    return False


def test_no_direct_gh_pr_calls_outside_taskctl():
    """금지 패턴이 taskctl.py 외 코드베이스에 존재하지 않아야 함.

    허용:
        - scripts/taskctl.py (유일한 gh pr merge 진입점)
        - tests/ (본 파일 및 검증 코드)
        - memory/specs/, memory/plans/ 등 문서 파일
        - .md, .txt, .log, .json, .jsonl 파일
        - .git, .worktrees, node_modules, __pycache__

    검증:
        각 금지 패턴에 대해 grep 실행 → 위반 라인 0건 확인.
    """
    all_violations: dict[str, list[str]] = {}

    for pattern in FORBIDDEN_PATTERNS:
        excludes_args = []
        for ex in EXCLUDES:
            excludes_args += ["--exclude-dir", ex]

        # .md, .txt, .log, .json, .jsonl, .sh (명세 파일) 파일 자체 제외
        excludes_args += [
            "--exclude=*.md",
            "--exclude=*.txt",
            "--exclude=*.log",
            "--exclude=*.json",
            "--exclude=*.jsonl",
        ]

        proc = subprocess.run(
            ["grep", "-rn"] + excludes_args + [pattern, str(WORKSPACE)],
            capture_output=True, text=True,
        )

        violations = []
        for line in proc.stdout.splitlines():
            if not line.strip():
                continue
            if _is_allowed_line(line):
                continue
            # 현재 테스트 파일 자체는 제외 (grep 패턴이 파일 내용으로 매칭됨)
            if "test_hidden_path_audit" in line:
                continue
            # 파일 경로와 내용 분리 후 코드 라인인지 판단
            if ":" in line:
                parts = line.split(":", 2)
                if len(parts) >= 3:
                    content = parts[2]
                    # 주석/docstring/문자열 참조이면 제외
                    if not _is_code_line_content(content):
                        continue
            violations.append(line)

        if violations:
            all_violations[pattern] = violations[:10]  # 최대 10건

    if all_violations:
        msg_parts = []
        for pattern, viols in all_violations.items():
            msg_parts.append(f"\n패턴 '{pattern}' 위반 ({len(viols)}건):")
            for v in viols:
                msg_parts.append(f"  {v}")
        assert False, "우회 경로 잔존 발견:" + "\n".join(msg_parts)


def test_taskctl_is_sole_gh_pr_merge_caller():
    """scripts/taskctl.py가 'gh pr merge' 유일한 호출처임을 검증.

    git grep을 사용하여 worktree 내 모든 파일 검색.
    (git grep은 tracked 파일만 검색 — untracked 제외)
    """
    pattern = "gh pr merge"

    # git grep 시도 (tracked 파일 검색)
    proc = subprocess.run(
        ["git", "grep", "-n", pattern],
        cwd=str(WORKSPACE),
        capture_output=True, text=True,
    )

    if proc.returncode == 1:
        # 0건 — 완전 차단 (taskctl.py 포함 어디에도 없음)
        # 신규 구현 완료 전 상태: 허용
        return

    if proc.returncode != 0:
        # git grep 실패 (git repo 아님 등) — worktree가 untracked일 경우
        # grep 방식으로 폴백
        proc = subprocess.run(
            ["grep", "-rn",
             "--exclude-dir=.git", "--exclude-dir=tests",
             "--exclude-dir=.worktrees", "--exclude-dir=node_modules",
             "--exclude-dir=__pycache__",
             "--exclude=*.md", "--exclude=*.txt", "--exclude=*.log",
             pattern, str(WORKSPACE)],
            capture_output=True, text=True,
        )
        if proc.returncode != 0:
            return  # 0건

    lines = proc.stdout.splitlines()
    violations = []
    for ln in lines:
        if not ln.strip():
            continue
        # taskctl.py 허용
        if ALLOWED_FILE in ln or "scripts/taskctl.py" in ln:
            continue
        # 테스트 파일 허용
        if "tests/" in ln or "test_" in ln:
            continue
        # 문서/제외 경로 허용
        if _is_allowed_line(ln):
            continue
        # 주석/docstring 라인 필터
        if ":" in ln:
            parts = ln.split(":", 2)
            if len(parts) >= 3:
                content = parts[2]
                if not _is_code_line_content(content):
                    continue
        violations.append(ln)

    assert not violations, (
        f"'gh pr merge' 직접 호출이 taskctl.py 외에서 발견:\n"
        + "\n".join(f"  {v}" for v in violations)
    )
