"""
test_codex_review_loop_decider_2711.py
task-2711 QA — ANU_CODEX_REVIEW_LOOP_DECIDER 테스트
작성자: 아르고스 (dev1팀 QA 엔지니어)

14개 시나리오 + 2개 메타 테스트 = 총 16개 함수
"""
import sys
import os
import json

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts", "anu"))
import codex_review_loop_decider as crld

# ---------------------------------------------------------------------------
# Fixture 경로
# ---------------------------------------------------------------------------
FIXDIR = os.path.join(os.path.dirname(__file__), "fixtures", "codex_review_2711")


def _load_fixture(filename: str) -> crld.DecisionResult:
    """JSON fixture 파일을 읽어 decide() 결과를 반환."""
    path = os.path.join(FIXDIR, filename)
    with open(path, "r", encoding="utf-8") as f:
        data = json.load(f)
    inp = crld.from_dict(data)
    return crld.decide(inp)


# ---------------------------------------------------------------------------
# T-1 ~ T-7b : 디스크 fixture 기반
# ---------------------------------------------------------------------------

def test_t1_round1_needs_revision():
    """Round 1: 전체 NR → AUTO_REVISION_CONTINUE"""
    result = _load_fixture("task-2710_round1.json")
    assert result.decision == "AUTO_REVISION_CONTINUE", (
        f"Expected AUTO_REVISION_CONTINUE, got {result.decision}"
    )


def test_t2_round6_pwr_pilot_not_ready():
    """Round 6: PASS_WITH_RECOMMENDATIONS + pilot NOT_READY_WITHOUT_FOLLOWUP → AUTO_REVISION_CONTINUE"""
    result = _load_fixture("task-2710_round6.json")
    assert result.decision == "AUTO_REVISION_CONTINUE", (
        f"Expected AUTO_REVISION_CONTINUE, got {result.decision}"
    )


def test_t3a_round7_chair_minor_false():
    """Round 7: pilot READY_WITH_RECOMMENDATIONS, chair_minor=False → PILOT_READY_BUT_NEEDS_CHAIR"""
    result = _load_fixture("task-2710_round7_chair_minor_false.json")
    assert result.decision == "PILOT_READY_BUT_NEEDS_CHAIR", (
        f"Expected PILOT_READY_BUT_NEEDS_CHAIR, got {result.decision}"
    )


def test_t3b_round7_chair_minor_true():
    """Round 7: pilot READY_WITH_RECOMMENDATIONS, chair_minor=True → AUTO_REVISION_CONTINUE"""
    result = _load_fixture("task-2710_round7_chair_minor_true.json")
    assert result.decision == "AUTO_REVISION_CONTINUE", (
        f"Expected AUTO_REVISION_CONTINUE, got {result.decision}"
    )


def test_t4_v8_lock_ready():
    """v8: PASS_WITH_RECOMMENDATIONS, remaining=[], locked=False → LOCK_READY"""
    result = _load_fixture("task-2710_v8_lock_ready.json")
    assert result.decision == "LOCK_READY", (
        f"Expected LOCK_READY, got {result.decision}"
    )


def test_t5_critical_evidence_reclassify():
    """evidence reclassify: trigger6 발동 → CRITICAL_ESCALATION, 6 in risk_triggers_matched"""
    result = _load_fixture("critical_escalation_evidence_reclassify.json")
    assert result.decision == "CRITICAL_ESCALATION", (
        f"Expected CRITICAL_ESCALATION, got {result.decision}"
    )
    assert 6 in result.risk_triggers_matched, (
        f"Expected trigger 6 in risk_triggers_matched, got {result.risk_triggers_matched}"
    )


def test_t7a_same_blocker_3_rounds():
    """동일 blocker 3회 반복 §5.4.1 → CHAIR_DECISION_REQUIRED"""
    result = _load_fixture("loop_boundary_same_blocker_3.json")
    assert result.decision == "CHAIR_DECISION_REQUIRED", (
        f"Expected CHAIR_DECISION_REQUIRED, got {result.decision}"
    )


def test_t7b_fail_2_same_axis():
    """FAIL 2회 같은 axis §5.4.2 → CHAIR_DECISION_REQUIRED"""
    result = _load_fixture("loop_boundary_fail_2_same_axis.json")
    assert result.decision == "CHAIR_DECISION_REQUIRED", (
        f"Expected CHAIR_DECISION_REQUIRED, got {result.decision}"
    )


# ---------------------------------------------------------------------------
# T-6, T-8 ~ T-14 : inline dict 기반
# ---------------------------------------------------------------------------

def test_t6_lock_ready_then_pilot():
    """locked_status=True, pilot READY, remaining=[] → LOCK_READY 조건 미충족 → PILOT_READY_BUT_NEEDS_CHAIR"""
    d = {
        "task_id": "task-2710",
        "version": 8,
        "round_number": 7,
        "overall_verdict": "PASS_WITH_RECOMMENDATIONS",
        "pilot_readiness": "READY",
        "axis_counts": {"pass": 3, "pwr": 2, "nr": 0, "fail": 0},
        "remaining_recommendations": [],
        "locked_status": True,           # LOCK_READY Step3 조건 불충족 (locked_status False 필요)
        "chair_authorization_id": "CHAIR-AUTH-T6-PLACEHOLDER",
        "chair_minor_doc_cleanup_authorized": False,
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "PILOT_READY_BUT_NEEDS_CHAIR", (
        f"Expected PILOT_READY_BUT_NEEDS_CHAIR, got {result.decision}"
    )


def test_t8_critical7_keyword():
    """remaining에 'Critical 7 doctrine 위반 — CHAIR_REQUIRED' → trigger1 발동 → CRITICAL_ESCALATION"""
    d = {
        "task_id": "task-2799",
        "version": 1,
        "round_number": 2,
        "overall_verdict": "NEEDS_REVISION",
        "pilot_readiness": "NOT_READY_WITHOUT_FOLLOWUP",
        "axis_counts": {"pass": 1, "pwr": 0, "nr": 3, "fail": 1},
        "remaining_recommendations": [
            "Critical 7 doctrine 위반 — CHAIR_REQUIRED",
        ],
        "locked_status": False,
        "chair_authorization_id": "CHAIR-AUTH-T8-PLACEHOLDER",
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "CRITICAL_ESCALATION", (
        f"Expected CRITICAL_ESCALATION, got {result.decision}"
    )
    assert 1 in result.risk_triggers_matched, (
        f"Expected trigger 1 in risk_triggers_matched, got {result.risk_triggers_matched}"
    )


def test_t9_forbidden_path_change():
    """remaining에 §11.3 forbidden target → trigger3만 발동 → CHAIR_DECISION_REQUIRED"""
    d = {
        "task_id": "task-2799",
        "version": 1,
        "round_number": 2,
        "overall_verdict": "NEEDS_REVISION",
        "pilot_readiness": "NOT_READY_WITHOUT_FOLLOWUP",
        "axis_counts": {"pass": 1, "pwr": 0, "nr": 2, "fail": 1},
        "remaining_recommendations": [
            "§11.3 forbidden target 에 새 path 추가 필요",
        ],
        "locked_status": False,
        "chair_authorization_id": "CHAIR-AUTH-T9-PLACEHOLDER",
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "CHAIR_DECISION_REQUIRED", (
        f"Expected CHAIR_DECISION_REQUIRED, got {result.decision}"
    )
    assert 3 in result.risk_triggers_matched, (
        f"Expected trigger 3 in risk_triggers_matched, got {result.risk_triggers_matched}"
    )


def test_t10_actual_dispatch_request():
    """remaining에 dispatch.py + bot_settings.json → trigger4 발동 → CRITICAL_ESCALATION"""
    d = {
        "task_id": "task-2799",
        "version": 1,
        "round_number": 2,
        "overall_verdict": "NEEDS_REVISION",
        "pilot_readiness": "NOT_READY_WITHOUT_FOLLOWUP",
        "axis_counts": {"pass": 1, "pwr": 0, "nr": 2, "fail": 1},
        "remaining_recommendations": [
            "dispatch.py 변경 필요",
            "bot_settings.json 변경",
        ],
        "locked_status": False,
        "chair_authorization_id": "CHAIR-AUTH-T10-PLACEHOLDER",
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "CRITICAL_ESCALATION", (
        f"Expected CRITICAL_ESCALATION, got {result.decision}"
    )
    assert 4 in result.risk_triggers_matched, (
        f"Expected trigger 4 in risk_triggers_matched, got {result.risk_triggers_matched}"
    )


def test_t11_permission_expansion():
    """remaining에 'new allowed_path 추가 권한 확대 요청' → trigger2 발동 → CHAIR_DECISION_REQUIRED"""
    d = {
        "task_id": "task-2799",
        "version": 1,
        "round_number": 2,
        "overall_verdict": "NEEDS_REVISION",
        "pilot_readiness": "NOT_READY_WITHOUT_FOLLOWUP",
        "axis_counts": {"pass": 1, "pwr": 0, "nr": 2, "fail": 1},
        "remaining_recommendations": [
            "new allowed_path 추가 권한 확대 요청",
        ],
        "locked_status": False,
        "chair_authorization_id": "CHAIR-AUTH-T11-PLACEHOLDER",
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "CHAIR_DECISION_REQUIRED", (
        f"Expected CHAIR_DECISION_REQUIRED, got {result.decision}"
    )
    assert 2 in result.risk_triggers_matched, (
        f"Expected trigger 2 in risk_triggers_matched, got {result.risk_triggers_matched}"
    )


def test_t12_revision_includes_pr_push_merge():
    """remaining에 'PR 생성 + git push + merge' → trigger4 발동 → CRITICAL_ESCALATION"""
    d = {
        "task_id": "task-2799",
        "version": 1,
        "round_number": 2,
        "overall_verdict": "NEEDS_REVISION",
        "pilot_readiness": "NOT_READY_WITHOUT_FOLLOWUP",
        "axis_counts": {"pass": 1, "pwr": 0, "nr": 2, "fail": 1},
        "remaining_recommendations": [
            "revision 산출물에 PR 생성 + git push + merge 포함",
        ],
        "locked_status": False,
        "chair_authorization_id": "CHAIR-AUTH-T12-PLACEHOLDER",
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "CRITICAL_ESCALATION", (
        f"Expected CRITICAL_ESCALATION, got {result.decision}"
    )
    assert 4 in result.risk_triggers_matched, (
        f"Expected trigger 4 in risk_triggers_matched, got {result.risk_triggers_matched}"
    )


def test_t13_immutable_scope_edit():
    """remaining에 'finish-task.sh immutable scope 수정' → trigger5 발동 → CRITICAL_ESCALATION"""
    d = {
        "task_id": "task-2799",
        "version": 1,
        "round_number": 2,
        "overall_verdict": "NEEDS_REVISION",
        "pilot_readiness": "NOT_READY_WITHOUT_FOLLOWUP",
        "axis_counts": {"pass": 1, "pwr": 0, "nr": 2, "fail": 1},
        "remaining_recommendations": [
            "allowed_existing_file_edits 가 finish-task.sh 를 immutable scope 에서 수정",
        ],
        "locked_status": False,
        "chair_authorization_id": "CHAIR-AUTH-T13-PLACEHOLDER",
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "CRITICAL_ESCALATION", (
        f"Expected CRITICAL_ESCALATION, got {result.decision}"
    )
    assert 5 in result.risk_triggers_matched, (
        f"Expected trigger 5 in risk_triggers_matched, got {result.risk_triggers_matched}"
    )


def test_t14_max_7_round():
    """round_number=8 > 7 boundary → risk trigger 없이 CHAIR_DECISION_REQUIRED"""
    d = {
        "task_id": "task-2799",
        "version": 1,
        "round_number": 8,
        "overall_verdict": "NEEDS_REVISION",
        "pilot_readiness": "NOT_READY",
        "axis_counts": {"pass": 1, "pwr": 0, "nr": 2, "fail": 1},
        "remaining_recommendations": [
            "minor doc 정리",
        ],
        "locked_status": False,
        "chair_authorization_id": "CHAIR-AUTH-T14-PLACEHOLDER",
        "prior_rounds_history": None,
    }
    result = crld.decide(crld.from_dict(d))
    assert result.decision == "CHAIR_DECISION_REQUIRED", (
        f"Expected CHAIR_DECISION_REQUIRED (max round), got {result.decision}"
    )


# ---------------------------------------------------------------------------
# 메타 테스트 2개
# ---------------------------------------------------------------------------

def test_total_14_scenarios_documented():
    """14개 시나리오 함수 이름이 모두 이 모듈에 존재하는지 sanity 확인."""
    this_module = sys.modules[__name__]

    expected_tests = [
        "test_t1_round1_needs_revision",
        "test_t2_round6_pwr_pilot_not_ready",
        "test_t3a_round7_chair_minor_false",
        "test_t3b_round7_chair_minor_true",
        "test_t4_v8_lock_ready",
        "test_t5_critical_evidence_reclassify",
        "test_t6_lock_ready_then_pilot",
        "test_t7a_same_blocker_3_rounds",
        "test_t7b_fail_2_same_axis",
        "test_t8_critical7_keyword",
        "test_t9_forbidden_path_change",
        "test_t10_actual_dispatch_request",
        "test_t11_permission_expansion",
        "test_t12_revision_includes_pr_push_merge",
        # T-13
        "test_t13_immutable_scope_edit",
        # T-14
        "test_t14_max_7_round",
    ]
    # T-6 is already in the list above (test_t6_lock_ready_then_pilot)
    for name in expected_tests:
        assert hasattr(this_module, name), f"Missing test function: {name}"


def test_decision_result_schema_fields():
    """DecisionResult 6개 필드 모두 존재 + decision이 5 enum 중 하나임을 검증."""
    VALID_DECISIONS = {
        "AUTO_REVISION_CONTINUE",
        "CHAIR_DECISION_REQUIRED",
        "LOCK_READY",
        "PILOT_READY_BUT_NEEDS_CHAIR",
        "CRITICAL_ESCALATION",
    }
    result = _load_fixture("task-2710_round1.json")

    # 6개 필드 존재 확인
    assert hasattr(result, "decision"), "Missing field: decision"
    assert hasattr(result, "rationale"), "Missing field: rationale"
    assert hasattr(result, "next_action"), "Missing field: next_action"
    assert hasattr(result, "risk_triggers_matched"), "Missing field: risk_triggers_matched"
    assert hasattr(result, "chair_facing_summary"), "Missing field: chair_facing_summary"
    assert hasattr(result, "audit_marker_path"), "Missing field: audit_marker_path"

    # decision이 5 enum 중 하나
    assert result.decision in VALID_DECISIONS, (
        f"decision '{result.decision}' is not in valid set {VALID_DECISIONS}"
    )
