#!/usr/bin/env python3
"""
task-2729+9 회귀 테스트: base source isolation (5 케이스)

모리건 (QA/회귀 테스트 엔지니어) — 2026-06-06

검증 대상:
  - cmd_create() 의 origin/main 기반 강제 (B1/B2 하드닝)
  - fail-closed 로직 (origin remote 존재 + origin/main 미해결)
  - 로컬 전용 repo 에서 HEAD 를 explicit SHA base 로 사용
  - 성공 마커 enriched 필드 전체 존재 검증
  - pre-push merge-base 분기 조건 단위 검증

모든 temp git repo 는 pytest tmp_path 하위에 생성.
canonical /home/jay/workspace 는 절대 건드리지 않는다.
"""
from __future__ import annotations

import importlib.util
import json
import os
import subprocess
import sys
from pathlib import Path

import pytest

# ---------------------------------------------------------------------------
# worktree root 경로 — worktree 버전의 worktree_manager.py 를 로드하기 위해 사용
# ---------------------------------------------------------------------------
_WORKTREE_ROOT = Path(__file__).resolve().parents[2]
assert "wt-2729p9" in str(_WORKTREE_ROOT), (
    f"잘못된 WORKTREE_ROOT 감지: {_WORKTREE_ROOT}. "
    "canonical workspace 를 건드리지 않는지 확인하세요."
)

# ---------------------------------------------------------------------------
# Git 환경 격리 헬퍼
# ---------------------------------------------------------------------------

def _git_env() -> dict:
    """canonical ~/.gitconfig, SSH 키 등 외부 영향을 차단한 격리 환경 반환."""
    env = os.environ.copy()
    env["GIT_CONFIG_GLOBAL"] = "/dev/null"
    env["GIT_AUTHOR_NAME"] = "morrigan-tester"
    env["GIT_AUTHOR_EMAIL"] = "morrigan@test.invalid"
    env["GIT_COMMITTER_NAME"] = "morrigan-tester"
    env["GIT_COMMITTER_EMAIL"] = "morrigan@test.invalid"
    env["HOME"] = "/tmp"
    # GPG 서명 비활성화
    env["GIT_CONFIG_COUNT"] = "1"
    env["GIT_CONFIG_KEY_0"] = "commit.gpgsign"
    env["GIT_CONFIG_VALUE_0"] = "false"
    return env


def _git(args: list[str], cwd: str, env: dict | None = None, check: bool = True) -> str:
    """git 명령 실행 → stdout strip 반환."""
    e = env or _git_env()
    r = subprocess.run(
        ["git"] + args,
        cwd=cwd,
        capture_output=True,
        text=True,
        env=e,
        timeout=30,
    )
    if check and r.returncode != 0:
        raise RuntimeError(f"git {' '.join(args)} failed:\n{r.stderr.strip()}")
    return r.stdout.strip()


def _commit(repo: str, filename: str, message: str, env: dict | None = None) -> str:
    """파일 하나 생성 후 커밋, 커밋 SHA 반환."""
    e = env or _git_env()
    fpath = os.path.join(repo, filename)
    with open(fpath, "w") as f:
        f.write(f"content of {filename}\n")
    _git(["add", filename], cwd=repo, env=e)
    _git(["-c", "commit.gpgsign=false", "commit", "-m", message], cwd=repo, env=e)
    return _git(["rev-parse", "HEAD"], cwd=repo, env=e)


# ---------------------------------------------------------------------------
# worktree_manager 로드 — worktree 버전만 (canonical scripts 차단)
# ---------------------------------------------------------------------------

def _load_worktree_manager():
    """wt-2729p9 worktree 의 worktree_manager.py 를 importlib 로 직접 로드."""
    spec_path = str(_WORKTREE_ROOT / "scripts" / "worktree_manager.py")
    assert os.path.exists(spec_path), f"worktree_manager.py 없음: {spec_path}"

    # canonical workspace 가 sys.path 에 먼저 있으면 제거 후 worktree root 선삽입
    _canonical = "/home/jay/workspace"
    if _canonical in sys.path:
        sys.path.remove(_canonical)
    if str(_WORKTREE_ROOT) not in sys.path:
        sys.path.insert(0, str(_WORKTREE_ROOT))

    # 모듈 캐시를 무시하고 worktree 본을 매번 freshly load
    mod_name = "worktree_manager_wt_2729p9"
    spec = importlib.util.spec_from_file_location(mod_name, spec_path)
    mod = importlib.util.module_from_spec(spec)
    # blast_radius_parser 등 utils 의존 — worktree sys.path 에서 해결되도록 보장
    spec.loader.exec_module(mod)
    return mod


# ---------------------------------------------------------------------------
# 공통 bare origin + clone 셋업 헬퍼
# ---------------------------------------------------------------------------

def _make_origin_clone(tmp_path: Path) -> tuple[str, str, str]:
    """
    bare origin 을 만들고, seed 커밋으로 main 을 채운 뒤 clone 반환.

    Returns:
        (origin_dir, clone_dir, sha_fresh) — sha_fresh = origin/main tip SHA
    """
    env = _git_env()
    origin_dir = str(tmp_path / "origin.git")
    os.makedirs(origin_dir)
    _git(["init", "--bare", "-b", "main", origin_dir], cwd=str(tmp_path), env=env)

    # seed clone 에서 최초 커밋 → origin push
    seed_dir = str(tmp_path / "seed")
    _git(["clone", origin_dir, seed_dir], cwd=str(tmp_path), env=env)
    _git(["checkout", "-B", "main"], cwd=seed_dir, env=env)
    sha_fresh = _commit(seed_dir, "seed.txt", "initial commit", env=env)
    _git(["push", "origin", "main"], cwd=seed_dir, env=env)

    # 테스트용 clone
    clone_dir = str(tmp_path / "clone")
    _git(["clone", origin_dir, clone_dir], cwd=str(tmp_path), env=env)
    _git(["checkout", "-B", "main"], cwd=clone_dir, env=env)

    return origin_dir, clone_dir, sha_fresh


# ---------------------------------------------------------------------------
# test_1: stale 로컬 main 이 있어도 origin/main SHA 를 base 로 사용
# ---------------------------------------------------------------------------

def test_1_stale_local_main_uses_origin_base(tmp_path):
    """
    [시나리오] bare origin + clone.
    로컬 main 은 STALE (추가 커밋 있음), origin/main tracking ref = SHA_FRESH.
    HEAD 는 로컬 main.

    [기대]
    - cmd_create 반환: status=="created", base_fallback is False, base_sha == SHA_FRESH
    - 생성된 worktree HEAD == SHA_FRESH
    - marker: base_source == "origin/main"
    """
    wm = _load_worktree_manager()
    env = _git_env()

    origin_dir, clone_dir, sha_fresh = _make_origin_clone(tmp_path)

    # 로컬 main 에 stale 커밋 추가 (origin 에 push 하지 않음)
    sha_stale = _commit(clone_dir, "local_only.txt", "stale local commit", env=env)
    assert sha_stale != sha_fresh, "STALE != FRESH 전제"

    # HEAD 는 로컬 main (stale)
    head = _git(["rev-parse", "HEAD"], cwd=clone_dir, env=env)
    assert head == sha_stale

    events_dir = str(tmp_path / "events")
    result = wm.cmd_create(
        clone_dir,
        "task-test1",
        "qa",
        copy_env=False,
        events_dir=events_dir,
    )

    assert result["status"] == "created", f"status 는 created 여야 함: {result}"
    assert result["base_fallback"] is False, f"base_fallback 은 False 여야 함: {result}"
    assert result["base_sha"] == sha_fresh, (
        f"base_sha={result['base_sha']} != sha_fresh={sha_fresh}"
    )

    # 생성된 worktree HEAD == SHA_FRESH
    wt_path = result["worktree_path"]
    assert wt_path is not None and os.path.isdir(wt_path), f"worktree 미생성: {wt_path}"
    wt_head = _git(["rev-parse", "HEAD"], cwd=wt_path, env=env)
    assert wt_head == sha_fresh, (
        f"worktree HEAD={wt_head} 가 origin/main tip={sha_fresh} 와 다름 (stale base 사용됨)"
    )

    # 마커 검증
    marker_path = result.get("base_marker_path")
    assert marker_path and os.path.exists(marker_path), f"마커 파일 없음: {marker_path}"
    marker = json.loads(Path(marker_path).read_text())
    assert marker["base_source"] == "origin/main", (
        f"base_source 가 origin/main 이어야 함: {marker['base_source']}"
    )
    assert marker["base_sha"] == sha_fresh, (
        f"마커 base_sha={marker['base_sha']} != sha_fresh={sha_fresh}"
    )


# ---------------------------------------------------------------------------
# test_2: origin remote 존재 + origin/main 미해결 → fail-closed
# ---------------------------------------------------------------------------

def test_2_origin_exists_unresolvable_fail_closed(tmp_path):
    """
    [시나리오] repo 에 origin remote 설정됨(존재하지 않는 경로 OK).
    refs/remotes/origin/main 없음 (미 fetch).
    HEAD 는 로컬 main(SHA_STALE).

    [기대]
    - cmd_create 반환: status == "failed"
    - worktree 디렉토리 미생성
    - <events_dir>/<task_id>.worktree-base-failed.json 존재
    - reason == "STALE_BASE_ORIGIN_UNRESOLVED"
    """
    wm = _load_worktree_manager()
    env = _git_env()

    # 로컬 전용 repo 생성 (origin 없음에서 시작)
    local_dir = str(tmp_path / "local")
    os.makedirs(local_dir)
    _git(["init", "-b", "main", local_dir], cwd=local_dir, env=env)
    _commit(local_dir, "init.txt", "initial", env=env)

    # origin remote 추가 — 실제로 접근 불가한 경로 (fetch 절대 성공 안 함)
    _git(
        ["remote", "add", "origin", str(tmp_path / "nonexistent_origin.git")],
        cwd=local_dir,
        env=env,
    )
    # fetch 하지 않으므로 refs/remotes/origin/main 없음

    sha_stale = _git(["rev-parse", "HEAD"], cwd=local_dir, env=env)

    events_dir = str(tmp_path / "events")
    result = wm.cmd_create(
        local_dir,
        "task-test2",
        "qa",
        copy_env=False,
        events_dir=events_dir,
    )

    assert result["status"] == "failed", (
        f"status 는 failed 여야 함 (fail-closed). 실제: {result}"
    )

    # worktree 미생성 확인
    wt_path = result.get("worktree_path")
    assert wt_path is None, f"worktree_path 는 None 이어야 함: {wt_path}"

    # .worktrees 디렉토리 자체가 없거나 비어있어야 함
    wt_dir = Path(local_dir) / ".worktrees"
    task_wt = wt_dir / "task-test2-qa"
    assert not task_wt.exists(), f"worktree 디렉터리가 생성됨 (fail-closed 위반): {task_wt}"

    # failed 마커 존재 확인
    failed_marker = Path(events_dir) / "task-test2.worktree-base-failed.json"
    assert failed_marker.exists(), f"failed 마커 없음: {failed_marker}"

    marker_data = json.loads(failed_marker.read_text())
    assert marker_data["reason"] == "STALE_BASE_ORIGIN_UNRESOLVED", (
        f"reason 이 STALE_BASE_ORIGIN_UNRESOLVED 이어야 함: {marker_data['reason']}"
    )
    assert marker_data["task_id"] == "task-test2"


# ---------------------------------------------------------------------------
# test_3: origin 없는 로컬 전용 repo → HEAD 를 explicit SHA base 로 사용
# ---------------------------------------------------------------------------

def test_3_no_origin_local_head_explicit_base(tmp_path):
    """
    [시나리오] origin remote 없는 로컬 전용 repo.
    커밋 1개 이상. HEAD = SHA_LOCAL.

    [기대]
    - status == "created"
    - worktree 생성됨
    - base_source == "local_head_no_origin"
    - base_sha == SHA_LOCAL
    - worktree HEAD == SHA_LOCAL
    - base_fallback is True  (로컬 HEAD fallback 임을 명시)

    회귀 방지: origin 없는 repo 가 fail-closed 되지 않음을 검증.
    """
    wm = _load_worktree_manager()
    env = _git_env()

    local_dir = str(tmp_path / "local_only")
    os.makedirs(local_dir)
    _git(["init", "-b", "main", local_dir], cwd=local_dir, env=env)
    sha_local = _commit(local_dir, "file.txt", "only commit", env=env)

    # remote 없음 확인
    remotes = _git(["remote"], cwd=local_dir, env=env)
    assert remotes == "", f"remote 가 존재해선 안 됨: {remotes!r}"

    events_dir = str(tmp_path / "events")
    result = wm.cmd_create(
        local_dir,
        "task-test3",
        "qa",
        copy_env=False,
        events_dir=events_dir,
    )

    assert result["status"] == "created", (
        f"origin 없는 로컬 repo 에서 fail-closed 되면 안 됨. 실제: {result}"
    )
    assert result["base_fallback"] is True, (
        f"base_fallback 은 True 이어야 함 (local_head_no_origin): {result}"
    )
    assert result["base_sha"] == sha_local, (
        f"base_sha={result['base_sha']} != sha_local={sha_local}"
    )

    wt_path = result["worktree_path"]
    assert wt_path is not None and os.path.isdir(wt_path), f"worktree 미생성: {wt_path}"

    wt_head = _git(["rev-parse", "HEAD"], cwd=wt_path, env=env)
    assert wt_head == sha_local, (
        f"worktree HEAD={wt_head} != sha_local={sha_local}"
    )

    # 마커 검증
    marker_path = result.get("base_marker_path")
    assert marker_path and os.path.exists(marker_path), f"마커 없음: {marker_path}"
    marker = json.loads(Path(marker_path).read_text())
    assert marker["base_source"] == "local_head_no_origin", (
        f"base_source={marker['base_source']!r} != 'local_head_no_origin'"
    )
    assert marker["base_sha"] == sha_local, (
        f"마커 base_sha={marker['base_sha']} != sha_local={sha_local}"
    )


# ---------------------------------------------------------------------------
# test_4: 성공 마커 enriched 필드 전체 존재 검증
# ---------------------------------------------------------------------------

def test_4_success_marker_enriched_fields(tmp_path):
    """
    [시나리오] test_1 과 유사한 정상(origin/main 해결) 구성.

    [기대]
    성공 마커 JSON 에 다음 키가 **모두** 존재:
        base_source, base_sha, merge_base, origin_main_sha, canonical_head_sha
    값 검증:
        origin_main_sha == SHA_FRESH
        base_sha == SHA_FRESH
    """
    wm = _load_worktree_manager()
    env = _git_env()

    origin_dir, clone_dir, sha_fresh = _make_origin_clone(tmp_path)

    events_dir = str(tmp_path / "events")
    result = wm.cmd_create(
        clone_dir,
        "task-test4",
        "qa",
        copy_env=False,
        events_dir=events_dir,
    )

    assert result["status"] == "created", f"status 는 created 여야 함: {result}"

    marker_path = result.get("base_marker_path")
    assert marker_path and os.path.exists(marker_path), f"마커 없음: {marker_path}"

    marker = json.loads(Path(marker_path).read_text())

    # 필수 enriched 필드 전체 존재 확인
    required_keys = ["base_source", "base_sha", "merge_base", "origin_main_sha", "canonical_head_sha"]
    missing = [k for k in required_keys if k not in marker]
    assert not missing, (
        f"마커에 다음 키가 없음: {missing}. 마커 내용: {marker}"
    )

    # 값 검증
    assert marker["origin_main_sha"] == sha_fresh, (
        f"origin_main_sha={marker['origin_main_sha']} != sha_fresh={sha_fresh}"
    )
    assert marker["base_sha"] == sha_fresh, (
        f"base_sha={marker['base_sha']} != sha_fresh={sha_fresh}"
    )
    assert marker["base_source"] == "origin/main", (
        f"base_source={marker['base_source']!r} != 'origin/main'"
    )
    # merge_base 는 None 이 아니어야 함 (정상 경로에서 계산됨)
    assert marker["merge_base"] is not None, (
        "merge_base 가 None — 정상 경로에서 계산되어야 함"
    )
    # canonical_head_sha 는 문자열
    assert isinstance(marker["canonical_head_sha"], str) and len(marker["canonical_head_sha"]) >= 7, (
        f"canonical_head_sha 가 유효한 SHA 가 아님: {marker['canonical_head_sha']!r}"
    )


# ---------------------------------------------------------------------------
# test_5: pre-push merge-base 분기 조건 단위 검증
# ---------------------------------------------------------------------------

def test_5_prepush_merge_base_fresh_vs_stale(tmp_path):
    """
    pre-push scope_check 의 merge-base 기반 stale/fresh 분기 로직 단위 검증.

    pre-push 훅 자체를 직접 실행하지 않는 이유:
        pre-push 는 lock 파일, taskctl, scope_check, cancelled marker 등
        다단계 선행 검증을 요구하며, 이 검증들은 temp repo 에 준비하기 위한
        setup 비용이 매우 크고 canonical workspace 의 스크립트(taskctl.py 등)에
        의존한다. canonical 무손상 원칙 상 그 의존성을 temp repo 로 완전히 격리하기가
        불가능하므로, 핵심 분기 조건(merge-base 비교)만 git 명령 레벨에서 검증한다.

    (a) fresh 케이스: HEAD 가 origin/main tip 의 후손
        → merge-base == origin/main tip (FRESH 판정 조건 성립)

    (b) stale 케이스: origin/main 과 분기된(공통 조상이 origin/main tip 이 아닌) 브랜치
        → merge-base != origin/main tip (STALE 판정 조건 성립)
    """
    env = _git_env()

    # ---- 공통 setup: bare origin + clone ----
    origin_dir, clone_dir, sha_origin_initial = _make_origin_clone(tmp_path)

    # origin/main 을 한 커밋 더 전진 (SHA_FRESH)
    seed2_dir = str(tmp_path / "seed2")
    _git(["clone", origin_dir, seed2_dir], cwd=str(tmp_path), env=env)
    _git(["checkout", "-B", "main"], cwd=seed2_dir, env=env)
    sha_fresh = _commit(seed2_dir, "fresh.txt", "fresh origin commit", env=env)
    _git(["push", "origin", "main"], cwd=seed2_dir, env=env)

    # clone 에서 fetch
    _git(["fetch", "origin"], cwd=clone_dir, env=env)
    origin_main_sha = _git(["rev-parse", "origin/main"], cwd=clone_dir, env=env)
    assert origin_main_sha == sha_fresh, "origin/main tip 확인"

    # ---------------------------------------------------------------
    # (a) FRESH 케이스: origin/main tip 기반 브랜치
    #     → merge-base(origin/main, HEAD) == origin/main tip
    # ---------------------------------------------------------------
    fresh_branch = "task/task-test5-fresh-qa"
    _git(
        ["checkout", "-b", fresh_branch, origin_main_sha],
        cwd=clone_dir,
        env=env,
    )
    # 브랜치에 커밋 하나 추가 (origin/main 의 후손)
    _commit(clone_dir, "work_fresh.txt", "work on fresh branch", env=env)

    merge_base_fresh = _git(
        ["merge-base", "origin/main", "HEAD"],
        cwd=clone_dir,
        env=env,
    )
    assert merge_base_fresh == origin_main_sha, (
        f"[FRESH] merge-base={merge_base_fresh} 가 origin/main tip={origin_main_sha} 과 달라야 함이 아님.\n"
        f"pre-push scope_check: merge-base == origin/main → three-dot diff (오탐 제거) 조건 성립해야 함."
    )

    # ---------------------------------------------------------------
    # (b) STALE 케이스: origin/main 이 전진했지만 브랜치는 이전 SHA 기반
    #     → merge-base != origin/main tip
    # ---------------------------------------------------------------
    # clone 의 로컬 main 을 sha_origin_initial (old) 로 되돌리고
    # 거기서 stale 브랜치 생성
    _git(["checkout", "main"], cwd=clone_dir, env=env)
    _git(["reset", "--hard", sha_origin_initial], cwd=clone_dir, env=env)

    stale_branch = "task/task-test5-stale-qa"
    _git(
        ["checkout", "-b", stale_branch, sha_origin_initial],
        cwd=clone_dir,
        env=env,
    )
    _commit(clone_dir, "work_stale.txt", "work on stale branch", env=env)

    merge_base_stale = _git(
        ["merge-base", "origin/main", "HEAD"],
        cwd=clone_dir,
        env=env,
    )
    assert merge_base_stale != origin_main_sha, (
        f"[STALE] merge-base={merge_base_stale} 가 origin/main tip={origin_main_sha} 와 같으면 안 됨.\n"
        f"pre-push scope_check: merge-base != origin/main → STALE_BASE 차단 조건 성립해야 함."
    )

    # 추가 명확성 검증: stale 케이스에서 merge_base 는 sha_origin_initial 과 동일
    assert merge_base_stale == sha_origin_initial, (
        f"[STALE] merge-base={merge_base_stale} 는 stale base={sha_origin_initial} 여야 함"
    )
