# -*- coding: utf-8 -*-
"""utils.callback_adjudicator_v2 — terminal_state / policy / Critical7 판정기 (v2).

task-2644+1 ANU_CALLBACK_COLLECTOR_CONTROL_PLANE_CLEAN_REPLACEMENT
task md: memory/tasks/task-2644+1.md
spec (read-only): memory/specs/system_anu_callback_collector_control_plane_spec_260524.md

v2 변경:
- schema name suffix v2
- callback_collector_helper_integration 가용성 명시 (필수)
- helper integration unavailable → policy=HOLD_FOR_CHAIR fail-closed
- replacement_verdict 필드 추가 (회장 16 필드 확장)
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional

from utils import callback_collector_helper_integration as _integration


SCHEMA = "utils.callback_adjudicator.v2"
REPLACEMENT_OF = "utils.callback_adjudicator.v1"


CRITICAL7_FIELDS = (
    "critical7_hit",
    "critical_block",
    "blocking_secret_detected",
    "admin_override_required",
    "permission_expansion",
    "credential_expansion",
    "forbidden_target_modified",
)


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:
    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
    sev = (envelope.get("severity") or "").upper()
    if sev in {"CRITICAL7", "CRITICAL", "CRITICAL_BLOCK"}:
        return True
    return False


def detect_chair_report_required(envelope: Dict[str, Any]) -> Optional[str]:
    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:
    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:
    if terminal_state == "MERGE_READY":
        return "MERGE_POLICY_LOCK"  # 보강-3 hardcoded · override 불가
    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"
    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"


def is_inside_expected_files(envelope: Dict[str, Any]) -> bool:
    return _truthy(envelope.get("inside_expected_files", True))


def adjudicate(envelope: Dict[str, Any]) -> Dict[str, Any]:
    """v2 adjudication: terminal_state + policy + critical7 + helper integration probe."""
    status = _integration.integration_status()
    if not status.available:
        return {
            "schema": SCHEMA,
            "replacement_verdict": "REPLACEMENT_FAIL",
            "callback_id": envelope.get("callback_id"),
            "task_id": envelope.get("task_id"),
            "terminal_state": "NOT_CONTROL_PLANE_COMPLIANT",
            "terminal_state_classified": True,
            "policy_class": "HOLD_FOR_CHAIR",
            "critical7_hit": False,
            "chair_report_trigger": "HELPER_INTEGRATION_BYPASS",
            "inside_expected_files": is_inside_expected_files(envelope),
            "state_freshness_status": envelope.get("state_freshness_status", "MISSING"),
            "batch_present": bool(envelope.get("batch")),
            "merge_ready": False,
            "control_plane_compliant_hint": False,
            "helper_integration_status": status.to_json(),
        }

    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,
        "replacement_verdict": "AUTHORITATIVE_CLEAN_REPLACEMENT",
        "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"),
        "helper_integration_status": status.to_json(),
    }


def validate_adjudication(adj: Dict[str, Any]) -> List[str]:
    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")
    if adj.get("replacement_verdict") not in {
        "AUTHORITATIVE_CLEAN_REPLACEMENT",
        "NON_AUTHORITATIVE_SELF_COLLECTOR",
        "REPLACEMENT_FAIL",
    }:
        failures.append("MISSING_OR_INVALID_REPLACEMENT_VERDICT")
    return failures


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