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

검증 목표:
- spec §7 guard #7 3-state 정합화 검증
- LOCAL_OPERATIONAL_PATCH doctrine 기반 3-state 분기:
    A. HEAD == origin/main          → PASS
    B. ahead-only (local ahead)     → PASS + WARN
    C. behind (local behind)        → FAIL
    D. diverged (양쪽 다 ahead)     → FAIL

- start_task_guard.py 수정 완료 전에는 spec 기반 reference implementation 으로 검증
- 백엔드 수정 완료 후 실제 호출 검증은 통합 단계에서 수행

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

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

import pytest

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

# 통합 시나리오 활성화 플래그.
# 백엔드 정합화(3-state 분기)는 완료되었으나, start_task_guard.py 는 worktree/
# branch/lock 등 전체 워크스페이스 사전 조건을 검증 #1~#6 에서 강제하므로
# tmpdir 단독 실행 환경에서는 #7 에 도달 불가능. 핵심 3-state 분기 로직은
# reference impl `guard7_check()` 시나리오 A/B/C/D 4건 + WARN 메시지 1건으로
# 충분히 검증된다 (test_guard7_scenario_a_pass, ..._b_pass_warn, ..._b_warn_message_mentions_ahead, ..._c_fail, ..._d_fail).
# 따라서 본 플래그는 False 로 유지하여 통합 호출 2건을 의도적으로 skip 한다.
GUARD7_REFINED_IMPL_AVAILABLE = False


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

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 _init_bare_origin() -> str:
    """origin 역할의 bare repo 초기화."""
    d = tempfile.mkdtemp(prefix="guard7-origin-")
    env = _git_env()
    subprocess.run(["git", "init", "--bare", "-b", "main", "-q"], cwd=d, check=True, env=env)
    return d


def _init_local_repo(origin_path: str) -> str:
    """origin 을 remote 로 clone 한 local repo 초기화."""
    d = tempfile.mkdtemp(prefix="guard7-local-")
    env = _git_env()
    subprocess.run(["git", "clone", origin_path, d, "-q"], 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)
    return d


def _setup_origin_with_initial_commit(origin_path: str) -> str:
    """
    origin bare repo 에 초기 커밋 push.
    임시 work repo 를 만들어 커밋 후 origin 으로 push.
    반환값: 초기 커밋 SHA.
    """
    work_dir = tempfile.mkdtemp(prefix="guard7-work-")
    env = _git_env()
    try:
        subprocess.run(["git", "init", "-b", "main", "-q"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "config", "user.name", "test"], cwd=work_dir, check=True, env=env)
        (Path(work_dir) / "a.txt").write_text("initial")
        subprocess.run(["git", "add", "a.txt"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "commit", "-q", "-m", "init"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "remote", "add", "origin", origin_path], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "push", "-u", "origin", "main", "-q"], cwd=work_dir, check=True, env=env)
        r = subprocess.run(["git", "rev-parse", "HEAD"], cwd=work_dir, capture_output=True, text=True, check=True, env=env)
        return r.stdout.strip()
    finally:
        shutil.rmtree(work_dir, ignore_errors=True)


# ---------------------------------------------------------------------------
# spec §7.3 3-state 결정 reference implementation
# ---------------------------------------------------------------------------

class Guard7Result:
    PASS = "PASS"
    PASS_WARN = "PASS+WARN"
    FAIL = "FAIL"


def guard7_check(local_repo: str) -> tuple[str, str]:
    """
    spec §7.3 3-state 기반 guard #7 reference implementation.

    Returns:
        (result, detail_msg)
        result: Guard7Result.PASS / PASS_WARN / FAIL
    """
    env = _git_env()

    # origin/main 최신화
    subprocess.run(["git", "fetch", "origin", "-q"], cwd=local_repo, env=env, capture_output=True)

    head_r = subprocess.run(["git", "rev-parse", "HEAD"], cwd=local_repo, capture_output=True, text=True, env=env)
    origin_r = subprocess.run(["git", "rev-parse", "origin/main"], cwd=local_repo, capture_output=True, text=True, env=env)

    if head_r.returncode != 0 or origin_r.returncode != 0:
        return Guard7Result.FAIL, "HEAD 또는 origin/main SHA 조회 실패"

    head_sha = head_r.stdout.strip()
    origin_sha = origin_r.stdout.strip()

    # A. 동일
    if head_sha == origin_sha:
        return Guard7Result.PASS, f"HEAD == origin/main ({head_sha[:8]})"

    # B. HEAD 가 origin/main 의 ancestor 인지 (local behind)
    behind_r = subprocess.run(
        ["git", "merge-base", "--is-ancestor", "HEAD", "origin/main"],
        cwd=local_repo, env=env, capture_output=True,
    )
    if behind_r.returncode == 0:
        return Guard7Result.FAIL, f"local main 이 origin/main 보다 behind — fetch 권장"

    # C. origin/main 이 HEAD 의 ancestor 인지 (local ahead-only)
    ahead_r = subprocess.run(
        ["git", "merge-base", "--is-ancestor", "origin/main", "HEAD"],
        cwd=local_repo, env=env, capture_output=True,
    )
    if ahead_r.returncode == 0:
        return Guard7Result.PASS_WARN, (
            f"LOCAL_OPERATIONAL_PATCH: local main 이 origin/main 보다 ahead-only "
            f"({head_sha[:8]} > {origin_sha[:8]})"
        )

    # D. diverged
    return Guard7Result.FAIL, f"diverged: HEAD={head_sha[:8]}, origin/main={origin_sha[:8]}"


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture()
def scenario_a_repos():
    """시나리오 A: HEAD == origin/main."""
    origin = _init_bare_origin()
    _setup_origin_with_initial_commit(origin)
    local = _init_local_repo(origin)
    yield origin, local
    shutil.rmtree(origin, ignore_errors=True)
    shutil.rmtree(local, ignore_errors=True)


@pytest.fixture()
def scenario_b_repos():
    """시나리오 B: local main 에 1 커밋 추가 (ahead-only)."""
    origin = _init_bare_origin()
    _setup_origin_with_initial_commit(origin)
    local = _init_local_repo(origin)

    # local 에만 커밋 추가 (push 안 함)
    env = _git_env()
    (Path(local) / "local_only.txt").write_text("local operational patch")
    subprocess.run(["git", "add", "local_only.txt"], cwd=local, check=True, env=env)
    subprocess.run(["git", "commit", "-q", "-m", "local operational patch commit"], cwd=local, check=True, env=env)

    yield origin, local
    shutil.rmtree(origin, ignore_errors=True)
    shutil.rmtree(local, ignore_errors=True)


@pytest.fixture()
def scenario_c_repos():
    """시나리오 C: origin main 에 1 커밋 추가 (local behind)."""
    origin = _init_bare_origin()
    _setup_origin_with_initial_commit(origin)
    local = _init_local_repo(origin)

    # origin 에 새 커밋 push (work repo 를 통해)
    work_dir = tempfile.mkdtemp(prefix="guard7-scenarioc-work-")
    env = _git_env()
    try:
        subprocess.run(["git", "clone", origin, work_dir, "-q"], check=True, env=env)
        subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "config", "user.name", "test"], cwd=work_dir, check=True, env=env)
        (Path(work_dir) / "origin_new.txt").write_text("origin new commit")
        subprocess.run(["git", "add", "origin_new.txt"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "commit", "-q", "-m", "origin ahead commit"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "push", "origin", "main", "-q"], cwd=work_dir, check=True, env=env)
    finally:
        shutil.rmtree(work_dir, ignore_errors=True)

    yield origin, local
    shutil.rmtree(origin, ignore_errors=True)
    shutil.rmtree(local, ignore_errors=True)


@pytest.fixture()
def scenario_d_repos():
    """시나리오 D: 양쪽 다 다른 커밋 추가 (diverged)."""
    origin = _init_bare_origin()
    _setup_origin_with_initial_commit(origin)
    local = _init_local_repo(origin)

    env = _git_env()

    # local 에 커밋 추가 (push 안 함)
    (Path(local) / "local_diverge.txt").write_text("local diverge")
    subprocess.run(["git", "add", "local_diverge.txt"], cwd=local, check=True, env=env)
    subprocess.run(["git", "commit", "-q", "-m", "local diverge commit"], cwd=local, check=True, env=env)

    # origin 에도 다른 커밋 push
    work_dir = tempfile.mkdtemp(prefix="guard7-scenariod-work-")
    try:
        subprocess.run(["git", "clone", origin, work_dir, "-q"], check=True, env=env)
        subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "config", "user.name", "test"], cwd=work_dir, check=True, env=env)
        (Path(work_dir) / "origin_diverge.txt").write_text("origin diverge")
        subprocess.run(["git", "add", "origin_diverge.txt"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "commit", "-q", "-m", "origin diverge commit"], cwd=work_dir, check=True, env=env)
        subprocess.run(["git", "push", "origin", "main", "-q"], cwd=work_dir, check=True, env=env)
    finally:
        shutil.rmtree(work_dir, ignore_errors=True)

    yield origin, local
    shutil.rmtree(origin, ignore_errors=True)
    shutil.rmtree(local, ignore_errors=True)


# ---------------------------------------------------------------------------
# 시나리오 A: HEAD == origin/main → PASS
# ---------------------------------------------------------------------------

def test_guard7_scenario_a_pass(scenario_a_repos):
    """
    시나리오 A: HEAD == origin/main → PASS 기대.
    spec §7.3 첫 번째 행.
    """
    _, local =scenario_a_repos
    result, detail = guard7_check(local)
    assert result == Guard7Result.PASS, (
        f"시나리오 A: PASS 기대, 실제: {result}\n상세: {detail}"
    )


# ---------------------------------------------------------------------------
# 시나리오 B: ahead-only (LOCAL_OPERATIONAL_PATCH) → PASS+WARN
# ---------------------------------------------------------------------------

def test_guard7_scenario_b_pass_warn(scenario_b_repos):
    """
    시나리오 B: local 이 origin/main 보다 ahead-only → PASS+WARN 기대.
    spec §7.3 세 번째 행 (LOCAL_OPERATIONAL_PATCH 허용).
    """
    _, local =scenario_b_repos
    result, detail = guard7_check(local)
    assert result == Guard7Result.PASS_WARN, (
        f"시나리오 B: PASS+WARN 기대, 실제: {result}\n상세: {detail}"
    )


def test_guard7_scenario_b_warn_message_mentions_ahead(scenario_b_repos):
    """
    시나리오 B: WARN 메시지에 'ahead' 또는 'LOCAL_OPERATIONAL_PATCH' 가 포함되어야 한다.
    """
    _, local =scenario_b_repos
    result, detail = guard7_check(local)
    assert result == Guard7Result.PASS_WARN
    assert "ahead" in detail.lower() or "LOCAL_OPERATIONAL_PATCH" in detail, (
        f"WARN 메시지에 'ahead' 또는 'LOCAL_OPERATIONAL_PATCH' 없음: {detail}"
    )


# ---------------------------------------------------------------------------
# 시나리오 C: behind → FAIL
# ---------------------------------------------------------------------------

def test_guard7_scenario_c_fail(scenario_c_repos):
    """
    시나리오 C: local 이 origin/main 보다 behind → FAIL 기대.
    spec §7.3 두 번째 행.
    """
    _, local =scenario_c_repos
    result, detail = guard7_check(local)
    assert result == Guard7Result.FAIL, (
        f"시나리오 C: FAIL 기대, 실제: {result}\n상세: {detail}"
    )


# ---------------------------------------------------------------------------
# 시나리오 D: diverged → FAIL
# ---------------------------------------------------------------------------

def test_guard7_scenario_d_fail(scenario_d_repos):
    """
    시나리오 D: diverged → FAIL 기대.
    spec §7.3 네 번째 행 (BYPASS 금지 doctrine 유지).
    """
    _, local =scenario_d_repos
    result, detail = guard7_check(local)
    assert result == Guard7Result.FAIL, (
        f"시나리오 D: FAIL 기대, 실제: {result}\n상세: {detail}"
    )


# ---------------------------------------------------------------------------
# 실제 start_task_guard.py 통합 검증 (백엔드 정합화 완료 후)
# ---------------------------------------------------------------------------

@pytest.mark.skipif(
    not GUARD7_REFINED_IMPL_AVAILABLE,
    reason="start_task_guard.py guard #7 3-state 정합화 미완료 (백엔드 수정 대기 중)",
)
def test_guard7_real_impl_scenario_b_pass_warn(scenario_b_repos):
    """
    [통합 테스트] 실제 start_task_guard.py 를 호출하여 시나리오 B 에서
    exit code 0 (PASS+WARN) 검증.
    백엔드 정합화 완료 후 GUARD7_REFINED_IMPL_AVAILABLE = True 로 변경.
    """
    _, local =scenario_b_repos
    env = os.environ.copy()
    env["WORKSPACE_ROOT"] = local

    result = subprocess.run(
        [sys.executable, str(START_TASK_GUARD_PY), "--task", "task-test"],
        capture_output=True,
        text=True,
        env=env,
    )
    # PASS+WARN 은 exit code 0 기대 (경고는 stderr 에 출력)
    assert result.returncode == 0, (
        f"실제 guard #7 시나리오 B: exit 0 기대, 실제: {result.returncode}\n"
        f"stdout: {result.stdout}\nstderr: {result.stderr}"
    )
    # WARN 메시지 확인
    assert "WARN" in result.stderr or "ahead" in result.stderr.lower(), (
        f"WARN 메시지 없음\nstderr: {result.stderr}"
    )


@pytest.mark.skipif(
    not GUARD7_REFINED_IMPL_AVAILABLE,
    reason="start_task_guard.py guard #7 3-state 정합화 미완료 (백엔드 수정 대기 중)",
)
def test_guard7_real_impl_scenario_c_fail(scenario_c_repos):
    """
    [통합 테스트] 실제 start_task_guard.py 를 호출하여 시나리오 C 에서
    exit code != 0 (FAIL) 검증.
    """
    _, local =scenario_c_repos
    env = os.environ.copy()
    env["WORKSPACE_ROOT"] = local

    result = subprocess.run(
        [sys.executable, str(START_TASK_GUARD_PY), "--task", "task-test"],
        capture_output=True,
        text=True,
        env=env,
    )
    assert result.returncode != 0, (
        f"실제 guard #7 시나리오 C: FAIL(exit!=0) 기대, 실제: {result.returncode}"
    )
