"""task-2457 Phase 2-A: pre-push hook 테스트.

명세:
  차단 케이스 (exit 1):
    1. main direct push (stdin: "refs/heads/x sha refs/heads/main sha") → "main direct push prohibited"
    2. cancelled marker (memory/events/<task-id>.cancelled 존재) → "task <id> cancelled"
    3. lock 부재 / branch-lock 불일치 → "lock missing" or "task-id mismatch"
    4. scope 위반 (변경 파일이 allowed_resources에 없음) → "scope violation"

  PASS 케이스 (exit 0):
    P3. lock + scope_check PASS + cancelled 아님 + main 아님
    P4. TASKCTL_BYPASS=1 + reason 제공 → bypass evidence 기록 + PASS

  Fallback 케이스:
    - taskctl verify 미존재 → fallback evidence 기록 (verify_status="fallback") + PASS
      파일: .tasks/evidence/<task-id>/verify-fallback-*.json
      필드: taskctl_verify_status / reason / lock_check / scope_check
"""

from __future__ import annotations

import json
import os
import subprocess
from pathlib import Path


def _commit_file(repo: Path, rel_path: str, content: str = "x") -> None:
    """헬퍼: repo 안에 파일을 만들고 --no-verify 로 commit (hook 우회)."""
    f = repo / rel_path
    f.parent.mkdir(parents=True, exist_ok=True)
    f.write_text(content, encoding="utf-8")
    subprocess.run(["git", "add", rel_path], cwd=str(repo), check=True, capture_output=True)
    subprocess.run(
        ["git", "commit", "-m", f"add {rel_path}", "--no-verify"],
        cwd=str(repo),
        check=True,
        capture_output=True,
    )


def _stdin_for(remote_branch: str = "task/task-2457-dev3") -> str:
    """헬퍼: pre-push hook 표준 stdin 라인 생성."""
    local = "task/task-2457-dev3"
    sha_l = "abc1234abc1234abc1234abc1234abc1234abc1"
    sha_r = "def5678def5678def5678def5678def5678def5"
    return f"refs/heads/{local} {sha_l} refs/heads/{remote_branch} {sha_r}\n"


# ---------- 차단 케이스 ----------


class TestPrePushFail:
    def test_main_direct_push_blocks(
        self, git_repo, checkout_branch, make_lock, run_hook
    ):
        """1) refspec 의 remote 가 refs/heads/main → 차단."""
        branch = "task/task-2457-dev3"
        checkout_branch(git_repo, branch)
        make_lock(git_repo, task_id="task-2457", branch=branch)
        stdin = _stdin_for(remote_branch="main")
        result = run_hook(git_repo, "pre-push", stdin=stdin)
        assert result.returncode == 1, (
            f"main direct push 인데 통과됨. stdout={result.stdout!r} stderr={result.stderr!r}"
        )
        assert "[BLOCKED]" in result.stderr
        assert "main direct push prohibited" in result.stderr

    def test_cancelled_marker_blocks(
        self,
        git_repo,
        checkout_branch,
        make_lock,
        make_cancelled,
        run_hook,
    ):
        """2) cancelled marker 존재 → 차단."""
        branch = "task/task-2457-dev3"
        checkout_branch(git_repo, branch)
        make_lock(git_repo, task_id="task-2457", branch=branch)
        make_cancelled(git_repo, "task-2457")
        result = run_hook(git_repo, "pre-push", stdin=_stdin_for())
        assert result.returncode == 1, (
            f"cancelled marker 인데 통과됨. stdout={result.stdout!r} stderr={result.stderr!r}"
        )
        assert "[BLOCKED]" in result.stderr
        assert "cancelled" in result.stderr.lower()
        assert "task-2457" in result.stderr

    def test_lock_missing_blocks(
        self, git_repo, checkout_branch, run_hook
    ):
        """3) lock 부재 → 차단."""
        branch = "task/task-2457-dev3"
        checkout_branch(git_repo, branch)
        # lock 의도적으로 미생성
        result = run_hook(git_repo, "pre-push", stdin=_stdin_for())
        assert result.returncode == 1, (
            f"lock 부재인데 통과됨. stdout={result.stdout!r} stderr={result.stderr!r}"
        )
        assert "[BLOCKED]" in result.stderr
        msg = result.stderr.lower()
        assert ("lock missing" in msg) or ("task-id mismatch" in msg) or ("lock" in msg), (
            f"lock 관련 메시지 부재: {result.stderr!r}"
        )

    def test_scope_violation_blocks(
        self,
        git_repo,
        checkout_branch,
        make_lock,
        make_capability,
        run_hook,
    ):
        """4) capability 의 allowed_resources 에 없는 파일 변경 → 차단."""
        branch = "task/task-2457-dev3"
        checkout_branch(git_repo, branch)
        make_lock(git_repo, task_id="task-2457", branch=branch)
        # 허용된 경로: tests/git_hooks/ 만
        make_capability(
            git_repo,
            "task-2457",
            allowed_paths=["tests/git_hooks/"],
        )
        # 허용되지 않은 경로의 파일을 push 대상에 포함
        _commit_file(git_repo, "scripts/forbidden.py", "print('out of scope')\n")
        result = run_hook(git_repo, "pre-push", stdin=_stdin_for())
        assert result.returncode == 1, (
            f"scope 위반인데 통과됨. stdout={result.stdout!r} stderr={result.stderr!r}"
        )
        assert "[BLOCKED]" in result.stderr
        assert "scope violation" in result.stderr.lower()


# ---------- PASS 케이스 ----------


class TestPrePushPass:
    def test_valid_lock_scope_passes(
        self,
        git_repo,
        checkout_branch,
        make_lock,
        make_capability,
        run_hook,
    ):
        """P3) lock + scope PASS + cancelled 아님 + main 아님 → exit 0."""
        branch = "task/task-2457-dev3"
        checkout_branch(git_repo, branch)
        make_lock(git_repo, task_id="task-2457", branch=branch)
        make_capability(
            git_repo,
            "task-2457",
            allowed_paths=["tests/git_hooks/", "scripts/git-hooks/"],
        )
        # 허용 경로 안의 파일 변경
        _commit_file(git_repo, "tests/git_hooks/sample.txt", "ok\n")
        result = run_hook(git_repo, "pre-push", stdin=_stdin_for())
        assert result.returncode == 0, (
            f"정상 케이스인데 차단됨. stdout={result.stdout!r} stderr={result.stderr!r}"
        )

    def test_bypass_with_reason_passes_and_writes_evidence(
        self, git_repo, checkout_branch, run_hook
    ):
        """P4) TASKCTL_BYPASS=1 + reason → PASS + evidence 기록."""
        branch = "task/task-2457-dev3"
        checkout_branch(git_repo, branch)
        env = {
            "TASKCTL_BYPASS": "1",
            "TASKCTL_BYPASS_REASON": "emergency push for outage rollback",
        }
        result = run_hook(git_repo, "pre-push", stdin=_stdin_for(), env_extra=env)
        assert result.returncode == 0, (
            f"bypass push 통과 못함. stdout={result.stdout!r} stderr={result.stderr!r}"
        )

        evidence_dir = git_repo / ".tasks" / "evidence" / "task-2457"
        assert evidence_dir.is_dir(), "evidence 디렉토리 미생성"
        bypass_files = list(evidence_dir.glob("bypass-*.json"))
        assert bypass_files, f"bypass-*.json 부재. 내용={list(evidence_dir.iterdir())}"

        data = json.loads(bypass_files[0].read_text(encoding="utf-8"))
        assert data.get("bypass") is True
        assert data.get("timestamp")
        assert data.get("actor") == os.environ.get("USER")
        assert data.get("reason") == "emergency push for outage rollback"


# ---------- Fallback 케이스 ----------


class TestPrePushFallback:
    def test_taskctl_verify_missing_uses_fallback_evidence(
        self,
        git_repo,
        checkout_branch,
        make_lock,
        make_capability,
        run_hook,
    ):
        """taskctl verify 가 사용 불가 → fallback evidence 기록 + PASS.

        - 파일: .tasks/evidence/<task-id>/verify-fallback-*.json
        - 필드: taskctl_verify_status="fallback", reason, lock_check="PASS", scope_check="PASS"
        """
        branch = "task/task-2457-dev3"
        checkout_branch(git_repo, branch)
        make_lock(git_repo, task_id="task-2457", branch=branch)
        make_capability(
            git_repo,
            "task-2457",
            allowed_paths=["tests/git_hooks/"],
        )
        _commit_file(git_repo, "tests/git_hooks/in_scope.txt", "ok\n")

        # 명세: hook 은 scripts/taskctl_verify.py 가 없으면 fallback evidence 를 기록한다.
        # 임시 repo 에는 해당 스크립트를 일부러 만들지 않는다 (= fallback 트리거).
        verify_script = git_repo / "scripts" / "taskctl_verify.py"
        assert not verify_script.exists(), "테스트 전제: taskctl_verify.py 부재"

        result = run_hook(git_repo, "pre-push", stdin=_stdin_for())
        assert result.returncode == 0, (
            f"fallback 통과 못함. stdout={result.stdout!r} stderr={result.stderr!r}"
        )

        evidence_dir = git_repo / ".tasks" / "evidence" / "task-2457"
        assert evidence_dir.is_dir(), "evidence 디렉토리 미생성"
        fallback_files = list(evidence_dir.glob("verify-fallback-*.json"))
        assert fallback_files, (
            f"verify-fallback-*.json 부재. 내용={list(evidence_dir.iterdir())}"
        )

        data = json.loads(fallback_files[0].read_text(encoding="utf-8"))
        assert data.get("taskctl_verify_status") == "fallback", (
            f"taskctl_verify_status != fallback: {data}"
        )
        assert data.get("reason"), f"reason 누락: {data}"
        assert data.get("lock_check") == "PASS", f"lock_check != PASS: {data}"
        assert data.get("scope_check") == "PASS", f"scope_check != PASS: {data}"
