"""task-2457 Phase 2-A: git hooks (pre-commit / pre-push) 테스트용 공용 픽스처.

- 모리건 (dev3-team / tester) 작성.
- 실제 hook 스크립트 경로:
    /home/jay/workspace/.worktrees/task-2457-dev3/scripts/git-hooks/{pre-commit,pre-push}
- 각 테스트는 tmp_path 안에 git init된 격리 repo를 받아, 진짜 hook 파일을 그대로 복사해 실행한다.
- 명세 가정 (루가 작성 중인 hook 본체 기준):
  * pre-commit: lock 존재 + branch task-id 일치 + main 아닌 task 패턴(task/task-<n>-...) 검증
  * pre-push: 위 + scope_check + cancelled marker + main direct push 거부
  * TASKCTL_BYPASS=1 + TASKCTL_BYPASS_REASON=<text> 시 bypass 허용 + evidence 기록
"""

from __future__ import annotations

import json
import os
import shutil
import subprocess
from pathlib import Path
from typing import Iterable

import pytest

# 진짜 hook 스크립트가 위치한 절대 경로 (루가 작성 중)
REAL_HOOKS_DIR = Path(
    "/home/jay/workspace/.worktrees/task-2457-dev3/scripts/git-hooks"
)


def _run(cmd: list[str], cwd: Path, env: dict | None = None) -> subprocess.CompletedProcess:
    """헬퍼: 서브프로세스 실행. 실패 시 에러 메시지를 가독성 있게 표시."""
    return subprocess.run(
        cmd,
        cwd=str(cwd),
        env=env if env is not None else os.environ.copy(),
        capture_output=True,
        text=True,
        check=False,
    )


@pytest.fixture
def git_repo(tmp_path: Path) -> Path:
    """임시 git repo + scripts/git-hooks 복사 + core.hooksPath 설정.

    반환: repo 루트 Path.

    구성:
      - git init -b main (main 브랜치로 시작)
      - user.email / user.name 설정 (CI에서도 commit 가능)
      - scripts/git-hooks/ 에 실제 hook 파일 복사 (실행 권한 유지)
      - .tasks/{locks,evidence} / memory/{events,capabilities} 디렉토리 미리 생성
      - 초기 commit 1개 생성 (HEAD가 존재해야 git rev-parse 등이 정상 동작)
    """
    repo = tmp_path / "repo"
    repo.mkdir()

    # 1) git init (main 브랜치로 시작; 후속 테스트에서 task 브랜치로 checkout)
    init = _run(["git", "init", "-b", "main"], cwd=repo)
    if init.returncode != 0:
        # 구버전 git은 -b 미지원 → fallback
        _run(["git", "init"], cwd=repo)
        _run(["git", "checkout", "-b", "main"], cwd=repo)

    _run(["git", "config", "user.email", "morrigan@dev3.local"], cwd=repo)
    _run(["git", "config", "user.name", "Morrigan"], cwd=repo)
    _run(["git", "config", "commit.gpgsign", "false"], cwd=repo)

    # 2) hook 파일 복사
    hooks_dst = repo / "scripts" / "git-hooks"
    hooks_dst.mkdir(parents=True, exist_ok=True)
    if REAL_HOOKS_DIR.exists():
        for hook_name in ("pre-commit", "pre-push"):
            src = REAL_HOOKS_DIR / hook_name
            if src.exists():
                dst = hooks_dst / hook_name
                shutil.copy2(src, dst)
                # 실행 권한 보장
                dst.chmod(0o755)

    # 3) core.hooksPath 설정 (git 자체가 hook을 부르도록)
    _run(
        ["git", "config", "core.hooksPath", "scripts/git-hooks"],
        cwd=repo,
    )

    # 4) 명세에서 사용하는 디렉토리 트리 미리 준비
    (repo / ".tasks" / "locks").mkdir(parents=True, exist_ok=True)
    (repo / ".tasks" / "evidence").mkdir(parents=True, exist_ok=True)
    (repo / "memory" / "events").mkdir(parents=True, exist_ok=True)
    (repo / "memory" / "capabilities").mkdir(parents=True, exist_ok=True)

    # 5) 초기 commit (HEAD 필요; --no-verify 로 hook 우회)
    (repo / "README.md").write_text("# test repo\n", encoding="utf-8")
    _run(["git", "add", "README.md"], cwd=repo)
    _run(
        ["git", "commit", "-m", "init", "--no-verify"],
        cwd=repo,
    )

    # 6) origin remote (bare) 생성 + main push
    #    pre-push hook 의 scope_check 가 `git diff --name-only origin/main..HEAD`
    #    를 수행하므로 origin/main 레퍼런스가 필요하다.
    bare = tmp_path / "origin.git"
    _run(["git", "init", "--bare", str(bare)], cwd=tmp_path)
    _run(["git", "remote", "add", "origin", str(bare)], cwd=repo)
    _run(["git", "push", "--no-verify", "-u", "origin", "main"], cwd=repo)

    return repo


@pytest.fixture
def make_lock():
    """헬퍼 픽스처: .tasks/locks/<task-id>.lock JSON 파일 생성.

    사용 예:
        make_lock(repo, task_id="task-2457", branch="task/task-2457-dev3")
    """

    def _factory(repo: Path, task_id: str, branch: str, **extra) -> Path:
        lock_path = repo / ".tasks" / "locks" / f"{task_id}.lock"
        payload = {
            "task_id": task_id,
            "branch": branch,
            "owner": os.environ.get("USER", "unknown"),
            "acquired_at": "2026-05-05T00:00:00Z",
        }
        payload.update(extra)
        lock_path.parent.mkdir(parents=True, exist_ok=True)
        lock_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
        return lock_path

    return _factory


@pytest.fixture
def make_cancelled():
    """헬퍼 픽스처: memory/events/<task-id>.cancelled 빈 파일 생성."""

    def _factory(repo: Path, task_id: str) -> Path:
        marker = repo / "memory" / "events" / f"{task_id}.cancelled"
        marker.parent.mkdir(parents=True, exist_ok=True)
        marker.write_text("", encoding="utf-8")
        return marker

    return _factory


@pytest.fixture
def make_capability():
    """헬퍼 픽스처: memory/capabilities/<task-id>.json 생성 (scope_check용).

    allowed_paths: 변경 허용 경로(글롭/접두) 리스트.
    """

    def _factory(repo: Path, task_id: str, allowed_paths: Iterable[str]) -> Path:
        cap_path = repo / "memory" / "capabilities" / f"{task_id}.json"
        # 명세: allowed_resources 는 dict로 paths 리스트를 가진다
        # ({"allowed_resources": {"paths": [...]}}). hook 본체가 이 구조를 사용.
        payload = {
            "task_id": task_id,
            "allowed_resources": {"paths": list(allowed_paths)},
        }
        cap_path.parent.mkdir(parents=True, exist_ok=True)
        cap_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
        return cap_path

    return _factory


@pytest.fixture
def checkout_branch():
    """헬퍼: repo 안에서 새 branch로 checkout."""

    def _factory(repo: Path, branch: str) -> None:
        # 이미 존재하면 switch, 없으면 -b 로 생성
        existing = _run(["git", "rev-parse", "--verify", branch], cwd=repo)
        if existing.returncode == 0:
            _run(["git", "checkout", branch], cwd=repo)
        else:
            _run(["git", "checkout", "-b", branch], cwd=repo)

    return _factory


@pytest.fixture
def run_hook():
    """헬퍼: hook 스크립트를 직접 bash로 실행한다.

    사용 예:
        result = run_hook(repo, "pre-commit", env_extra={"TASKCTL_BYPASS": "1"})
        result = run_hook(repo, "pre-push", stdin="refs/heads/x sha refs/heads/main sha\n")
    """

    def _factory(
        repo: Path,
        hook_name: str,
        stdin: str | None = None,
        env_extra: dict | None = None,
    ) -> subprocess.CompletedProcess:
        hook_path = repo / "scripts" / "git-hooks" / hook_name
        env = os.environ.copy()
        # GIT_DIR/WORKSPACE 등이 hook 안에서 사용될 수 있으므로 명시
        env["GIT_DIR"] = str(repo / ".git")
        env["WORKSPACE"] = str(repo)
        if env_extra:
            env.update(env_extra)
        return subprocess.run(
            ["bash", str(hook_path)],
            cwd=str(repo),
            env=env,
            input=stdin if stdin is not None else "",
            capture_output=True,
            text=True,
            check=False,
        )

    return _factory
