#!/usr/bin/env python3
"""
test_stash_lifecycle_quarantine.py
task: task-2571 TODO-7 T-3

검증 목표:
- spec §2 unknown 분기 정책 검증
- unknown stash 는 항상 preserved
- wip stash 는 항상 preserved
- pre-task stash 는 dry-run 시 dry-run-pop
- skipped_unknown_count 검증

작성자: 하누만 (개발4팀 QA)
"""

import json
import os
import shutil
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path

import pytest

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


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

def _init_temp_repo() -> str:
    """격리된 임시 git repo 초기화."""
    d = tempfile.mkdtemp(prefix="stash-lifecycle-test-")
    env = _git_env()
    subprocess.run(["git", "init", "-q", "-b", "main"], cwd=d, check=True, env=env)
    subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=d, check=True, env=env)
    subprocess.run(["git", "config", "user.name", "test"], cwd=d, check=True, env=env)
    (Path(d) / "a.txt").write_text("hello")
    subprocess.run(["git", "add", "a.txt"], cwd=d, check=True, env=env)
    subprocess.run(["git", "commit", "-q", "-m", "init"], cwd=d, check=True, env=env)
    return d


def _git_env() -> dict:
    """git 명령 실행용 환경변수."""
    env = os.environ.copy()
    env["GIT_AUTHOR_NAME"] = "test"
    env["GIT_AUTHOR_EMAIL"] = "test@example.com"
    env["GIT_COMMITTER_NAME"] = "test"
    env["GIT_COMMITTER_EMAIL"] = "test@example.com"
    return env


def _stash_push(repo_dir: str, message: str, filename: str) -> None:
    """파일 하나 dirty 상태로 만들고 stash push."""
    env = _git_env()
    fpath = Path(repo_dir) / filename
    fpath.write_text(f"dirty: {message}\n")
    subprocess.run(["git", "add", filename], cwd=repo_dir, check=True, env=env)
    subprocess.run(["git", "stash", "push", "-m", message], cwd=repo_dir, check=True, env=env)


def _run_audit_json(repo_dir: str) -> dict:
    """stash_audit.py --json 실행 후 파싱된 dict 반환."""
    result = subprocess.run(
        [sys.executable, str(STASH_AUDIT_PY), "--json", "--workspace", repo_dir],
        capture_output=True,
        text=True,
    )
    assert result.returncode == 0, (
        f"stash_audit.py 실행 실패 (exit={result.returncode})\n"
        f"stderr: {result.stderr}"
    )
    return json.loads(result.stdout)


# ---------------------------------------------------------------------------
# spec §2 정책 결정 reference implementation
# ---------------------------------------------------------------------------

def decide_action(
    source: str,
    approve: bool,
    pr_verified: bool,
    idx_in_drop_list: bool,
    etid: str | None,
    task_id: str | None,
) -> str:
    """
    spec §2 결정 흐름 reference implementation.
    """
    if source == "pre-task":
        if approve:
            return "popped"
        return "dry-run-pop"

    if source == "finish-task":
        if approve and pr_verified and (etid == task_id):
            return "popped"
        if not approve:
            return "dry-run-pop"
        return "preserved"

    if source == "other-files":
        if approve and idx_in_drop_list:
            return "dropped"
        if not approve:
            return "dry-run-drop"
        return "dry-run-drop"

    if source in ("wip", "quarantine", "unknown"):
        return "preserved"

    return "skipped"


def simulate_dispatch(entries: list[dict], approve: bool, task_id: str) -> dict:
    """
    spec §2/§3 기반 dispatch 시뮬레이션.
    audit log 포맷(§4.2)에 맞는 dict 반환.
    """
    decisions = []
    skipped_unknown_count = 0

    for e in entries:
        source = e["source"]
        etid = e.get("task_id")
        idx = e["index"]

        action = decide_action(
            source=source,
            approve=approve,
            pr_verified=False,
            idx_in_drop_list=False,
            etid=etid,
            task_id=task_id,
        )

        if source == "unknown":
            skipped_unknown_count += 1

        decisions.append({
            "index": idx,
            "source": source,
            "task_id": etid,
            "reason": e.get("reason", ""),
            "action": action,
        })

    return {
        "timestamp_utc": datetime.now(timezone.utc).isoformat(),
        "task_id": task_id,
        "approval_mode": "approved" if approve else "dry-run",
        "stash_count_before": len(entries),
        "decisions": decisions,
        "skipped_unknown_count": skipped_unknown_count,
    }


# ---------------------------------------------------------------------------
# Fixture: unknown 3건 + wip 1건 + pre-task 1건
# ---------------------------------------------------------------------------

def _stash_push_no_msg(repo_dir: str, filename: str) -> None:
    """
    메시지 없이 git stash push — git 이 'WIP on main: <SHA> <desc>' 형식 자동 생성.
    stash_audit.py 의 wip 패턴(^WIP on \\w) 에 매칭됨.
    """
    env = _git_env()
    fpath = Path(repo_dir) / filename
    fpath.write_text("wip content")
    subprocess.run(["git", "add", filename], cwd=repo_dir, check=True, env=env)
    # 메시지 없이 stash push → "WIP on main: <SHA> <last commit msg>" 자동 생성
    subprocess.run(["git", "stash", "push"], cwd=repo_dir, check=True, env=env)


@pytest.fixture()
def mixed_quarantine_repo():
    """
    unknown stash 3건 + wip 1건 + pre-task 1건 시드.
    총 5건.

    wip stash: 메시지 없이 git stash push → "WIP on main: <SHA> ..." 자동 생성
              → stash_audit.py 의 ^WIP on \\w 패턴 매칭
    """
    repo_dir = _init_temp_repo()

    # unknown 3건 — 패턴 매칭 안 되는 임의 메시지
    _stash_push(repo_dir, "random unknown stash alpha no match", "u1.txt")
    _stash_push(repo_dir, "completely unrelated stash message beta", "u2.txt")
    _stash_push(repo_dir, "some garbage stash data gamma 9999", "u3.txt")

    # wip 1건 — 메시지 없이 push, git이 "WIP on main: ..." 자동 생성
    _stash_push_no_msg(repo_dir, "w1.txt")

    # pre-task 1건
    _stash_push(repo_dir, "WIP: pre-task-2571 stash sample", "p1.txt")

    yield repo_dir

    shutil.rmtree(repo_dir, ignore_errors=True)


# ---------------------------------------------------------------------------
# 테스트
# ---------------------------------------------------------------------------

def test_quarantine_total_stash_count(mixed_quarantine_repo):
    """5건 stash 가 모두 감지되어야 한다."""
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    assert len(entries) == 5, (
        f"5건 시드 후 {len(entries)}건 감지됨"
    )


def test_quarantine_unknown_count_is_3(mixed_quarantine_repo):
    """unknown 분류 건수가 3이어야 한다."""
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    unknown_entries = [e for e in entries if e["source"] == "unknown"]
    assert len(unknown_entries) == 3, (
        f"unknown 건수 기대: 3, 실제: {len(unknown_entries)}\n"
        f"sources: {[e['source'] for e in entries]}"
    )


def test_quarantine_wip_count_is_1(mixed_quarantine_repo):
    """wip 분류 건수가 1이어야 한다."""
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    wip_entries = [e for e in entries if e["source"] == "wip"]
    assert len(wip_entries) == 1, (
        f"wip 건수 기대: 1, 실제: {len(wip_entries)}"
    )


def test_quarantine_pretask_count_is_1(mixed_quarantine_repo):
    """pre-task 분류 건수가 1이어야 한다."""
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    pretask_entries = [e for e in entries if e["source"] == "pre-task"]
    assert len(pretask_entries) == 1, (
        f"pre-task 건수 기대: 1, 실제: {len(pretask_entries)}"
    )


def test_quarantine_unknown_all_preserved(mixed_quarantine_repo):
    """unknown 3건 모두 action == preserved 이어야 한다."""
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    log = simulate_dispatch(entries, approve=False, task_id="task-2571")

    unknown_decisions = [d for d in log["decisions"] if d["source"] == "unknown"]
    assert len(unknown_decisions) == 3, (
        f"unknown decisions 수 기대: 3, 실제: {len(unknown_decisions)}"
    )
    for d in unknown_decisions:
        assert d["action"] == "preserved", (
            f"unknown stash action 기대: preserved, 실제: {d['action']}\n"
            f"decision: {d}"
        )


def test_quarantine_wip_preserved(mixed_quarantine_repo):
    """wip stash 는 action == preserved 이어야 한다 (항상, approve 무관)."""
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])

    # dry-run 모드
    log_dry = simulate_dispatch(entries, approve=False, task_id="task-2571")
    wip_decisions_dry = [d for d in log_dry["decisions"] if d["source"] == "wip"]
    for d in wip_decisions_dry:
        assert d["action"] == "preserved", (
            f"wip dry-run action 기대: preserved, 실제: {d['action']}"
        )

    # approve 모드
    log_approved = simulate_dispatch(entries, approve=True, task_id="task-2571")
    wip_decisions_approved = [d for d in log_approved["decisions"] if d["source"] == "wip"]
    for d in wip_decisions_approved:
        assert d["action"] == "preserved", (
            f"wip approved action 기대: preserved, 실제: {d['action']}"
        )


def test_quarantine_pretask_dry_run_pop(mixed_quarantine_repo):
    """pre-task stash 는 dry-run 시 action == dry-run-pop 이어야 한다."""
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    log = simulate_dispatch(entries, approve=False, task_id="task-2571")

    pretask_decisions = [d for d in log["decisions"] if d["source"] == "pre-task"]
    assert len(pretask_decisions) == 1
    assert pretask_decisions[0]["action"] == "dry-run-pop", (
        f"pre-task dry-run action 기대: dry-run-pop, 실제: {pretask_decisions[0]['action']}"
    )


def test_quarantine_skipped_unknown_count_is_3(mixed_quarantine_repo):
    """
    audit log 의 skipped_unknown_count == 3 검증.
    spec §4.2 포맷에서 unknown 건은 skipped_unknown_count 로 박제.
    """
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    log = simulate_dispatch(entries, approve=False, task_id="task-2571")

    assert log["skipped_unknown_count"] == 3, (
        f"skipped_unknown_count 기대: 3, 실제: {log['skipped_unknown_count']}"
    )


def test_quarantine_unknown_preserved_even_with_approve(mixed_quarantine_repo):
    """
    APPROVE=1 이어도 unknown stash 는 preserved 여야 한다 (spec §3.1 — cleanup 금지).
    """
    data = _run_audit_json(mixed_quarantine_repo)
    entries = data.get("entries", [])
    log = simulate_dispatch(entries, approve=True, task_id="task-2571")

    unknown_decisions = [d for d in log["decisions"] if d["source"] == "unknown"]
    for d in unknown_decisions:
        assert d["action"] == "preserved", (
            f"APPROVE=1 이어도 unknown action 기대: preserved, 실제: {d['action']}"
        )
