# -*- coding: utf-8 -*-
"""utils.callback_adjudicator — terminal_state / policy / Critical7 판정기.

task-2644 ANU_CALLBACK_COLLECTOR_CONTROL_PLANE (회장 verbatim 우선순위 2).
spec: memory/specs/system_anu_callback_collector_control_plane_spec_260524.md
spec sha256: b27da557d4245bce476cd63f4ab174aefc8a25d2da07ec2c8d2c83b01ee96153

회장 verbatim flow (spec §1.2):
    callback adjudication (terminal_state / policy / Critical7 판정)
        → next_action decision (utils.callback_next_action_runner)

본 모듈은 next_action 결정 직전 단계로, envelope 의 단일 callback 을 받아
adjudication dict 를 생성한다. next_action runner 가 이 dict + .anu_state +
batch_state 를 결합하여 11 enum 중 하나를 결정한다.

ANCHOR-3/4/9/11/14/15 박제 준수.
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional


SCHEMA = "utils.callback_adjudicator.v1"


# Critical7 (회장 verbatim 6 + Critical7 종합)
CRITICAL7_FIELDS = (
    "critical7_hit",
    "critical_block",
    "blocking_secret_detected",
    "admin_override_required",
    "permission_expansion",
    "credential_expansion",
    "forbidden_target_modified",
)

# Critical7 보고 트리거 (회장 verbatim 6)
CHAIR_REPORT_TRIGGERS = (
    "critical7_hit",
    "credential_expansion",
    "permission_expansion",
    "live_settings_modification_attempt",
    "github_destructive_write",
    "forbidden_target_modified",
)


def _truthy(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        return value != 0
    if isinstance(value, str):
        return value.strip().lower() in {"true", "1", "yes", "hit", "detected"}
    return bool(value)


def detect_critical7(envelope: Dict[str, Any]) -> bool:
    """Critical7 hit 여부."""
    if any(_truthy(envelope.get(f)) for f in CRITICAL7_FIELDS):
        return True
    flags = envelope.get("flags") or {}
    if isinstance(flags, dict) and any(_truthy(flags.get(f)) for f in CRITICAL7_FIELDS):
        return True
    severity = (envelope.get("severity") or "").upper()
    if severity in {"CRITICAL7", "CRITICAL", "CRITICAL_BLOCK"}:
        return True
    return False


def detect_chair_report_required(envelope: Dict[str, Any]) -> Optional[str]:
    """회장 verbatim 6 트리거 중 하나라도 hit 면 사유 문자열 반환."""
    for trig in CHAIR_REPORT_TRIGGERS:
        if _truthy(envelope.get(trig)):
            return trig
    flags = envelope.get("flags") or {}
    if isinstance(flags, dict):
        for trig in CHAIR_REPORT_TRIGGERS:
            if _truthy(flags.get(trig)):
                return trig
    return None


def classify_terminal_state(envelope: Dict[str, Any]) -> str:
    """terminal_state 분류 (callback_ledger_v1.json terminal_state enum)."""
    # 1C0F6F52 패턴: envelope 회수 + status=ok chain 만 본 경우
    if envelope.get("control_plane_compliant") is False and envelope.get(
        "schedule_history_chain_ok"
    ):
        return "NOT_CONTROL_PLANE_COMPLIANT"

    if envelope.get("state_freshness_status") in {"STALE", "MISMATCH", "MISSING"}:
        return "STATE_STALE_OR_MISMATCH"

    if detect_critical7(envelope):
        return "CRITICAL7_HIT"

    if _truthy(envelope.get("permission_expansion")):
        return "PERMISSION_EXPANSION"
    if _truthy(envelope.get("credential_expansion")):
        return "CREDENTIAL_EXPANSION"

    batch = envelope.get("batch") or {}
    if isinstance(batch, dict) and batch:
        all_settled = batch.get("all_settled")
        if all_settled is False:
            return "SIBLING_INCOMPLETE"
        if all_settled is True:
            return "SIBLING_FINAL"

    status = (envelope.get("status") or "").upper()
    if status == "MERGE_READY" or _truthy(envelope.get("merge_ready")):
        return "MERGE_READY"
    if status in {"OWNER_GEMINI_TRIGGER", "OWNER_GEMINI_NUDGE"}:
        return "OWNER_GEMINI_TRIGGER"
    if status in {"PHASE3_TIMING_RACE", "AUTO_REMEDIATION_CANDIDATE"} or _truthy(
        envelope.get("auto_remediation_candidate")
    ):
        return "AUTO_REMEDIATION_CANDIDATE"
    if status in {"PASS", "TERMINAL_PASS", "OK"}:
        return "TERMINAL_PASS"
    if status in {"FAIL", "TERMINAL_FAIL", "FAILED"}:
        return "TERMINAL_FAIL"
    if status in {"IN_PROGRESS", "RUNNING"}:
        return "IN_PROGRESS"
    return "IN_PROGRESS"


def classify_policy(envelope: Dict[str, Any], terminal_state: str) -> str:
    """policy_class 분류 (보강-3 merge_policy_lock + 정책 분기)."""
    if terminal_state == "MERGE_READY":
        return "MERGE_POLICY_LOCK"  # 보강-3 hardcoded
    if terminal_state == "CRITICAL7_HIT":
        return "CRITICAL7_REPORT"
    if terminal_state in {"PERMISSION_EXPANSION", "CREDENTIAL_EXPANSION"}:
        return "PERMISSION_OR_CREDENTIAL_EXPANSION"
    if terminal_state == "AUTO_REMEDIATION_CANDIDATE":
        if _truthy(envelope.get("inside_expected_files", True)):
            return "AUTO_REMEDIATION_INSIDE_EXPECTED_FILES"
        return "HOLD_FOR_CHAIR"  # expected_files 밖이면 금지 → chair
    if terminal_state == "OWNER_GEMINI_TRIGGER":
        return "OWNER_GEMINI_TRIGGER_NUDGE"
    if terminal_state == "SIBLING_INCOMPLETE":
        return "BATCH_WAIT"
    if terminal_state == "SIBLING_FINAL":
        return "BATCH_ADJUDICATE"
    if terminal_state in {"TERMINAL_PASS", "TERMINAL_FAIL"}:
        if _truthy(envelope.get("followup_required")):
            return "FOLLOWUP_TASK_SPEC"
        if envelope.get("rerun_reason") in {"PHASE3_TIMING_RACE"}:
            return "PHASE3_TIMING_RACE"
        if _truthy(envelope.get("regression_rerun_needed")):
            return "REGRESSION_RERUN"
        return "LEDGER_ONLY_NOOP"
    if terminal_state == "STATE_STALE_OR_MISMATCH":
        return "SAFE_DEGRADED_MODE"
    if terminal_state == "NOT_CONTROL_PLANE_COMPLIANT":
        return "HOLD_FOR_CHAIR"
    return "HOLD_FOR_CHAIR"  # fail-closed default


def is_inside_expected_files(envelope: Dict[str, Any]) -> bool:
    """expected_files 내부 여부 (자동 진행 8 vs 금지 11 분기)."""
    return _truthy(envelope.get("inside_expected_files", True))


def adjudicate(envelope: Dict[str, Any]) -> Dict[str, Any]:
    """단일 callback 의 adjudication dict 생성.

    Returns next_action_runner 의 입력으로 쓰이는 dict.
    """
    terminal_state = classify_terminal_state(envelope)
    policy_class = classify_policy(envelope, terminal_state)
    critical7 = detect_critical7(envelope)
    chair_trigger = detect_chair_report_required(envelope)
    return {
        "schema": SCHEMA,
        "callback_id": envelope.get("callback_id"),
        "task_id": envelope.get("task_id"),
        "terminal_state": terminal_state,
        "terminal_state_classified": True,
        "policy_class": policy_class,
        "critical7_hit": critical7,
        "chair_report_trigger": chair_trigger,
        "inside_expected_files": is_inside_expected_files(envelope),
        "state_freshness_status": envelope.get("state_freshness_status", "FRESH"),
        "batch_present": bool(envelope.get("batch")),
        "merge_ready": terminal_state == "MERGE_READY",
        "control_plane_compliant_hint": envelope.get("control_plane_compliant"),
    }


def validate_adjudication(adj: Dict[str, Any]) -> List[str]:
    """adjudication dict 내부 정합성 검사. 위반 리스트 반환 (빈 리스트=PASS)."""
    failures: List[str] = []
    if not adj.get("terminal_state_classified"):
        failures.append("MISSING_TERMINAL_STATE_CLASSIFIED")
    if not adj.get("terminal_state"):
        failures.append("MISSING_TERMINAL_STATE")
    if not adj.get("policy_class"):
        failures.append("MISSING_POLICY_CLASS")
    if adj.get("merge_ready") and adj.get("policy_class") != "MERGE_POLICY_LOCK":
        failures.append("MERGE_READY_WITHOUT_POLICY_LOCK")
    return failures


__all__ = [
    "SCHEMA",
    "CRITICAL7_FIELDS",
    "CHAIR_REPORT_TRIGGERS",
    "detect_critical7",
    "detect_chair_report_required",
    "classify_terminal_state",
    "classify_policy",
    "is_inside_expected_files",
    "adjudicate",
    "validate_adjudication",
]
