"""회귀 테스트: task-2729+10 G1/G2 변경 검증

목표:
  G1 — start_task_guard 검증 #7: canonical workspace 브랜치 가정 제거 →
       worktree(cwd) 의 HEAD vs origin/main fresh-base 검증으로 전환.
       canonical 이 non-main / dirty 여도 worktree 가 fresh base 이면 PASS.

  G2 — finish-task.sh GIT-GATE 면제 결정:
       EXTERNAL_DIRTY_BLOCKER + worktree 격리 시 차단 면제(EXEMPT),
       그 외(own dirty, worktree 없음)는 BLOCK(fail-closed).

작성자: 벨레스 (개발6팀 QA 엔지니어)
참조: task-2729+10 스펙, scripts/start_task_guard.py, utils/dirty_registry.py,
      scripts/finish-task.sh line ~964
"""

from __future__ import annotations

import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

import pytest

# ---------------------------------------------------------------------------
# 상수
# ---------------------------------------------------------------------------

# 이 파일에서 2단계 위 = worktree 루트
_WORKTREE_ROOT = Path(__file__).resolve().parents[2]
START_TASK_GUARD = _WORKTREE_ROOT / "scripts" / "start_task_guard.py"

TASK = "task-9001"
BOT = "dev6"


# ---------------------------------------------------------------------------
# 헬퍼
# ---------------------------------------------------------------------------

def _git_env() -> dict:
    """git 작성자/커미터 환경변수 고정."""
    env = os.environ.copy()
    env.update({
        "GIT_AUTHOR_NAME": "test",
        "GIT_AUTHOR_EMAIL": "test@example.com",
        "GIT_COMMITTER_NAME": "test",
        "GIT_COMMITTER_EMAIL": "test@example.com",
    })
    return env


def _run(args: list[str], cwd: str, env: dict | None = None, check: bool = True) -> subprocess.CompletedProcess:
    """subprocess 래퍼 (기본 check=True)."""
    return subprocess.run(
        args,
        cwd=cwd,
        env=env or _git_env(),
        capture_output=True,
        text=True,
        check=check,
    )


def _git(args: list[str], cwd: str, env: dict | None = None, check: bool = True) -> subprocess.CompletedProcess:
    return _run(["git"] + args, cwd=cwd, env=env or _git_env(), check=check)


# ---------------------------------------------------------------------------
# 공통 fixture: isolated temp git repo 환경 구성
# ---------------------------------------------------------------------------

@pytest.fixture
def isolated_repo():
    """
    TC1 / TC2 / TC3 공통 픽스처.

    구성:
      tmpdir/
        origin/          (bare)
        work/            (origin 초기화에 사용한 작업 repo)
        canonical/       (origin clone, 기본 상태: main)
        canonical/.worktrees/<TASK>-<BOT>/   (worktree, fresh origin/main base)

    반환: dict with keys
      - tmpdir (str)
      - origin (str)
      - work (str)
      - canonical (str)
      - worktree (str)
    """
    tmpdir = tempfile.mkdtemp(prefix="reg2729p10_")
    try:
        env = _git_env()

        # 1. bare origin
        origin = os.path.join(tmpdir, "origin")
        _git(["init", "--bare", "-b", "main", origin], cwd=tmpdir)

        # 2. work repo: 초기 커밋 + push
        work = os.path.join(tmpdir, "work")
        os.makedirs(work)
        _git(["init", "-b", "main", work], cwd=work)
        _git(["remote", "add", "origin", origin], cwd=work)

        # start_task_guard.py 를 work repo 에 복사 (scripts/ 포함)
        scripts_dir = os.path.join(work, "scripts")
        os.makedirs(scripts_dir, exist_ok=True)
        shutil.copy2(str(START_TASK_GUARD), os.path.join(scripts_dir, "start_task_guard.py"))

        # 초기 커밋
        init_file = os.path.join(work, "a.txt")
        Path(init_file).write_text("init\n")
        _git(["add", "."], cwd=work)
        _git(["commit", "-m", "[init] initial commit"], cwd=work)
        _git(["push", "-u", "origin", "main"], cwd=work)

        # 3. canonical: clone from origin
        canonical = os.path.join(tmpdir, "canonical")
        _git(["clone", origin, canonical], cwd=tmpdir)

        # 4. worktree: canonical 안에서 fresh origin/main 기반으로 생성
        worktree_dir = os.path.join(canonical, ".worktrees")
        os.makedirs(worktree_dir, exist_ok=True)
        worktree = os.path.join(worktree_dir, f"{TASK}-{BOT}")
        _git(
            ["worktree", "add", "-b", f"task/{TASK}-{BOT}", worktree, "origin/main"],
            cwd=canonical,
        )

        yield {
            "tmpdir": tmpdir,
            "origin": origin,
            "work": work,
            "canonical": canonical,
            "worktree": worktree,
        }
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


# ---------------------------------------------------------------------------
# TC1 — 검증 #7: canonical=non-main+dirty 여도 worktree=fresh → PASS
# ---------------------------------------------------------------------------

def test_guard7_fresh_worktree_passes_despite_canonical_parked(isolated_repo):
    """G1 핵심 케이스: canonical 이 task/task-2716-parked + dirty 여도
    worktree 가 fresh origin/main base 이면 start_task_guard 가 성공해야 한다.

    검증 목표:
      - exit code == 0
      - lock 파일 생성 확인
      - stdout 에 "검증 #7 통과" 포함
    """
    canonical = isolated_repo["canonical"]
    worktree = isolated_repo["worktree"]

    # canonical 을 non-main 브랜치로 전환 + dirty 상태 만들기
    _git(["checkout", "-b", "task/task-2716-parked"], cwd=canonical)
    dirty_file = os.path.join(canonical, "parked_dirty.txt")
    Path(dirty_file).write_text("dirty working tree\n")
    # commit 하지 않고 dirty 유지 (unstaged)

    # start_task_guard 실행 (cwd=worktree, WORKSPACE_ROOT=canonical)
    result = subprocess.run(
        [sys.executable, str(START_TASK_GUARD), "--task", TASK, "--bot", BOT],
        cwd=worktree,
        env={**_git_env(), "WORKSPACE_ROOT": canonical},
        capture_output=True,
        text=True,
    )

    assert result.returncode == 0, (
        f"[TC1] exit code should be 0 (PASS), got {result.returncode}\n"
        f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    )

    lock_path = Path(worktree) / ".tasks" / "locks" / f"{TASK}.lock"
    assert lock_path.exists(), (
        f"[TC1] lock file should exist at {lock_path}\n"
        f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    )

    assert "검증 #7 통과" in result.stdout, (
        f"[TC1] stdout should contain '검증 #7 통과'\n"
        f"STDOUT:\n{result.stdout}"
    )


# ---------------------------------------------------------------------------
# TC2 — 검증 #7: worktree base 가 stale(behind) → FAIL
# ---------------------------------------------------------------------------

def test_guard7_stale_base_worktree_fails(isolated_repo):
    """G1 역 케이스: worktree HEAD 가 origin/main 보다 behind(stale) 이면 FAIL 해야 한다.

    재현 방법:
      1. worktree 는 커밋1(initial) 기반으로 이미 생성됨.
      2. work repo 에서 커밋2 push → origin/main 전진.
      3. canonical 에서 git fetch origin → origin/main ref 가 커밋2 로 갱신.
      4. worktree HEAD=커밋1, origin/main=커밋2 → behind(stale).

    검증 목표:
      - exit code != 0
      - lock 파일 미생성
      - stdout+stderr 에 "stale" 포함
    """
    work = isolated_repo["work"]
    canonical = isolated_repo["canonical"]
    worktree = isolated_repo["worktree"]

    # work repo 에 커밋2 추가 + push
    second_file = os.path.join(work, "commit2.txt")
    Path(second_file).write_text("second commit\n")
    _git(["add", "."], cwd=work)
    _git(["commit", "-m", "[task-9001] second commit"], cwd=work)
    _git(["push", "origin", "main"], cwd=work)

    # canonical 에서 fetch → origin/main 갱신
    _git(["fetch", "origin"], cwd=canonical)

    # start_task_guard 실행
    result = subprocess.run(
        [sys.executable, str(START_TASK_GUARD), "--task", TASK, "--bot", BOT],
        cwd=worktree,
        env={**_git_env(), "WORKSPACE_ROOT": canonical},
        capture_output=True,
        text=True,
    )

    assert result.returncode != 0, (
        f"[TC2] exit code should be non-zero (FAIL), got {result.returncode}\n"
        f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    )

    lock_path = Path(worktree) / ".tasks" / "locks" / f"{TASK}.lock"
    assert not lock_path.exists(), (
        f"[TC2] lock file should NOT exist (stale base must block)\n"
        f"lock path: {lock_path}"
    )

    combined = result.stdout + result.stderr
    assert "stale" in combined, (
        f"[TC2] stdout+stderr should contain 'stale'\n"
        f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    )


# ---------------------------------------------------------------------------
# TC3 — 무회귀: canonical=main 정상 케이스도 여전히 PASS
# ---------------------------------------------------------------------------

def test_guard7_canonical_on_main_still_passes(isolated_repo):
    """무회귀: canonical 이 main 브랜치 그대로인 정상 케이스에서도 성공해야 한다.

    검증 목표:
      - exit code == 0
      - lock 파일 생성
    """
    canonical = isolated_repo["canonical"]
    worktree = isolated_repo["worktree"]

    # canonical 은 main 브랜치 그대로 (전환 안 함)
    result = subprocess.run(
        [sys.executable, str(START_TASK_GUARD), "--task", TASK, "--bot", BOT],
        cwd=worktree,
        env={**_git_env(), "WORKSPACE_ROOT": canonical},
        capture_output=True,
        text=True,
    )

    assert result.returncode == 0, (
        f"[TC3] canonical-on-main should still pass, got {result.returncode}\n"
        f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    )

    lock_path = Path(worktree) / ".tasks" / "locks" / f"{TASK}.lock"
    assert lock_path.exists(), (
        f"[TC3] lock file should exist\n"
        f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    )


# ---------------------------------------------------------------------------
# TC4 — G2 분류: EXTERNAL_DIRTY_BLOCKER 반환 검증
# ---------------------------------------------------------------------------

def test_g2_classify_blocker_external_dirty():
    """G2: classify_blocker 가 own 없고 unrelated 있으면 EXTERNAL_DIRTY_BLOCKER 반환.

    worktree root 의 utils/dirty_registry.py 를 직접 임포트해 검증.
    expected_files = ["scripts/start_task_guard.py"]  (본 task 파일)
    dirty_paths   = ["other_team/foo.py"]             (무관 파일)
    → own 비어있음 → EXTERNAL_DIRTY_BLOCKER
    """
    # worktree 루트의 utils 모듈 임포트 (conftest.py 에서 sys.path[0] 에 이미 삽입됨)
    # 명시적으로 보장
    wt_root = str(_WORKTREE_ROOT)
    if wt_root not in sys.path:
        sys.path.insert(0, wt_root)

    # 캐시에 다른 위치의 utils 가 있으면 교체
    if "utils.dirty_registry" in sys.modules:
        cached = getattr(sys.modules.get("utils"), "__file__", "") or ""
        if wt_root not in cached:
            sys.modules.pop("utils.dirty_registry", None)
            sys.modules.pop("utils", None)

    from utils.dirty_registry import classify_blocker, EXTERNAL_DIRTY_BLOCKER as _EDB

    expected_files = ["scripts/start_task_guard.py"]
    dirty_paths = ["other_team/foo.py"]

    result = classify_blocker(expected_files, dirty_paths)

    assert result["classification"] == _EDB, (
        f"[TC4] Expected EXTERNAL_DIRTY_BLOCKER, got: {result['classification']}\n"
        f"full result: {result}"
    )
    assert result["own_dirty"] == [], f"[TC4] own_dirty should be empty, got: {result['own_dirty']}"
    assert "other_team/foo.py" in result["unrelated_dirty"], (
        f"[TC4] unrelated_dirty should contain 'other_team/foo.py', got: {result['unrelated_dirty']}"
    )


# ---------------------------------------------------------------------------
# TC5 — G2 면제 결정: bash 스니펫으로 EXEMPT / BLOCK 검증
# ---------------------------------------------------------------------------

def test_g2_worktree_isolation_exempt_decision():
    """G2: finish-task.sh line ~964 의 면제 결정 조건을 bash 스니펫으로 재현.

    조건 (finish-task.sh line 964):
      if [ "$_DIRTY_CLASSIFICATION" = "EXTERNAL_DIRTY_BLOCKER" ] && [ "$_WT_ISOLATED" = "1" ]; then
        EXEMPT
      else
        BLOCK
      fi

    케이스 A: worktree 존재 + EXTERNAL_DIRTY_BLOCKER → exit 0, "EXEMPT"
    케이스 B: worktree 없음  + EXTERNAL_DIRTY_BLOCKER → exit 1, "BLOCK"  (fail-closed)
    케이스 C: worktree 존재 + OWN_DIRTY_FAIL         → exit 1, "BLOCK"  (own dirty 면제 불가)
    """
    # G2 결정 로직 bash 스니펫 (finish-task.sh line ~964 의 조건과 동일)
    # 이 스니펫이 실제 finish-task.sh 의 G2 조건
    # `[ "$_DIRTY_CLASSIFICATION" = "EXTERNAL_DIRTY_BLOCKER" ] && [ "$_WT_ISOLATED" = "1" ]`
    # 과 동일함을 주석으로 명시.
    snippet = r"""#!/usr/bin/env bash
set -euo pipefail
WORKSPACE="$1"; TASK_ID="$2"; _DIRTY_CLASSIFICATION="$3"
_WT_ISOLATED=0
# finish-task.sh line 879: worktree 존재 여부 확인
if ls -d "$WORKSPACE/.worktrees/${TASK_ID}-"* >/dev/null 2>&1; then _WT_ISOLATED=1; fi
# finish-task.sh line 964: EXTERNAL_DIRTY_BLOCKER + worktree 격리 시 면제
# 조건: [ "$_DIRTY_CLASSIFICATION" = "EXTERNAL_DIRTY_BLOCKER" ] && [ "$_WT_ISOLATED" = "1" ]
if [ "$_DIRTY_CLASSIFICATION" = "EXTERNAL_DIRTY_BLOCKER" ] && [ "$_WT_ISOLATED" = "1" ]; then
    echo "EXEMPT"; exit 0
else
    echo "BLOCK"; exit 1
fi
"""
    tmpdir = tempfile.mkdtemp(prefix="reg2729p10_g2_")
    try:
        # 스니펫 임시 파일 작성
        snippet_path = os.path.join(tmpdir, "g2_decision.sh")
        Path(snippet_path).write_text(snippet)
        os.chmod(snippet_path, 0o755)

        # 케이스 A: worktree 존재 + EXTERNAL_DIRTY_BLOCKER → EXEMPT
        ws_a = os.path.join(tmpdir, "ws_a")
        wt_dir_a = os.path.join(ws_a, ".worktrees", f"{TASK}-{BOT}")
        os.makedirs(wt_dir_a, exist_ok=True)

        res_a = subprocess.run(
            ["bash", snippet_path, ws_a, TASK, "EXTERNAL_DIRTY_BLOCKER"],
            capture_output=True, text=True,
        )
        assert res_a.returncode == 0, (
            f"[TC5-A] EXEMPT expected exit 0, got {res_a.returncode}\n"
            f"STDOUT: {res_a.stdout}  STDERR: {res_a.stderr}"
        )
        assert "EXEMPT" in res_a.stdout, (
            f"[TC5-A] stdout should contain 'EXEMPT', got: {res_a.stdout}"
        )

        # 케이스 B: worktree 없음 + EXTERNAL_DIRTY_BLOCKER → BLOCK (fail-closed)
        ws_b = os.path.join(tmpdir, "ws_b")
        os.makedirs(ws_b, exist_ok=True)
        # .worktrees/<TASK>-<BOT> 디렉토리 생성 안 함

        res_b = subprocess.run(
            ["bash", snippet_path, ws_b, TASK, "EXTERNAL_DIRTY_BLOCKER"],
            capture_output=True, text=True,
        )
        assert res_b.returncode != 0, (
            f"[TC5-B] BLOCK expected exit 1, got {res_b.returncode}\n"
            f"STDOUT: {res_b.stdout}  STDERR: {res_b.stderr}"
        )
        assert "BLOCK" in res_b.stdout, (
            f"[TC5-B] stdout should contain 'BLOCK', got: {res_b.stdout}"
        )

        # 케이스 C: worktree 존재 + OWN_DIRTY_FAIL → BLOCK (own dirty 는 면제 불가)
        ws_c = os.path.join(tmpdir, "ws_c")
        wt_dir_c = os.path.join(ws_c, ".worktrees", f"{TASK}-{BOT}")
        os.makedirs(wt_dir_c, exist_ok=True)

        res_c = subprocess.run(
            ["bash", snippet_path, ws_c, TASK, "OWN_DIRTY_FAIL"],
            capture_output=True, text=True,
        )
        assert res_c.returncode != 0, (
            f"[TC5-C] OWN_DIRTY_FAIL should BLOCK (exit 1), got {res_c.returncode}\n"
            f"STDOUT: {res_c.stdout}  STDERR: {res_c.stderr}"
        )
        assert "BLOCK" in res_c.stdout, (
            f"[TC5-C] stdout should contain 'BLOCK', got: {res_c.stdout}"
        )

    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)
