# -*- coding: utf-8 -*-
"""task-2673 RS-2670-A — LOOP_BOUNDARY elapsed-only 우선순위 보정.

수정 목표 5: elapsed >= max_watch 도달 시 unresolved/BLOCKED 잔재가 있으면
HOLD_FOR_CHAIR 로 격상; 잔재 없으면 LOOP_BOUNDARY 정상.
"""
from __future__ import annotations

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[2]))

import pytest  # noqa: E402

from utils.pr_watcher_terminal_state_classifier import (  # noqa: E402
    HOLD_FOR_CHAIR,
    LOOP_BOUNDARY,
    PRSnapshot,
    classify,
)


def test_loop_boundary_with_unresolved_escalates_to_hold(
    poll_12_pr_data, poll_12_th_data, expected_head, stale_gemini_review
):
    """RS-2670-A — poll #30 박제 시점 unresolved=3, BLOCKED 잔재 → HOLD.

    fresh-evidence HOLD 분기가 LOOP_BOUNDARY 보다 먼저 발화하므로,
    본 케이스는 gemini stale 시나리오로 LOOP_BOUNDARY-residual 분기 발화 확인.
    """
    pr = dict(poll_12_pr_data)
    pr["reviews"] = [stale_gemini_review]
    snap = PRSnapshot.from_gh(pr, poll_12_th_data)
    state, reason = classify(
        snap,
        elapsed_watcher_sec=3600,
        expected_head=expected_head,
        max_watch_seconds=60 * 60,
    )
    assert state == HOLD_FOR_CHAIR
    assert "loop_boundary_with_residual" in reason
    assert "unresolved=3" in reason
    assert "mss=BLOCKED" in reason


def test_fresh_evidence_priority_over_loop_boundary(
    poll_12_pr_data, poll_12_th_data, expected_head
):
    """fresh-evidence HOLD 분기는 LOOP_BOUNDARY 도달 시점에도 먼저 발화한다.

    이는 사고 박제 (dev7 watcher poll #12 silent fall-through) 의 결정적
    재발 방지 — fresh evidence 가 있으면 max_watch 까지 굳이 기다리지 않고
    즉시 HOLD_FOR_CHAIR 로 격상한다.
    """
    snap = PRSnapshot.from_gh(poll_12_pr_data, poll_12_th_data)
    state, reason = classify(
        snap,
        elapsed_watcher_sec=3600,
        expected_head=expected_head,
        max_watch_seconds=60 * 60,
    )
    assert state == HOLD_FOR_CHAIR
    assert "fresh_gemini_head_match" in reason


def test_loop_boundary_clean_no_residual_returns_loop_boundary(
    expected_head, empty_th_data
):
    """잔재 없는 max_watch 도달은 정상 LOOP_BOUNDARY (회장 보고는 별 의미 없음)."""
    pr = {
        "headRefOid": expected_head,
        "mergeStateStatus": "BEHIND",  # CLEAN 아님 + BLOCKED 아님
        "reviewDecision": "",
        "statusCheckRollup": [
            {"name": "cancel-kill-switch", "conclusion": "SUCCESS"},
        ],
        "reviews": [],  # gemini stale 분기 미발동 (head_committed 미주입)
    }
    snap = PRSnapshot.from_gh(pr, empty_th_data)
    state, reason = classify(
        snap,
        elapsed_watcher_sec=3700,
        expected_head=expected_head,
        max_watch_seconds=60 * 60,
    )
    assert state == LOOP_BOUNDARY
    assert "no residual" in reason


def test_loop_boundary_blocked_only_escalates(expected_head, empty_th_data):
    """unresolved=0 이지만 BLOCKED 잔재만 있어도 HOLD 로 격상."""
    pr = {
        "headRefOid": expected_head,
        "mergeStateStatus": "BLOCKED",
        "reviewDecision": "",
        "statusCheckRollup": [
            {"name": "cancel-kill-switch", "conclusion": "SUCCESS"},
        ],
        "reviews": [],
    }
    snap = PRSnapshot.from_gh(pr, empty_th_data)
    state, _ = classify(
        snap,
        elapsed_watcher_sec=3600,
        expected_head=expected_head,
    )
    assert state == HOLD_FOR_CHAIR


def test_below_max_watch_no_terminal(expected_head, empty_th_data):
    """elapsed < max_watch 이면 LOOP_BOUNDARY 분기 자체가 발화 안 함."""
    pr = {
        "headRefOid": expected_head,
        "mergeStateStatus": "BEHIND",
        "reviewDecision": "",
        "statusCheckRollup": [],
        "reviews": [],
    }
    snap = PRSnapshot.from_gh(pr, empty_th_data)
    state, reason = classify(
        snap,
        elapsed_watcher_sec=120,
        expected_head=expected_head,
    )
    assert state == ""
    assert reason == "continue"
