"""tests/taskctl/test_takeover.py
taskctl.py takeover 서브커맨드 테스트 (task-2458 Phase 2-B)

테스트 케이스:
- test_takeover_handoff_missing         — handoff 없음 → exit 1
- test_takeover_schema_invalid          — handoff schema invalid → exit 1
- test_takeover_from_branch_missing     — --from 가 존재하지 않는 branch → exit 1
- test_takeover_head_sha_mismatch       — handoff.head_sha != from-branch HEAD → exit 1
- test_takeover_changed_paths_violation — changed_paths ⊄ allowed_paths → exit 1
- test_takeover_pass                    — 정상 경로 → exit 0 + worktree + evidence 생성

fixture 패턴 (PASS 케이스):
1. tmp_path/repo 내 git repo 초기화
2. 초기 commit → task 브랜치 생성 → 추가 commit
3. refs/remotes/origin/main 을 설정 (origin/main 시뮬레이션)
4. valid handoff JSON 작성 (head_sha = task 브랜치 HEAD)
5. WORKSPACE_ROOT=tmp_path/repo 로 taskctl takeover 실행
"""
from __future__ import annotations

import glob
import json
import os
import subprocess
from pathlib import Path

# ---------------------------------------------------------------------------
# 경로 상수
# ---------------------------------------------------------------------------
WORKTREE = Path("/home/jay/workspace/.worktrees/task-2458-dev4")
TASKCTL_SCRIPT = WORKTREE / "scripts" / "taskctl.py"
SCHEMA_PATH = WORKTREE / "memory" / "specs" / "handoff-schema.json"

# 테스트용 ID (스키마 패턴 ^task-\d+$ 만족)
TEST_TASK_ID = "task-9020"
TEST_TASK_BRANCH = f"task/{TEST_TASK_ID}-dev4"
NEW_BOT = "dev5"

# ---------------------------------------------------------------------------
# 유효한 handoff 데이터 템플릿
# ---------------------------------------------------------------------------
def _make_valid_handoff(head_sha: str) -> dict:
    return {
        "task_id": TEST_TASK_ID,
        "schema_version": "1.0",
        "previous_bot": "dev4",
        "current_branch": TEST_TASK_BRANCH,
        "base_sha": "abc1234",
        "head_sha": head_sha,
        "changed_paths": ["tests/taskctl/test_takeover.py"],
        "allowed_paths": ["tests/taskctl/", "scripts/"],
        "forbidden_paths": [],
        "test_results": {},
        "handoff_reason": "takeover_request",
        "created_at": "2026-05-05T12:00:00Z",
        "pending_work": "takeover 테스트 진행 중",
    }


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

def _run_taskctl(
    args: list[str],
    workspace_root: Path,
    extra_env: dict | None = None,
) -> subprocess.CompletedProcess:
    """taskctl.py 실행 헬퍼."""
    env = dict(os.environ)
    env["WORKSPACE_ROOT"] = str(workspace_root)
    if extra_env:
        env.update(extra_env)
    return subprocess.run(
        ["python3", str(TASKCTL_SCRIPT)] + args,
        cwd=str(workspace_root),
        capture_output=True,
        text=True,
        timeout=60,
        env=env,
    )


def _setup_git_repo(base_dir: Path) -> tuple[Path, str]:
    """base_dir/repo 내에 임시 git repo를 설정하고 (repo_path, task_branch_head_sha) 반환.

    절차:
    1. git init --initial-branch=main
    2. 초기 commit (origin/main 역할)
    3. task 브랜치 checkout → 추가 commit
    4. git update-ref refs/remotes/origin/main main (origin/main 참조 생성)
    """
    repo = base_dir / "repo"
    repo.mkdir(parents=True, exist_ok=True)

    def _git(*args, check=True):
        return subprocess.run(
            ["git"] + list(args),
            cwd=str(repo),
            capture_output=True,
            text=True,
            check=check,
        )

    _git("init", "--initial-branch=main")
    _git("config", "user.email", "test@test.com")
    _git("config", "user.name", "Test Bot")

    # 초기 commit on main
    (repo / "README.md").write_text("init")
    _git("add", ".")
    _git("commit", "-m", "init: main base")

    # origin/main 참조 설정 (bare remote 없이 simulated)
    _git("update-ref", "refs/remotes/origin/main", "HEAD")

    # task 브랜치 생성
    _git("checkout", "-b", TEST_TASK_BRANCH)
    (repo / "task_file.txt").write_text("task work")
    _git("add", ".")
    _git("commit", "-m", f"feat: {TEST_TASK_ID} work")

    # task 브랜치 HEAD SHA 조회
    head_sha = _git("rev-parse", "HEAD").stdout.strip()

    # 메인 워크스페이스는 main branch 상태여야 한다
    # (start_task_guard 검증 #7: main HEAD == origin/main)
    _git("checkout", "main")

    return repo, head_sha


def _write_handoff_in_repo(repo: Path, data: dict) -> Path:
    """repo/memory/handoffs/<task_id>.json 기록 + 스키마/start_guard 복사."""
    spec_dir = repo / "memory" / "specs"
    spec_dir.mkdir(parents=True, exist_ok=True)
    schema_dst = spec_dir / "handoff-schema.json"
    if not schema_dst.exists():
        schema_dst.write_bytes(SCHEMA_PATH.read_bytes())

    # start_task_guard.py + validate_handoff.py 를 임시 워크스페이스 scripts/ 로 복사
    scripts_dst = repo / "scripts"
    scripts_dst.mkdir(parents=True, exist_ok=True)
    for sname in ("start_task_guard.py", "validate_handoff.py"):
        src = WORKTREE / "scripts" / sname
        dst = scripts_dst / sname
        if src.exists() and not dst.exists():
            dst.write_bytes(src.read_bytes())
            dst.chmod(0o755)

    hdir = repo / "memory" / "handoffs"
    hdir.mkdir(parents=True, exist_ok=True)
    hpath = hdir / f"{TEST_TASK_ID}.json"
    hpath.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
    return hpath


def _takeover_args(task_id: str = TEST_TASK_ID,
                   from_branch: str = TEST_TASK_BRANCH,
                   new_bot: str = NEW_BOT) -> list[str]:
    return ["takeover", task_id, "--from", from_branch, "--bot", new_bot]


# ---------------------------------------------------------------------------
# test_takeover_handoff_missing
# ---------------------------------------------------------------------------

def test_takeover_handoff_missing(tmp_path: Path):
    """handoff JSON이 존재하지 않으면 takeover → exit 1."""
    repo, _ = _setup_git_repo(tmp_path)
    # handoff 파일 없이 실행
    spec_dir = repo / "memory" / "specs"
    spec_dir.mkdir(parents=True, exist_ok=True)
    (spec_dir / "handoff-schema.json").write_bytes(SCHEMA_PATH.read_bytes())

    result = _run_taskctl(_takeover_args(), workspace_root=repo)
    assert result.returncode == 1, (
        f"handoff 없는데 exit 0\nstdout: {result.stdout}\nstderr: {result.stderr}"
    )
    combined = result.stderr + result.stdout
    assert (
        "handoff" in combined.lower()
        or "없음" in combined
        or "not found" in combined.lower()
        or "FAIL" in combined
    ), f"에러 메시지 없음\nstderr: {result.stderr}\nstdout: {result.stdout}"


# ---------------------------------------------------------------------------
# test_takeover_schema_invalid
# ---------------------------------------------------------------------------

def test_takeover_schema_invalid(tmp_path: Path):
    """handoff JSON이 schema를 만족하지 않으면 takeover → exit 1."""
    repo, _ = _setup_git_repo(tmp_path)
    # 필수 필드 누락 handoff
    invalid_data = {
        "task_id": TEST_TASK_ID,
        "handoff_reason": "takeover_request",
        # schema_version, previous_bot 등 필수 필드 누락
    }
    _write_handoff_in_repo(repo, invalid_data)

    result = _run_taskctl(_takeover_args(), workspace_root=repo)
    assert result.returncode == 1, (
        f"schema invalid인데 exit 0\nstdout: {result.stdout}\nstderr: {result.stderr}"
    )
    combined = result.stderr + result.stdout
    assert (
        "schema" in combined.lower()
        or "FAIL" in combined
        or "invalid" in combined.lower()
        or "검증" in combined
    ), f"스키마 에러 메시지 없음\ncombined: {combined}"


# ---------------------------------------------------------------------------
# test_takeover_from_branch_missing
# ---------------------------------------------------------------------------

def test_takeover_from_branch_missing(tmp_path: Path):
    """--from 에 존재하지 않는 branch 지정 → exit 1."""
    repo, head_sha = _setup_git_repo(tmp_path)
    data = _make_valid_handoff(head_sha)
    _write_handoff_in_repo(repo, data)

    nonexistent_branch = "task/task-9999-devX"
    result = _run_taskctl(
        _takeover_args(from_branch=nonexistent_branch),
        workspace_root=repo,
    )
    assert result.returncode == 1, (
        f"존재하지 않는 branch인데 exit 0\nstdout: {result.stdout}\nstderr: {result.stderr}"
    )
    combined = result.stderr + result.stdout
    assert (
        "branch" in combined.lower()
        or "rev-parse" in combined
        or "FAIL" in combined
        or "없" in combined
        or "not found" in combined.lower()
    ), f"branch 없음 에러 메시지 없음\ncombined: {combined}"


# ---------------------------------------------------------------------------
# test_takeover_head_sha_mismatch
# ---------------------------------------------------------------------------

def test_takeover_head_sha_mismatch(tmp_path: Path):
    """handoff.head_sha != --from branch HEAD → exit 1."""
    repo, real_head_sha = _setup_git_repo(tmp_path)

    # 의도적으로 잘못된 SHA 기록
    data = _make_valid_handoff("0000000deadbeef")
    _write_handoff_in_repo(repo, data)

    result = _run_taskctl(_takeover_args(), workspace_root=repo)
    assert result.returncode == 1, (
        f"head_sha 불일치인데 exit 0\nstdout: {result.stdout}\nstderr: {result.stderr}"
        f"\nreal_head_sha={real_head_sha}"
    )
    combined = result.stderr + result.stdout
    assert (
        "head_sha" in combined
        or "sha" in combined.lower()
        or "불일치" in combined
        or "FAIL" in combined
        or "mismatch" in combined.lower()
    ), f"SHA 불일치 에러 메시지 없음\ncombined: {combined}"


# ---------------------------------------------------------------------------
# test_takeover_changed_paths_violation
# ---------------------------------------------------------------------------

def test_takeover_changed_paths_violation(tmp_path: Path):
    """changed_paths ⊄ allowed_paths → exit 1."""
    repo, head_sha = _setup_git_repo(tmp_path)

    data = _make_valid_handoff(head_sha)
    data["allowed_paths"] = ["tests/taskctl/"]
    data["changed_paths"] = [
        "tests/taskctl/test_takeover.py",
        "scripts/dangerous.py",  # allowed 범위 밖
    ]
    _write_handoff_in_repo(repo, data)

    result = _run_taskctl(_takeover_args(), workspace_root=repo)
    assert result.returncode == 1, (
        f"changed_paths 위반인데 exit 0\nstdout: {result.stdout}\nstderr: {result.stderr}"
    )
    combined = result.stderr + result.stdout
    assert (
        "allowed" in combined.lower()
        or "초과" in combined
        or "violation" in combined.lower()
        or "FAIL" in combined
    ), f"경로 위반 에러 메시지 없음\ncombined: {combined}"


# ---------------------------------------------------------------------------
# test_takeover_pass
# ---------------------------------------------------------------------------

def test_takeover_pass(tmp_path: Path):
    """정상 경로: exit 0 + 새 worktree 존재 + evidence JSON 존재.

    검증 항목:
    1. exit code == 0
    2. .worktrees/<task-id>-<new-bot>/ 디렉토리 존재 (worktree add 완료)
    3. .tasks/evidence/<task-id>/takeover-*.json 존재
    """
    repo, head_sha = _setup_git_repo(tmp_path)

    data = _make_valid_handoff(head_sha)
    _write_handoff_in_repo(repo, data)

    result = _run_taskctl(
        _takeover_args(),
        workspace_root=repo,
    )
    assert result.returncode == 0, (
        f"정상 takeover인데 exit 1\nstdout: {result.stdout}\nstderr: {result.stderr}"
    )

    # 2. 새 worktree 디렉토리 존재 확인
    # worktree 경로 패턴: .worktrees/<task-id>-<new-bot> 또는 <task-id>-<new-bot>
    # taskctl 구현에 따라 경로가 다를 수 있으므로 여러 패턴 검색
    new_worktree_candidates = [
        repo.parent / ".worktrees" / f"{TEST_TASK_ID}-{NEW_BOT}",
        repo / ".worktrees" / f"{TEST_TASK_ID}-{NEW_BOT}",
        repo.parent / f"{TEST_TASK_ID}-{NEW_BOT}",
    ]
    worktree_found = any(p.exists() for p in new_worktree_candidates)
    # worktree list로도 확인
    wt_list = subprocess.run(
        ["git", "worktree", "list"],
        cwd=str(repo),
        capture_output=True,
        text=True,
    )
    worktree_in_list = NEW_BOT in wt_list.stdout or TEST_TASK_ID in wt_list.stdout
    assert worktree_found or worktree_in_list, (
        f"새 worktree가 생성되지 않음\n"
        f"candidates checked: {new_worktree_candidates}\n"
        f"git worktree list:\n{wt_list.stdout}\n"
        f"stdout: {result.stdout}\nstderr: {result.stderr}"
    )

    # 3. evidence JSON 존재 확인
    evidence_pattern = str(repo / ".tasks" / "evidence" / TEST_TASK_ID / "takeover-*.json")
    evidence_files = glob.glob(evidence_pattern)
    assert evidence_files, (
        f"evidence JSON이 생성되지 않음\n"
        f"검색 패턴: {evidence_pattern}\n"
        f"stdout: {result.stdout}\nstderr: {result.stderr}"
    )

    # evidence JSON 내용 기본 검증
    ev_data = json.loads(Path(evidence_files[0]).read_text(encoding="utf-8"))
    assert ev_data.get("task_id") == TEST_TASK_ID or "task_id" in ev_data or "takeover" in str(ev_data).lower(), (
        f"evidence JSON 내용 이상: {ev_data}"
    )
