"""
tests/regression/test_gemini_cli_gate_check_2562.py — task-2562 G4 Pre-PR Gemini CLI gate

8 시나리오 (회장 §명시 박제):
1. OAuth-personal 강제 (GEMINI_API_KEY 거부)
2. scope_violation → 즉시 ESCALATED, fix_loop 진입 0
3. fix_loop_count >= max=2 → ESCALATED
4. Lv.1~2 soft FAIL → action=PR_OPEN_ALLOWED (warning marker)
5. Lv.2 risk-trigger hard (security 민감 파일) → action=PR_OPEN_BLOCKED
6. Lv.3+ hard FAIL → action=PR_OPEN_BLOCKED
7. PASS → action=PR_OPEN_ALLOWED + marker 없음
8. Gemini CLI binary not found → fallback static check
"""

from __future__ import annotations

import sys
from pathlib import Path
from unittest.mock import patch

import pytest

# import path bootstrap
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts"))

import gemini_cli_gate_check as g4  # noqa: E402  type: ignore[import-not-found]


# ---------- helpers ----------


def _write_task_file(tmp_path: Path, level: str, danger: bool = False) -> Path:
    body = f"# task-XXXX\n\n## 레벨\n{level}\n\n## 본문\n"
    if danger:
        body += "이 작업은 인증/결제 관련 보안 처리를 포함한다.\n"
    p = tmp_path / "task.md"
    p.write_text(body, encoding="utf-8")
    return p


def _stub_workspace(tmp_path: Path) -> Path:
    ws = tmp_path / "ws"
    (ws / "memory" / "events").mkdir(parents=True, exist_ok=True)
    return ws


# ---------- scenario 1 ----------


def test_oauth_personal_enforced_rejects_api_key(monkeypatch, tmp_path):
    monkeypatch.setenv("GEMINI_API_KEY", "DUMMY-VALUE-FROM-TEST")
    task_file = _write_task_file(tmp_path, "Lv.3 control-plane")
    with pytest.raises(RuntimeError) as excinfo:
        g4.gemini_cli_gate_check(
            task_file=str(task_file),
            affected_files=[],
            workspace_root=str(_stub_workspace(tmp_path)),
            task_id="task-2562-test-1",
        )
    assert "FORBIDDEN_CAPABILITY_USE" in str(excinfo.value)
    assert "OAuth-personal" in str(excinfo.value)


# ---------- scenario 2 ----------


def test_scope_violation_escalates_with_fix_loop_zero(monkeypatch, tmp_path):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    ws = _stub_workspace(tmp_path)
    task_file = _write_task_file(tmp_path, "Lv.3 control-plane")
    expected = ["a.py", "b.py"]
    affected = ["a.py", "b.py", "c.py"]  # c.py 외부
    res = g4.gemini_cli_gate_check(
        task_file=str(task_file),
        affected_files=affected,
        workspace_root=str(ws),
        task_id="task-2562-test-2",
        expected_files=expected,
    )
    assert res["scope_violation"] is True
    assert "c.py" in res["scope_violation_extras"]
    assert res["action"] == "ESCALATED_OWNER_DECISION"
    assert res["fix_loop_count"] == 0
    # marker 생성
    marker_path = ws / "memory" / "events" / "task-2562-test-2.g4-scope-violation.json"
    assert marker_path.exists()


# ---------- scenario 3 ----------


def test_fix_loop_count_cap_escalates(monkeypatch, tmp_path):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    ws = _stub_workspace(tmp_path)
    task_file = _write_task_file(tmp_path, "Lv.1")
    counter_path = ws / "memory" / "events" / "task-2562-test-3.g4-fix-loop-count"
    counter_path.write_text("2", encoding="utf-8")

    res = g4.gemini_cli_gate_check(
        task_file=str(task_file),
        affected_files=[],
        workspace_root=str(ws),
        task_id="task-2562-test-3",
        expected_files=None,
    )
    assert res["fix_loop_count"] == 2
    assert res["fix_loop_max"] == 2
    assert res["action"] == "ESCALATED_OWNER_DECISION"
    cap_marker = ws / "memory" / "events" / "task-2562-test-3.g4-fix-loop-cap.json"
    assert cap_marker.exists()


# ---------- scenario 4 ----------


def test_lv1_soft_fail_allows_pr_open(monkeypatch, tmp_path):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    ws = _stub_workspace(tmp_path)
    task_file = _write_task_file(tmp_path, "Lv.1 minor")

    fake_parsed = {
        "risks": [{"severity": "critical", "description": "stub critical"}],
        "suggestions": ["fix later"],
    }

    with patch.object(g4, "_run_gemini_cli", return_value=(fake_parsed, None)):
        res = g4.gemini_cli_gate_check(
            task_file=str(task_file),
            affected_files=["unrelated.py"],
            workspace_root=str(ws),
            task_id="task-2562-test-4",
        )
    assert res["gate_mode"] == "soft"
    assert res["pass"] is False
    assert res["action"] == "PR_OPEN_ALLOWED"
    soft_marker = ws / "memory" / "events" / "task-2562-test-4.g4-soft-warning.json"
    assert soft_marker.exists()


# ---------- scenario 5 ----------


def test_lv2_risk_trigger_hard_blocks(monkeypatch, tmp_path):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    ws = _stub_workspace(tmp_path)
    task_file = _write_task_file(tmp_path, "Lv.2", danger=False)

    fake_parsed = {
        "risks": [{"severity": "critical", "description": "auth flow risk"}],
        "suggestions": [],
    }

    affected = ["dispatch/x.py", "auth/y.py"]  # 보안 민감 fragment
    with patch.object(g4, "_run_gemini_cli", return_value=(fake_parsed, None)):
        res = g4.gemini_cli_gate_check(
            task_file=str(task_file),
            affected_files=affected,
            workspace_root=str(ws),
            task_id="task-2562-test-5",
        )
    assert res["gate_mode"] == "hard"
    assert res["gate_trigger"].startswith("lv2_risk_trigger")
    assert res["action"] == "PR_OPEN_BLOCKED"
    fail_marker = ws / "memory" / "events" / "task-2562-test-5.g4-failed"
    assert fail_marker.exists()


# ---------- scenario 6 ----------


def test_lv3plus_hard_blocks(monkeypatch, tmp_path):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    ws = _stub_workspace(tmp_path)
    task_file = _write_task_file(tmp_path, "Lv.3 control-plane")

    fake_parsed = {
        "risks": [{"severity": "critical", "description": "ctrl-plane risk"}],
        "suggestions": [],
    }
    with patch.object(g4, "_run_gemini_cli", return_value=(fake_parsed, None)):
        res = g4.gemini_cli_gate_check(
            task_file=str(task_file),
            affected_files=["script.py"],
            workspace_root=str(ws),
            task_id="task-2562-test-6",
        )
    assert res["level"] == "lv3plus"
    assert res["gate_mode"] == "hard"
    assert res["action"] == "PR_OPEN_BLOCKED"


# ---------- scenario 7 ----------


def test_pass_action_pr_open_allowed_no_marker(monkeypatch, tmp_path):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    ws = _stub_workspace(tmp_path)
    task_file = _write_task_file(tmp_path, "Lv.3")

    fake_parsed = {
        "risks": [{"severity": "low", "description": "style nit"}],
        "suggestions": ["minor"],
    }
    with patch.object(g4, "_run_gemini_cli", return_value=(fake_parsed, None)):
        res = g4.gemini_cli_gate_check(
            task_file=str(task_file),
            affected_files=["a.py"],
            workspace_root=str(ws),
            task_id="task-2562-test-7",
        )
    assert res["pass"] is True
    assert res["action"] == "PR_OPEN_ALLOWED"
    assert res["marker_path"] is None
    # soft/fail/scope marker 없음 어셀션
    ev = ws / "memory" / "events"
    failures = list(ev.glob("task-2562-test-7.g4-*"))
    # fix-loop-count counter만 존재
    failures = [p for p in failures if "fix-loop-count" not in p.name]
    assert failures == []


# ---------- scenario 8 ----------


def test_gemini_binary_not_found_fallback_static(monkeypatch, tmp_path):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    ws = _stub_workspace(tmp_path)
    task_file = _write_task_file(tmp_path, "Lv.1")
    # 누락 파일 → fallback static에서 high risk 감지
    affected = ["does_not_exist.py"]
    with patch.object(g4, "_run_gemini_cli", return_value=(None, "gemini_cli_binary_not_found")):
        res = g4.gemini_cli_gate_check(
            task_file=str(task_file),
            affected_files=affected,
            workspace_root=str(ws),
            task_id="task-2562-test-8",
            target_dir=str(tmp_path / "non-existent-target"),
        )
    assert res["source"] == "gemini_fallback_static"
    assert res["fallback_reason"] == "gemini_cli_binary_not_found"
    assert any(r["severity"] == "high" for r in res["risks"])
    # Lv.1 soft → action=PR_OPEN_ALLOWED (no critical, but high)
    assert res["action"] == "PR_OPEN_ALLOWED"


# ---------- meta ----------


def test_oauth_personal_check_passes_when_no_api_key(monkeypatch):
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    # should not raise
    g4._assert_oauth_personal_only()


def test_cli_help_smoke():
    """CLI --help가 sys.exit하지 않고 정상 종료 (smoke)."""
    with pytest.raises(SystemExit) as excinfo:
        g4.main(["--help"])
    # argparse --help는 SystemExit(0)
    assert excinfo.value.code == 0
