"""
regression: test_stash_origin_audit_tool.py
task: task-2570 TODO-4
작성자: 하누만(테스터)

검증 목표:
1. stash_audit.py 실행 권한 + py_compile 통과
2. --json 출력이 valid JSON인지 검증
3. JSON에 summary 키 + entries/stashes 리스트 필드 존재
4. --markdown 출력에 ## heading 최소 1개 존재
5. mocked stash 시나리오: tmp git repo → 패턴별 stash → 분류 결과 검증
"""

import json
import os
import py_compile
import re
import subprocess
import sys
from pathlib import Path

import pytest

WORKTREE_ROOT = Path(__file__).resolve().parents[2]
STASH_AUDIT_PY = WORKTREE_ROOT / "scripts" / "stash_audit.py"


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

def _run_audit(args: list, cwd: str | None = None) -> subprocess.CompletedProcess:
    """stash_audit.py 호출 helper."""
    cmd = [sys.executable, str(STASH_AUDIT_PY)] + args
    return subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        cwd=cwd or str(WORKTREE_ROOT),
    )


def _git(args: list, cwd: str, check: bool = True) -> subprocess.CompletedProcess:
    """git 명령 실행 helper (tmp repo 전용)."""
    env = os.environ.copy()
    env["GIT_AUTHOR_NAME"] = "test"
    env["GIT_AUTHOR_EMAIL"] = "test@test.com"
    env["GIT_COMMITTER_NAME"] = "test"
    env["GIT_COMMITTER_EMAIL"] = "test@test.com"
    return subprocess.run(
        ["git"] + args,
        cwd=cwd,
        capture_output=True,
        text=True,
        check=check,
        env=env,
    )


# ---------------------------------------------------------------------------
# 1. 실행 권한 + py_compile
# ---------------------------------------------------------------------------

def test_stash_audit_py_exists():
    """stash_audit.py 파일이 존재해야 한다."""
    assert STASH_AUDIT_PY.exists(), f"stash_audit.py 없음: {STASH_AUDIT_PY}"


def test_stash_audit_py_executable():
    """stash_audit.py에 실행 권한이 있어야 한다."""
    mode = STASH_AUDIT_PY.stat().st_mode
    # 소유자 실행 권한 (0o100) 또는 그룹/기타 실행 권한
    has_exec = bool(mode & 0o111)
    assert has_exec, (
        f"stash_audit.py에 실행 권한이 없음 (mode={oct(mode)})\n"
        f"파일: {STASH_AUDIT_PY}"
    )


def test_stash_audit_py_compile():
    """stash_audit.py가 py_compile로 문법 오류 없이 통과해야 한다."""
    try:
        py_compile.compile(str(STASH_AUDIT_PY), doraise=True)
    except py_compile.PyCompileError as exc:
        pytest.fail(f"stash_audit.py py_compile 실패: {exc}")


# ---------------------------------------------------------------------------
# 2. --json 출력 유효성 검증 (worktree 자체 사용)
# ---------------------------------------------------------------------------

def test_stash_audit_json_output_is_valid_json():
    """
    --json --workspace <worktree> 실행 시 출력이 valid JSON이어야 한다.
    실제 stash가 없어도 빈 결과 JSON이 반환되어야 한다.
    """
    result = _run_audit(["--json", "--workspace", str(WORKTREE_ROOT)])
    assert result.returncode == 0, (
        f"stash_audit.py 실행 실패 (exit={result.returncode})\n"
        f"stderr: {result.stderr}"
    )
    try:
        data = json.loads(result.stdout)
    except json.JSONDecodeError as exc:
        pytest.fail(
            f"--json 출력이 valid JSON이 아님: {exc}\n"
            f"stdout: {result.stdout[:500]}"
        )
    assert isinstance(data, dict), "--json 출력의 최상위 타입이 dict이어야 함"


# ---------------------------------------------------------------------------
# 3. JSON에 summary + entries 키 존재
# ---------------------------------------------------------------------------

def test_stash_audit_json_has_summary_key():
    """--json 출력에 'summary' 키가 있어야 한다."""
    result = _run_audit(["--json", "--workspace", str(WORKTREE_ROOT)])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    assert "summary" in data, (
        f"--json 출력에 'summary' 키가 없음\n키 목록: {list(data.keys())}"
    )


def test_stash_audit_json_has_entries_or_stashes_key():
    """
    --json 출력에 'entries' 또는 'stashes' 키가 있어야 한다.
    (entries가 실제 구현이지만 stashes도 동등한 구조로 허용)
    """
    result = _run_audit(["--json", "--workspace", str(WORKTREE_ROOT)])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    has_entries = "entries" in data
    has_stashes = "stashes" in data
    assert has_entries or has_stashes, (
        f"--json 출력에 'entries' 또는 'stashes' 키가 없음\n"
        f"키 목록: {list(data.keys())}"
    )


def test_stash_audit_json_entries_is_list():
    """--json 출력의 'entries' 또는 'stashes' 값이 리스트여야 한다."""
    result = _run_audit(["--json", "--workspace", str(WORKTREE_ROOT)])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", None))
    assert entries is not None, "'entries'/'stashes' 키가 없음"
    assert isinstance(entries, list), f"'entries' 값이 list가 아님: {type(entries)}"


def test_stash_audit_json_summary_has_total():
    """summary에 'total' 필드가 있어야 한다."""
    result = _run_audit(["--json", "--workspace", str(WORKTREE_ROOT)])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    summary = data.get("summary", {})
    assert "total" in summary, (
        f"summary에 'total' 필드가 없음\nsummary: {summary}"
    )


# ---------------------------------------------------------------------------
# 4. --markdown 출력에 ## heading 최소 1개
# ---------------------------------------------------------------------------

def test_stash_audit_markdown_has_heading():
    """--markdown 출력에 '## ' 형태의 heading이 최소 1개 있어야 한다."""
    result = _run_audit(["--markdown", "--workspace", str(WORKTREE_ROOT)])
    assert result.returncode == 0, (
        f"stash_audit.py --markdown 실패 (exit={result.returncode})\n"
        f"stderr: {result.stderr}"
    )
    heading_pattern = re.compile(r"^## ", re.MULTILINE)
    assert heading_pattern.search(result.stdout), (
        f"--markdown 출력에 '## ' heading이 없음\n"
        f"stdout(앞 300자): {result.stdout[:300]}"
    )


# ---------------------------------------------------------------------------
# 5. mocked stash 시나리오 (tmp_path isolated git repo)
# ---------------------------------------------------------------------------

@pytest.fixture()
def mock_git_repo(tmp_path):
    """
    격리된 tmp git repo 생성 + 다양한 패턴의 stash push.
    실제 워크스페이스 stash에는 절대 손대지 않음.
    """
    repo = str(tmp_path)

    # 기본 git 초기화
    _git(["init", "-b", "main"], cwd=repo)
    _git(["config", "user.email", "test@test.com"], cwd=repo)
    _git(["config", "user.name", "test"], cwd=repo)
    _git(["commit", "--allow-empty", "-m", "init: initial commit"], cwd=repo)

    def stash_push(message: str, filename: str | None = None):
        """dirty 파일 생성 후 stash push."""
        fname = filename or "dirty.txt"
        fpath = tmp_path / fname
        fpath.write_text(f"dirty content for {message}\n", encoding="utf-8")
        _git(["add", fname], cwd=repo)
        _git(["stash", "push", "-m", message], cwd=repo)

    # 패턴 1: pre-task
    stash_push("pre-task-9999 stash: dirty before task start", "f1.txt")
    # 패턴 2: quarantine (finish-task-quarantine)
    stash_push("task-9999-finish-task-quarantine: scope isolation", "f2.txt")
    # 패턴 3: other-files
    stash_push("task-9998-other-files-stash: unrelated dirty files", "f3.txt")
    # 패턴 4: finish-task
    stash_push("task-9997-finish-task GIT-GATE 격리", "f4.txt")
    # 패턴 5: unknown (패턴 매칭 안 됨)
    stash_push("random stash with no pattern 12345", "f5.txt")

    return repo


def test_mock_stash_audit_runs_successfully(mock_git_repo):
    """mock git repo에서 stash_audit.py가 성공적으로 실행되어야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0, (
        f"stash_audit.py 실행 실패\nstderr: {result.stderr}"
    )


def test_mock_stash_audit_detects_all_stashes(mock_git_repo):
    """mock git repo의 5개 stash가 모두 감지되어야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    assert len(entries) == 5, (
        f"감지된 stash 수가 5개가 아님: {len(entries)}\n"
        f"entries: {json.dumps(entries, ensure_ascii=False, indent=2)}"
    )


def test_mock_stash_pretask_classified(mock_git_repo):
    """'pre-task-9999 stash' 메시지가 pre-task로 분류되어야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    sources = [e.get("source") for e in entries]
    assert "pre-task" in sources, (
        f"pre-task 분류가 없음\nsources: {sources}"
    )


def test_mock_stash_quarantine_classified(mock_git_repo):
    """'finish-task-quarantine' 패턴이 quarantine으로 분류되어야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    sources = [e.get("source") for e in entries]
    assert "quarantine" in sources, (
        f"quarantine 분류가 없음\nsources: {sources}"
    )


def test_mock_stash_other_files_classified(mock_git_repo):
    """'other-files-stash' 패턴이 other-files로 분류되어야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    sources = [e.get("source") for e in entries]
    assert "other-files" in sources, (
        f"other-files 분류가 없음\nsources: {sources}"
    )


def test_mock_stash_finish_task_classified(mock_git_repo):
    """'finish-task' 패턴이 finish-task로 분류되어야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    sources = [e.get("source") for e in entries]
    assert "finish-task" in sources, (
        f"finish-task 분류가 없음\nsources: {sources}"
    )


def test_mock_stash_unknown_classified(mock_git_repo):
    """패턴 없는 stash가 unknown으로 분류되어야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    sources = [e.get("source") for e in entries]
    assert "unknown" in sources, (
        f"unknown 분류가 없음\nsources: {sources}"
    )


def test_mock_stash_at_least_three_categories_present(mock_git_repo):
    """mock stash 결과에 최소 3개의 서로 다른 source 카테고리가 존재해야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    unique_sources = set(e.get("source") for e in entries if e.get("source"))
    assert len(unique_sources) >= 3, (
        f"source 카테고리가 {len(unique_sources)}개만 존재 (최소 3개 필요)\n"
        f"발견된 카테고리: {unique_sources}"
    )


def test_mock_stash_entry_has_required_fields(mock_git_repo):
    """각 stash 항목이 필수 필드를 포함해야 한다."""
    required_fields = ["index", "source", "reason", "caller_script", "raw_message"]
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    assert len(entries) > 0, "entries가 비어있음"

    for entry in entries:
        for field in required_fields:
            assert field in entry, (
                f"entry에 '{field}' 필드가 없음\n"
                f"entry: {json.dumps(entry, ensure_ascii=False)}"
            )


def test_mock_stash_summary_count_by_source(mock_git_repo):
    """summary.count_by_source가 존재하고 총합이 entries 수와 일치해야 한다."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    summary = data.get("summary", {})

    count_by_source = summary.get("count_by_source", {})
    assert isinstance(count_by_source, dict), "count_by_source가 dict가 아님"

    total_from_counts = sum(count_by_source.values())
    assert total_from_counts == len(entries), (
        f"count_by_source 합계({total_from_counts}) != entries 수({len(entries)})"
    )


def test_mock_stash_pretask_task_id_extracted(mock_git_repo):
    """pre-task stash의 task_id가 추출되거나 None이어야 한다 (타입 검증)."""
    result = _run_audit(["--json", "--workspace", mock_git_repo])
    assert result.returncode == 0
    data = json.loads(result.stdout)
    entries = data.get("entries", data.get("stashes", []))
    for entry in entries:
        # task_id는 str 또는 None이어야 함
        task_id = entry.get("task_id")
        assert task_id is None or isinstance(task_id, str), (
            f"task_id가 str 또는 None이 아님: {type(task_id)}, 값: {task_id}"
        )
