"""task-2556 §3 — 30min timeout 후 FIRST_GEMINI_TRIGGER_MISSING 자동 trigger 회귀.

회장 §명시 2026-05-12 §3:
  PR createdAt + 30min 경과 + Gemini reviews 0건 = FIRST_GEMINI_TRIGGER_MISSING.

검증 포인트:
  1. 정확 grace period (30분) 경계 검증 (within / boundary / past).
  2. boundary 직전 (29:59) → WITHIN_GRACE_PERIOD, scheduler 는 SKIP.
  3. 30:00 정각 + reviews 0 → FIRST_GEMINI_TRIGGER_MISSING.
  4. 1h 경과 + reviews 0 → FIRST_GEMINI_TRIGGER_MISSING + scheduler OWNER_TRIGGER_DISPATCHED.
  5. 30:01 경과 + 1 review 도착 (head 가 review commit_id 와 일치) → GEMINI_FRESH_ON_HEAD → SKIP.
"""

from __future__ import annotations

import sys
from pathlib import Path


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

from anu_v2.executor_scheduler import (  # noqa: E402
    ACTION_OWNER_TRIGGER_DISPATCHED,
    ACTION_WITHIN_GRACE,
    ACTION_FRESH_RESUME,
    ExecutorScheduler,
)
from anu_v2.idle_pr_diagnoser import (  # noqa: E402
    GeminiReviewMeta,
    GRACE_PERIOD_SECONDS,
    IdlePRDiagnoser,
    IdlePRSnapshot,
    STATE_FIRST_GEMINI_TRIGGER_MISSING,
    STATE_GEMINI_FRESH_ON_HEAD,
    STATE_WITHIN_GRACE_PERIOD,
)
from anu_v2.merge_queue_executor import MergeQueueExecutor  # noqa: E402
from anu_v2.owner_trigger_audit import OwnerTriggerAudit  # noqa: E402
from anu_v2.owner_trigger_only import OwnerTriggerOnly  # noqa: E402


_HEAD = "a" * 40


def _snapshot(
    *,
    number: int = 200,
    head_sha: str = _HEAD,
    created_at: str = "2026-05-12T10:00:00+00:00",
    reviews: tuple[GeminiReviewMeta, ...] = (),
    ci_pass: bool = True,
) -> IdlePRSnapshot:
    return IdlePRSnapshot(
        number=number,
        head_sha=head_sha,
        head_ref="task/task-2556-dev5",
        created_at=created_at,
        gemini_reviews=reviews,
        ci_required_all_success=ci_pass,
    )


def _make_runner(tmp_path: Path, *, http_calls: list) -> OwnerTriggerOnly:
    def http_post(method, path, body, headers):
        http_calls.append({"method": method, "path": path, "body": body})
        return {"id": 1}

    return OwnerTriggerOnly(
        workspace_root=tmp_path,
        http_post=http_post,
        token_provider=lambda: "ghp_owner_fake_xxxxxxxxxxxxxxxxxxxx",
        audit=OwnerTriggerAudit(tmp_path),
    )


def _make_scheduler(
    tmp_path: Path,
    snapshots: list[IdlePRSnapshot],
    *,
    http_calls: list | None = None,
) -> ExecutorScheduler:
    if http_calls is None:
        http_calls = []
    return ExecutorScheduler(
        workspace_root=tmp_path,
        decision_dir=tmp_path / "memory" / "events",
        snapshot_provider=lambda: snapshots,
        owner_trigger=_make_runner(tmp_path, http_calls=http_calls),
        merge_executor=MergeQueueExecutor(
            gh_runner=lambda a, e: {},
            git_runner=lambda a: "",
            pytest_runner=lambda p: 0,
            audit_writer=lambda p: None,
            task_md_root=tmp_path,
        ),
        owner="o",
        repo="r",
    )


# ─── grace period boundary tests ──────────────────────────────────────────


def test_within_grace_29_59_classified_within_grace():
    diag = IdlePRDiagnoser().diagnose(
        _snapshot(created_at="2026-05-12T10:00:00+00:00"),
        now="2026-05-12T10:29:59+00:00",
    )
    assert diag.state == STATE_WITHIN_GRACE_PERIOD
    assert diag.elapsed_since_created_seconds == 29 * 60 + 59
    assert diag.requires_owner_trigger is False


def test_boundary_30_00_exact_classified_first_missing():
    """30:00 정각, reviews 0 → FIRST_GEMINI_TRIGGER_MISSING."""
    diag = IdlePRDiagnoser().diagnose(
        _snapshot(created_at="2026-05-12T10:00:00+00:00"),
        now="2026-05-12T10:30:00+00:00",
    )
    assert diag.state == STATE_FIRST_GEMINI_TRIGGER_MISSING
    assert diag.elapsed_since_created_seconds == GRACE_PERIOD_SECONDS
    assert diag.requires_owner_trigger is True
    assert diag.latest_gemini_commit_id is None


def test_past_1h_no_reviews_dispatches_owner_trigger(tmp_path):
    http_calls: list = []
    snap = _snapshot(created_at="2026-05-12T10:00:00+00:00", reviews=())
    scheduler = _make_scheduler(tmp_path, [snap], http_calls=http_calls)
    result = scheduler.run_one_cycle(
        env={"OWNER_GEMINI_TRIGGER_TOKEN": "ghp_xxxxxxxxxxxxxxxxxxxxxxxx"},
        now="2026-05-12T11:00:00+00:00",
    )
    assert len(result.pr_actions) == 1
    assert result.pr_actions[0].state == STATE_FIRST_GEMINI_TRIGGER_MISSING
    assert result.pr_actions[0].action == ACTION_OWNER_TRIGGER_DISPATCHED
    assert len(http_calls) == 1
    assert http_calls[0]["body"] == {"body": "/gemini review"}


def test_within_grace_scheduler_skips_no_http_call(tmp_path):
    http_calls: list = []
    snap = _snapshot(created_at="2026-05-12T10:00:00+00:00")
    scheduler = _make_scheduler(tmp_path, [snap], http_calls=http_calls)
    result = scheduler.run_one_cycle(
        env={"OWNER_GEMINI_TRIGGER_TOKEN": "ghp_xxxxxxxxxxxxxxxxxxxxxxxx"},
        now="2026-05-12T10:15:00+00:00",
    )
    assert result.pr_actions[0].action == ACTION_WITHIN_GRACE
    assert len(http_calls) == 0


def test_fresh_review_after_grace_period_classifies_fresh(tmp_path):
    """30min 경과 + Gemini review (commit_id == head_sha) → GEMINI_FRESH_ON_HEAD."""
    http_calls: list = []
    review = GeminiReviewMeta(commit_id=_HEAD, submitted_at="2026-05-12T10:35:00+00:00")
    snap = _snapshot(created_at="2026-05-12T10:00:00+00:00", reviews=(review,))
    scheduler = _make_scheduler(tmp_path, [snap], http_calls=http_calls)
    result = scheduler.run_one_cycle(
        env={"OWNER_GEMINI_TRIGGER_TOKEN": "ghp_xxxxxxxxxxxxxxxxxxxxxxxx"},
        now="2026-05-12T10:36:00+00:00",
    )
    assert result.pr_actions[0].state == STATE_GEMINI_FRESH_ON_HEAD
    assert result.pr_actions[0].action == ACTION_FRESH_RESUME
    assert len(http_calls) == 0


def test_grace_period_constant_is_30_minutes():
    """회장 § 명시 30min 1:1."""
    assert GRACE_PERIOD_SECONDS == 30 * 60
