# pyright: reportUnusedVariable=false
"""
test_pre_push_guard.py — pre_push_guard.py 회귀 테스트 (Guard MVP Phase 1, task-2434)

시나리오 (7개):
    1. test_clean_tree_scope_match_qc_match          — clean + scope 일치 + qc 일치 → rc=0
    2. test_modified_outside_scope_fail_b1            — task scope 외 파일 modified → rc=1 (B-1)
    3. test_behind_origin_fail_b2                     — HEAD behind base ref → rc=1 (B-2)
    4. test_forbidden_path_match_fail_b3              — head_diff에 forbidden_paths 매치 → rc=1 (B-3)
    5. test_qc_warn_report_overall_pass_fail_b4       — qc=WARN + 보고서=OVERALL PASS → rc=1 (B-4)
    6. test_normal_push_pass                          — 정상 push 케이스 → rc=0
    7. test_system_ignore_excluded_b1                 — memory/logs/*.log modified → system-ignore로 PASS
"""

import json
import subprocess
import sys
from pathlib import Path

import pytest

# ---------------------------------------------------------------------------
# 상수
# ---------------------------------------------------------------------------
_SCRIPTS_DIR = Path("/home/jay/workspace/scripts")
_GUARD_PY    = _SCRIPTS_DIR / "pre_push_guard.py"

TASK_ID      = "task-2434-test"

# ---------------------------------------------------------------------------
# 헬퍼: 임시 workspace + git repo 구성
# ---------------------------------------------------------------------------

def _init_git(path: Path) -> None:
    def _g(*args: str) -> None:
        subprocess.run(
            ["git", "-C", str(path)] + list(args),
            check=True, capture_output=True,
        )
    _g("init")
    _g("config", "user.email", "test@test.com")
    _g("config", "user.name",  "Test")


def _make_commit(path: Path, file_rel: str, content: str = "data\n") -> str:
    target = path / file_rel
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(content)
    subprocess.run(["git", "-C", str(path), "add", file_rel],
                   check=True, capture_output=True)
    subprocess.run(["git", "-C", str(path), "commit", "-m", f"add {file_rel}"],
                   check=True, capture_output=True)
    r = subprocess.run(
        ["git", "-C", str(path), "rev-parse", "HEAD"],
        check=True, capture_output=True, text=True,
    )
    return r.stdout.strip()


def _get_first_sha(path: Path) -> str:
    r = subprocess.run(
        ["git", "-C", str(path), "rev-list", "--max-parents=0", "HEAD"],
        check=True, capture_output=True, text=True,
    )
    return r.stdout.strip()


def _setup_workspace(tmp: Path, task_id: str,
                     allowed_paths: list[str] | None = None,
                     forbidden_paths: list[str] | None = None,
                     qc_verdict_json: str = "PASS",
                     report_content: str | None = None) -> tuple[Path, Path]:
    """
    tmp/workspace 구조 생성:
      memory/capabilities/<task_id>.json
      memory/tasks/<task_id>.md
      memory/events/<task_id>.qc-result
      memory/reports/<task_id>.md  (선택)
    또한 tmp/repo 에 최소 git repo 생성.
    반환: (workspace, repo)
    """
    ws = tmp / "workspace"

    for d in ("memory/capabilities", "memory/tasks",
              "memory/events", "memory/reports", "memory/logs"):
        (ws / d).mkdir(parents=True, exist_ok=True)

    # capability snapshot
    cap = {
        "task_id": task_id,
        "allowed_resources": {
            "paths": allowed_paths or ["scripts/**", "tests/**"],
            "forbidden_paths": forbidden_paths or [],
        },
    }
    (ws / "memory" / "capabilities" / f"{task_id}.json").write_text(
        json.dumps(cap), encoding="utf-8"
    )

    # task 파일
    (ws / "memory" / "tasks" / f"{task_id}.md").write_text(
        f"# {task_id}\n", encoding="utf-8"
    )

    # qc-result — "qc_result" 키 사용 (실 시스템 형식)
    qc_result = {"task_id": task_id, "qc_result": qc_verdict_json, "checks_summary": {}}
    (ws / "memory" / "events" / f"{task_id}.qc-result").write_text(
        json.dumps(qc_result), encoding="utf-8"
    )

    # 보고서 (선택)
    if report_content is not None:
        (ws / "memory" / "reports" / f"{task_id}.md").write_text(
            report_content, encoding="utf-8"
        )

    # git repo
    repo = tmp / "repo"
    repo.mkdir()
    _init_git(repo)

    return ws, repo


def _run_guard(ws: Path, repo: Path, task_id: str,
               base_sha: str | None = None,
               extra_args: list[str] | None = None) -> tuple[int, str, str]:
    """pre_push_guard.py 실행 → (returncode, stdout, stderr)."""
    cmd = [
        sys.executable,
        str(_GUARD_PY),
        "--task-id", task_id,
        "--workspace", str(ws),
        "--cwd", str(repo),
    ]
    if base_sha:
        cmd += ["--base-sha", base_sha]
    if extra_args:
        cmd += extra_args

    r = subprocess.run(cmd, capture_output=True, text=True, cwd=str(ws))
    return r.returncode, r.stdout, r.stderr


# ---------------------------------------------------------------------------
# guard.py가 아직 미구현인 경우 스킵 마커
# ---------------------------------------------------------------------------
_GUARD_MISSING = not _GUARD_PY.exists()
_skip_if_missing = pytest.mark.skipif(
    _GUARD_MISSING,
    reason="pre_push_guard.py not yet implemented (Thor in progress)",
)

# ---------------------------------------------------------------------------
# 1. test_clean_tree_scope_match_qc_match
# ---------------------------------------------------------------------------

@_skip_if_missing
def test_clean_tree_scope_match_qc_match(tmp_path: Path) -> None:
    """clean working tree + scope 일치 + qc=PASS → rc=0 (PASS)."""
    report = (
        "---\n"
        "qc_verdict: PASS\n"
        "---\n"
        "# Report\n\n"
        "## QC Verdict\n\nPASS\n"
    )
    ws, repo = _setup_workspace(
        tmp_path, TASK_ID,
        allowed_paths=["scripts/**", "tests/**"],
        qc_verdict_json="PASS",
        report_content=report,
    )

    # 초기 커밋 (base)
    _make_commit(repo, "scripts/hello.py")
    base_sha = _get_first_sha(repo)
    # task scope 내 파일 커밋
    _make_commit(repo, "tests/test_hello.py")
    # working tree clean

    rc, stdout, stderr = _run_guard(ws, repo, TASK_ID, base_sha=base_sha)
    assert rc == 0, (
        f"expected PASS (rc=0) but got rc={rc}\nstdout={stdout}\nstderr={stderr}"
    )


# ---------------------------------------------------------------------------
# 2. test_modified_outside_scope_fail_b1
# ---------------------------------------------------------------------------

@_skip_if_missing
def test_modified_outside_scope_fail_b1(tmp_path: Path) -> None:
    """task scope 외 파일이 working tree에 modified → B-1 FAIL, rc=1."""
    ws, repo = _setup_workspace(
        tmp_path, TASK_ID,
        allowed_paths=["scripts/**", "tests/**"],
        qc_verdict_json="PASS",
    )

    _make_commit(repo, "scripts/hello.py")
    base_sha = _get_first_sha(repo)
    _make_commit(repo, "tests/test_hello.py")

    # scope 외 파일 dirty (extension/foo.js 수정, unstaged)
    out_of_scope = repo / "extension" / "foo.js"
    out_of_scope.parent.mkdir(parents=True, exist_ok=True)
    out_of_scope.write_text("modified\n")
    subprocess.run(
        ["git", "-C", str(repo), "add", "extension/foo.js"],
        check=True, capture_output=True,
    )
    # staged but not committed — B-1 (working tree dirty with out-of-scope)

    rc, stdout, stderr = _run_guard(ws, repo, TASK_ID, base_sha=base_sha)
    assert rc == 1, (
        f"expected FAIL (rc=1, B-1) but got rc={rc}\nstdout={stdout}\nstderr={stderr}"
    )
    combined = stdout + stderr
    assert "B-1" in combined or "working" in combined.lower() or "dirty" in combined.lower(), (
        f"Expected B-1 / dirty / working mention in output:\n{combined}"
    )


# ---------------------------------------------------------------------------
# 3. test_behind_origin_fail_b2
# ---------------------------------------------------------------------------

@_skip_if_missing
def test_behind_origin_fail_b2(tmp_path: Path) -> None:
    """base_sha 이후 HEAD가 뒤처진 상황 → B-2 FAIL, rc=1.

    실제 'behind origin' 상태를 흉내 내기 위해:
    origin 역할의 bare repo를 만들고 origin에 추가 커밋을 push한 후
    로컬을 fetch 없이 HEAD 고정.
    """
    ws, repo = _setup_workspace(
        tmp_path, TASK_ID,
        allowed_paths=["scripts/**"],
        qc_verdict_json="PASS",
    )

    # origin bare repo — explicit main branch
    origin = tmp_path / "origin.git"
    subprocess.run(
        ["git", "init", "--bare", "--initial-branch=main", str(origin)],
        check=True, capture_output=True,
    )

    _make_commit(repo, "scripts/a.py")
    base_sha = _get_first_sha(repo)

    subprocess.run(
        ["git", "-C", str(repo), "remote", "add", "origin", str(origin)],
        check=True, capture_output=True,
    )
    subprocess.run(
        ["git", "-C", str(repo), "push", "-u", "origin", "HEAD:main"],
        check=True, capture_output=True,
    )

    # origin에 추가 커밋 (clone을 통해) — user config 명시 설정 필요
    clone = tmp_path / "clone"
    subprocess.run(
        ["git", "clone", str(origin), str(clone)],
        check=True, capture_output=True,
    )
    subprocess.run(
        ["git", "-C", str(clone), "config", "user.email", "test@test.com"],
        check=True, capture_output=True,
    )
    subprocess.run(
        ["git", "-C", str(clone), "config", "user.name", "Test"],
        check=True, capture_output=True,
    )
    _make_commit(clone, "scripts/origin_extra.py")
    subprocess.run(
        ["git", "-C", str(clone), "push", "origin", "HEAD:main"],
        check=True, capture_output=True,
    )

    # 로컬은 fetch 안 함 → behind
    rc, stdout, stderr = _run_guard(ws, repo, TASK_ID, base_sha=base_sha)
    assert rc == 1, (
        f"expected FAIL (rc=1, B-2) but got rc={rc}\nstdout={stdout}\nstderr={stderr}"
    )
    combined = stdout + stderr
    assert "B-2" in combined or "behind" in combined.lower() or "ahead" in combined.lower(), (
        f"Expected B-2 / behind / ahead mention in output:\n{combined}"
    )


# ---------------------------------------------------------------------------
# 4. test_forbidden_path_match_fail_b3
# ---------------------------------------------------------------------------

@_skip_if_missing
def test_forbidden_path_match_fail_b3(tmp_path: Path) -> None:
    """head_diff에 forbidden_paths 패턴 매칭 파일 포함 → B-3 FAIL, rc=1."""
    ws, repo = _setup_workspace(
        tmp_path, TASK_ID,
        allowed_paths=["scripts/**", "tests/**"],
        forbidden_paths=["scripts/finish-task.sh", "scripts/auto_merge.py"],
        qc_verdict_json="PASS",
    )

    _make_commit(repo, "scripts/safe.py")
    base_sha = _get_first_sha(repo)
    # forbidden 파일 커밋 → head_diff에 포함
    _make_commit(repo, "scripts/finish-task.sh", content="#!/bin/bash\necho hi\n")

    rc, stdout, stderr = _run_guard(ws, repo, TASK_ID, base_sha=base_sha)
    assert rc == 1, (
        f"expected FAIL (rc=1, B-3) but got rc={rc}\nstdout={stdout}\nstderr={stderr}"
    )
    combined = stdout + stderr
    assert "B-3" in combined or "forbidden" in combined.lower() or "scope" in combined.lower(), (
        f"Expected B-3 / forbidden / scope mention in output:\n{combined}"
    )


# ---------------------------------------------------------------------------
# 5. test_qc_warn_report_overall_pass_fail_b4
# ---------------------------------------------------------------------------

@_skip_if_missing
def test_qc_warn_report_overall_pass_fail_b4(tmp_path: Path) -> None:
    """qc-result=WARN + 보고서 본문에 'OVERALL PASS' → B-4 FAIL, rc=1.

    task-2431 사고 재발 방지 회귀 가드.
    """
    # WARN verdict인데 보고서는 "OVERALL PASS" — 불일치 → FAIL
    report = (
        "---\n"
        "title: Report\n"
        "---\n"
        "# Report\n\n"
        "## QC Verdict\n\nOVERALL PASS\n"
    )
    ws, repo = _setup_workspace(
        tmp_path, TASK_ID,
        allowed_paths=["scripts/**"],
        qc_verdict_json="WARN",
        report_content=report,
    )

    _make_commit(repo, "scripts/a.py")
    base_sha = _get_first_sha(repo)
    _make_commit(repo, "scripts/b.py")

    rc, stdout, stderr = _run_guard(ws, repo, TASK_ID, base_sha=base_sha)
    assert rc == 1, (
        f"expected FAIL (rc=1, B-4) but got rc={rc}\nstdout={stdout}\nstderr={stderr}"
    )
    combined = stdout + stderr
    assert "B-4" in combined or "qc" in combined.lower() or "warn" in combined.lower(), (
        f"Expected B-4 / qc / warn mention in output:\n{combined}"
    )


# ---------------------------------------------------------------------------
# 6. test_normal_push_pass
# ---------------------------------------------------------------------------

@_skip_if_missing
def test_normal_push_pass(tmp_path: Path) -> None:
    """정상 push 가능 케이스: clean tree + scope 내 커밋 + qc=PASS → rc=0."""
    report = (
        "---\n"
        "qc_verdict: PASS\n"
        "---\n"
        "## QC Verdict\n\nPASS\n"
    )
    ws, repo = _setup_workspace(
        tmp_path, TASK_ID,
        allowed_paths=["scripts/**", "tests/**"],
        forbidden_paths=[],
        qc_verdict_json="PASS",
        report_content=report,
    )

    _make_commit(repo, "scripts/util.py")
    base_sha = _get_first_sha(repo)
    _make_commit(repo, "tests/test_util.py")
    # 완전히 clean working tree

    rc, stdout, stderr = _run_guard(ws, repo, TASK_ID, base_sha=base_sha)
    assert rc == 0, (
        f"expected PASS (rc=0) but got rc={rc}\nstdout={stdout}\nstderr={stderr}"
    )


# ---------------------------------------------------------------------------
# 7. test_system_ignore_excluded_b1
# ---------------------------------------------------------------------------

@_skip_if_missing
def test_system_ignore_excluded_b1(tmp_path: Path) -> None:
    """memory/logs/*.log 만 dirty → SYSTEM_IGNORE_PATTERN 적용으로 B-1 무시, rc=0.

    memory/logs/ 는 SYSTEM_IGNORE_PATTERN에 포함되므로 working tree dirty로
    처리하지 않아야 한다.
    """
    report = (
        "---\n"
        "qc_verdict: PASS\n"
        "---\n"
        "## QC Verdict\n\nPASS\n"
    )
    ws, repo = _setup_workspace(
        tmp_path, TASK_ID,
        allowed_paths=["scripts/**"],
        qc_verdict_json="PASS",
        report_content=report,
    )

    _make_commit(repo, "scripts/a.py")
    base_sha = _get_first_sha(repo)
    _make_commit(repo, "scripts/b.py")

    # memory/logs/foo.log — untracked (system-ignore 패턴)
    log_dir = repo / "memory" / "logs"
    log_dir.mkdir(parents=True, exist_ok=True)
    (log_dir / "foo.log").write_text("log content\n")
    # NOTE: git untracked이므로 git status에 나타나지만
    # pre_push_guard는 SYSTEM_IGNORE_PATTERN으로 무시해야 함

    rc, stdout, stderr = _run_guard(ws, repo, TASK_ID, base_sha=base_sha)
    assert rc == 0, (
        f"expected PASS (rc=0, system-ignore log) but got rc={rc}\n"
        f"stdout={stdout}\nstderr={stderr}"
    )
