"""
git_evidence.py - git 커밋 증거 검증 verifier
task-2031: 코드 커밋 없이 .done 생성 방지

3가지 검증:
1. COMMIT_EXISTS: task ID가 포함된 커밋 최소 1건
2. NO_UNCOMMITTED: uncommitted 변경 없음
3. NON_EMPTY_COMMIT: 마지막 커밋이 빈 커밋이 아님

non-code task (문서만/리서치만) → SKIP
"""

import os
import re
import subprocess

# 시스템이 자동으로 생성/수정하는 파일 패턴 (git_evidence 검사에서 제외)
SYSTEM_AUTO_FILES = [
    "memory/heartbeats/",
    "memory/events/",
    "logs/",
    "memory/daily/",
    "whisper/",
    "bot-activity.json",
    "token-ledger.json",
    "memory/pipeline-status.json",
    "memory/preview-state.json",
    "memory/merge-log.json",
    "memory/bot_settings_sync.json",
    "memory/memory-check-log.json",
    "dashboard/data/refine-status.json",
    "dashboard/data/refine-history.json",
    ".heartbeat",
    "memory/.task-counter",
    "memory/task-timers.json",
    "memory/logs/",
    "memory/reports/",
    "memory/tasks/",
]


def _is_system_auto_file(filepath: str) -> bool:
    """시스템이 자동 생성/수정하는 파일인지 판별."""
    normalized = filepath.replace("\\", "/")
    for pattern in SYSTEM_AUTO_FILES:
        if pattern.startswith("."):
            # 확장자 패턴 (예: .heartbeat)
            if normalized.endswith(pattern):
                return True
        elif pattern.endswith("/"):
            # 디렉토리 패턴 (예: memory/heartbeats/)
            if pattern in normalized or normalized.startswith(pattern):
                return True
        else:
            # 파일명 패턴 (예: bot-activity.json)
            if normalized.endswith("/" + pattern) or normalized == pattern:
                return True
    return False


def _is_non_code_task(task_id: str, workspace_root: str) -> bool:
    """task 파일의 ## 레벨 섹션에서 non-code 키워드 판별."""
    task_path = os.path.join(workspace_root, "memory", "tasks", f"{task_id}.md")
    if not os.path.isfile(task_path):
        return False
    try:
        with open(task_path, "r", encoding="utf-8") as f:
            content = f.read()
        # ## 레벨 섹션 내에서만 검사 (본문 설명 텍스트 오탐 방지)
        m = re.search(r"## 레벨\s*\n(.*?)(?=\n## |\Z)", content, re.DOTALL)
        if not m:
            return False
        section = m.group(1)
        return bool(re.search(r"코드 수정 없음|문서 업데이트만|문서만|리서치만", section))
    except OSError:
        return False


def _run_git(args: list[str], cwd: str) -> subprocess.CompletedProcess:
    """git 명령 실행 헬퍼."""
    return subprocess.run(
        ["git"] + args,
        cwd=cwd,
        capture_output=True,
        text=True,
        timeout=30,
    )


def _find_project_root(workspace_root: str) -> str:
    """git rev-parse로 프로젝트 루트 탐지."""
    try:
        result = _run_git(["rev-parse", "--show-toplevel"], workspace_root)
        if result.returncode == 0:
            return result.stdout.strip()
    except (subprocess.TimeoutExpired, OSError):
        pass
    return workspace_root


def verify(task_id: str, workspace_root: str = "/home/jay/workspace") -> dict:
    """
    git 커밋 증거 검증.

    Returns:
        {"status": "PASS"|"FAIL"|"SKIP", "details": [...]}
    """
    # non-code task → SKIP
    if _is_non_code_task(task_id, workspace_root):
        return {
            "status": "SKIP",
            "details": ["non-code task (문서/리서치) — git 검증 SKIP"],
        }

    proj_dir = _find_project_root(workspace_root)
    details = []
    failed = []

    # 1) COMMIT_EXISTS: task ID 커밋 최소 1건
    try:
        result = _run_git(["log", "--oneline", "--all", f"--grep={task_id}"], proj_dir)
        lines = [l for l in result.stdout.strip().splitlines() if l.strip()]
        commit_count = len(lines)
        if commit_count == 0:
            details.append(f"FAIL COMMIT_EXISTS: {task_id} 커밋 0건")
            failed.append("COMMIT_EXISTS")
        else:
            details.append(f"PASS COMMIT_EXISTS: {task_id} 커밋 {commit_count}건")
    except (subprocess.TimeoutExpired, OSError) as e:
        details.append(f"FAIL COMMIT_EXISTS: git 명령 실패 — {e}")
        failed.append("COMMIT_EXISTS")

    # 2) NO_UNCOMMITTED: uncommitted 변경 없음 (시스템 자동 파일 제외)
    try:
        diff_result = _run_git(["diff", "--name-only"], proj_dir)
        cached_result = _run_git(["diff", "--cached", "--name-only"], proj_dir)
        diff_files = [f for f in diff_result.stdout.strip().splitlines() if f.strip()]
        cached_files = [f for f in cached_result.stdout.strip().splitlines() if f.strip()]
        # 시스템 자동 파일 제외
        real_diff = [f for f in diff_files if not _is_system_auto_file(f)]
        real_cached = [f for f in cached_files if not _is_system_auto_file(f)]
        excluded_count = len(diff_files) + len(cached_files) - len(real_diff) - len(real_cached)
        if real_diff or real_cached:
            details.append(f"FAIL NO_UNCOMMITTED: uncommitted 변경 존재 ({len(real_diff)} unstaged, {len(real_cached)} staged)")
            failed.append("NO_UNCOMMITTED")
        else:
            msg = "PASS NO_UNCOMMITTED: uncommitted 변경 없음"
            if excluded_count > 0:
                msg += f" (시스템 자동 파일 {excluded_count}건 제외)"
            details.append(msg)
    except (subprocess.TimeoutExpired, OSError) as e:
        details.append(f"FAIL NO_UNCOMMITTED: git 명령 실패 — {e}")
        failed.append("NO_UNCOMMITTED")

    # 3) NON_EMPTY_COMMIT: 빈 커밋 방어
    try:
        hash_result = _run_git(
            ["log", "--all", f"--grep={task_id}", "--format=%H", "-1"], proj_dir
        )
        last_hash = hash_result.stdout.strip()
        if last_hash:
            diff_files_result = _run_git(
                ["diff", "--name-only", f"{last_hash}^..{last_hash}"], proj_dir
            )
            file_lines = [l for l in diff_files_result.stdout.strip().splitlines() if l.strip()]
            diff_file_count = len(file_lines)
            if diff_file_count == 0:
                details.append("FAIL NON_EMPTY_COMMIT: 빈 커밋(변경 파일 0건)")
                failed.append("NON_EMPTY_COMMIT")
            else:
                details.append(f"PASS NON_EMPTY_COMMIT: 변경 파일 {diff_file_count}건")
        else:
            details.append("SKIP NON_EMPTY_COMMIT: task ID 커밋 없음 (COMMIT_EXISTS에서 처리)")
    except (subprocess.TimeoutExpired, OSError) as e:
        details.append(f"FAIL NON_EMPTY_COMMIT: git 명령 실패 — {e}")
        failed.append("NON_EMPTY_COMMIT")

    if failed:
        return {"status": "FAIL", "details": details, "failed_checks": failed}

    return {"status": "PASS", "details": details}
