"""
test_request_review.py — cmd_request_review 회귀 테스트.

1. bot author PR + human reviewer → PASS (gh pr edit monkeypatched)
2. human author PR + same reviewer → FAIL (self-approval or non-bot author)
"""
from __future__ import annotations

import argparse
import hashlib
import json
import sys
from pathlib import Path
from typing import Any

import pytest

# worktree 루트를 sys.path에 추가
_WORKTREE_ROOT = str(Path(__file__).resolve().parents[2])
if _WORKTREE_ROOT not in sys.path:
    sys.path.insert(0, _WORKTREE_ROOT)

_SCRIPTS_DIR = str(Path(__file__).resolve().parents[2] / "scripts")
if _SCRIPTS_DIR not in sys.path:
    sys.path.insert(0, _SCRIPTS_DIR)


def _make_state(tmp_path: Path, task_id: str, current_state: str = "PR_OPEN") -> None:
    """테스트용 task state json 생성 (checksum 포함)."""
    state_dir = tmp_path / ".tasks" / "state"
    state_dir.mkdir(parents=True, exist_ok=True)

    state: dict[str, Any] = {
        "task_id": task_id,
        "current_state": current_state,
        "transitions": [],
        "evidence": {
            "git_diff_sha": None,
            "changed_paths": [],
            "branch": None,
            "pr_number": None,
            "pr_state": None,
            "ci_checks": {},
            "guard_sh_result": None,
            "gemini_evidence": None,
            "qc_report": None,
        },
    }

    def _canonical(s: dict) -> str:
        payload = {k: v for k, v in s.items() if k != "_checksum"}
        return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))

    checksum = hashlib.sha256(_canonical(state).encode()).hexdigest()
    state["_checksum"] = checksum

    state_path = state_dir / f"{task_id}.json"
    state_path.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")


def _fake_run_success(cmd: list[str], **kwargs: Any):
    """gh pr edit 성공 mock."""
    del kwargs
    import subprocess
    return subprocess.CompletedProcess(cmd, returncode=0, stdout="", stderr="")


def test_request_review_human_reviewer_passes(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """bot author + human reviewer → PASS. evidence에 result=PASS 기록."""
    task_id = "task-2481"
    monkeypatch.setenv("WORKSPACE_ROOT", str(tmp_path))
    monkeypatch.setenv("TASKCTL_TEST_MODE", "1")
    monkeypatch.setenv("TASKCTL_PR_AUTHOR_OVERRIDE", "jeon-jonghyuk-taskctl-bot[bot]")

    # allowed_approvers.json 생성 (회장/아누 사람 계정 allowlist)
    allowed_dir = tmp_path / "memory" / "specs"
    allowed_dir.mkdir(parents=True, exist_ok=True)
    (allowed_dir / "allowed_approvers.json").write_text(json.dumps({
        "version": 1,
        "approvers": [],
        "manual_logins": ["jonghyuk-jeon", "JonghyukJeon", "Jeon-Jonghyuk"],
    }), encoding="utf-8")
    # bot_pr_author 모듈 재로드 강제 (WORKSPACE_ROOT 반영)
    monkeypatch.delitem(sys.modules, "utils.bot_pr_author", raising=False)

    _make_state(tmp_path, task_id, current_state="PR_OPEN")

    import importlib.util

    taskctl_path = Path(_WORKTREE_ROOT) / "scripts" / "taskctl.py"
    spec = importlib.util.spec_from_file_location("_taskctl_scripts", str(taskctl_path))
    tc = importlib.util.module_from_spec(spec)  # type: ignore[arg-type]
    spec.loader.exec_module(tc)  # type: ignore[union-attr]

    monkeypatch.setattr(tc, "WORKSPACE", tmp_path)
    monkeypatch.setattr(tc, "STATE_DIR", tmp_path / ".tasks" / "state")
    monkeypatch.setattr(tc, "EVIDENCE_DIR", tmp_path / ".tasks" / "evidence")

    # gh pr edit 성공으로 monkeypatch
    monkeypatch.setattr(tc, "_run", _fake_run_success)

    args = argparse.Namespace(
        task_id=task_id,
        pr=100,
        reviewer="jonghyuk-jeon",
    )

    result = tc.cmd_request_review(args)
    assert result == 0, f"expected 0 exit code, got {result}"

    # evidence에 result=PASS 확인
    evidence_dir = tmp_path / ".tasks" / "evidence" / task_id
    ev_files = sorted(evidence_dir.glob("*.json"))
    assert ev_files, "evidence file not created"

    ev_data = json.loads(ev_files[-1].read_text())
    assert ev_data.get("result") == "PASS", f"expected result=PASS, got: {ev_data}"
    assert ev_data.get("author") == "jeon-jonghyuk-taskctl-bot[bot]" or \
           "jeon-jonghyuk-taskctl-bot" in str(ev_data.get("author", "")), \
           f"bot author not in evidence: {ev_data}"
    assert ev_data.get("reviewer") == "jonghyuk-jeon", (
        f"reviewer mismatch: {ev_data.get('reviewer')}"
    )


def test_request_review_self_approval_rejected(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """human author + same reviewer (self-approval) → SystemExit, evidence에 result=FAIL."""
    task_id = "task-2481"
    monkeypatch.setenv("WORKSPACE_ROOT", str(tmp_path))
    monkeypatch.setenv("TASKCTL_TEST_MODE", "1")
    monkeypatch.setenv("TASKCTL_PR_AUTHOR_OVERRIDE", "human-user")

    _make_state(tmp_path, task_id, current_state="PR_OPEN")

    import importlib.util

    taskctl_path = Path(_WORKTREE_ROOT) / "scripts" / "taskctl.py"
    spec = importlib.util.spec_from_file_location("_taskctl_scripts", str(taskctl_path))
    tc = importlib.util.module_from_spec(spec)  # type: ignore[arg-type]
    spec.loader.exec_module(tc)  # type: ignore[union-attr]

    monkeypatch.setattr(tc, "WORKSPACE", tmp_path)
    monkeypatch.setattr(tc, "STATE_DIR", tmp_path / ".tasks" / "state")
    monkeypatch.setattr(tc, "EVIDENCE_DIR", tmp_path / ".tasks" / "evidence")
    monkeypatch.setattr(tc, "_run", _fake_run_success)

    args = argparse.Namespace(
        task_id=task_id,
        pr=100,
        reviewer="human-user",  # self-approval (동일 human author)
    )

    with pytest.raises(SystemExit) as exc_info:
        tc.cmd_request_review(args)

    assert exc_info.value.code != 0, (
        f"expected non-zero exit for self-approval/non-bot author, got {exc_info.value.code}"
    )

    # evidence에 result=FAIL 확인
    evidence_dir = tmp_path / ".tasks" / "evidence" / task_id
    ev_files = sorted(evidence_dir.glob("*.json"))
    assert ev_files, "evidence file not created even on failure"

    ev_data = json.loads(ev_files[-1].read_text())
    assert ev_data.get("result") == "FAIL", (
        f"expected result=FAIL in evidence, got: {ev_data}"
    )
    assert ev_data.get("reason"), f"reason field missing in evidence: {ev_data}"


def test_request_review_unauthorized_human_reviewer_rejected(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """allowed_approvers.json이 정의된 환경에서 비허용 사람 reviewer → reject."""
    task_id = "task-2481"
    monkeypatch.setenv("WORKSPACE_ROOT", str(tmp_path))
    monkeypatch.setenv("TASKCTL_TEST_MODE", "1")
    monkeypatch.setenv("TASKCTL_PR_AUTHOR_OVERRIDE", "jeon-jonghyuk-taskctl-bot[bot]")

    # 임시 allowed_approvers.json 생성 (회장/아누만 허용)
    allowed_dir = tmp_path / "memory" / "specs"
    allowed_dir.mkdir(parents=True, exist_ok=True)
    (allowed_dir / "allowed_approvers.json").write_text(json.dumps({
        "version": 1,
        "approvers": [],
        "manual_logins": ["JonghyukJeon", "Jeon-Jonghyuk"],
    }), encoding="utf-8")

    _make_state(tmp_path, task_id, current_state="PR_OPEN")

    import importlib.util

    taskctl_path = Path(_WORKTREE_ROOT) / "scripts" / "taskctl.py"
    spec = importlib.util.spec_from_file_location("_taskctl_scripts2", str(taskctl_path))
    tc = importlib.util.module_from_spec(spec)  # type: ignore[arg-type]
    spec.loader.exec_module(tc)  # type: ignore[union-attr]

    monkeypatch.setattr(tc, "WORKSPACE", tmp_path)
    monkeypatch.setattr(tc, "STATE_DIR", tmp_path / ".tasks" / "state")
    monkeypatch.setattr(tc, "EVIDENCE_DIR", tmp_path / ".tasks" / "evidence")
    monkeypatch.setattr(tc, "_run", _fake_run_success)

    # bot_pr_author 모듈 재로드 — 새 WORKSPACE_ROOT 반영
    monkeypatch.delitem(sys.modules, "utils.bot_pr_author", raising=False)

    args = argparse.Namespace(
        task_id=task_id,
        pr=100,
        reviewer="random-stranger",  # allowed_approvers.json에 없는 인물
    )

    with pytest.raises(SystemExit) as exc_info:
        tc.cmd_request_review(args)

    assert exc_info.value.code != 0, (
        f"expected non-zero exit for unauthorized reviewer, got {exc_info.value.code}"
    )

    evidence_dir = tmp_path / ".tasks" / "evidence" / task_id
    ev_files = sorted(evidence_dir.glob("*.json"))
    assert ev_files, "evidence file not created"

    ev_data = json.loads(ev_files[-1].read_text())
    assert ev_data.get("result") == "FAIL", (
        f"expected result=FAIL in evidence, got: {ev_data}"
    )
    assert "allowed_approvers" in str(ev_data.get("reason", "")) or \
           "manual_logins" in str(ev_data.get("reason", "")), (
        f"reason should mention allowlist: {ev_data}"
    )
