# -*- coding: utf-8 -*-
"""utils.anu_collector_action_trigger — collector action vs sendfile-only split.

task-2636 — callback collector canonical-root resolver (Core hardening).

Spec: memory/specs/system_callback_collector_canonical_root_spec_260523.md
sha256: 6f0b04810cc458ea4cb12f3c1c9c511d14b1439917b7ef4f1ef91982e32d92c1

회장 verbatim (task md §2 항목 9, 10):
    9. callback envelope 는 단순 로그가 아니라 ANU collector action trigger
       임을 명확히 검증
    10. sendfile-only 와 normal callback envelope 구분 유지
        (collector trigger 시점에 sendfile envelope 무시)

ANCHOR-5 (spec §14): sendfile-only ≠ normal callback trigger 분리
(is_callback_action_trigger 단언).

fail-closed: NOT_REGISTERED / REGISTER_FAILED 는 trigger=False (5축 정합).
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional

from utils.callback_envelope_schema import (
    DeliveryMethod,
    RegistrationResultStatus,
    normalize_status,
)

TRIGGER_SCHEMA = "utils.anu_collector_action_trigger.v1"

# Sentinel reasons surfaced on action records — single source of truth for
# audit log / regression assertions.
REASON_NORMAL_CALLBACK = "normal_callback_registered"
REASON_SENDFILE_ONLY = "sendfile_only_delivery"
REASON_NOT_REGISTERED = "registration_result_status_not_registered"
REASON_REGISTER_FAILED = "registration_result_status_register_failed"
REASON_SKIPPED = "registration_result_status_skipped"
REASON_INVALID_ENVELOPE = "envelope_invalid_or_missing"


def is_callback_action_trigger(envelope: Any) -> bool:
    """Decide whether ``envelope`` should fire a collector action (spec §5.2).

    True iff ALL of:
      * envelope is a dict
      * registration_result_status (or legacy alias) == REGISTERED
      * delivery_method == anu_cron_callback

    False (fail-closed) otherwise — including sendfile_only, NOT_REGISTERED,
    REGISTER_FAILED, SKIPPED_WITH_EXPLICIT_REASON, and any envelope that is
    not a dict.
    """
    if not isinstance(envelope, dict):
        return False
    status = normalize_status(
        envelope.get(
            "registration_result_status",
            envelope.get("registration_status"),
        )
    )
    if status != RegistrationResultStatus.REGISTERED.value:
        return False
    delivery_method = envelope.get("delivery_method")
    if delivery_method != DeliveryMethod.ANU_CRON_CALLBACK.value:
        return False
    return True


def _classify_reason(envelope: Any) -> str:
    """Single-string explanation of the trigger decision (for action records)."""
    if not isinstance(envelope, dict):
        return REASON_INVALID_ENVELOPE
    status = normalize_status(
        envelope.get(
            "registration_result_status",
            envelope.get("registration_status"),
        )
    )
    delivery_method = envelope.get("delivery_method")
    if delivery_method == DeliveryMethod.SENDFILE_ONLY.value or status == (
        RegistrationResultStatus.SENDFILE_ONLY.value
    ):
        return REASON_SENDFILE_ONLY
    if status == RegistrationResultStatus.REGISTERED.value and delivery_method == (
        DeliveryMethod.ANU_CRON_CALLBACK.value
    ):
        return REASON_NORMAL_CALLBACK
    if status == RegistrationResultStatus.NOT_REGISTERED.value:
        return REASON_NOT_REGISTERED
    if status == RegistrationResultStatus.REGISTER_FAILED.value:
        return REASON_REGISTER_FAILED
    if status == RegistrationResultStatus.SKIPPED_WITH_EXPLICIT_REASON.value:
        return REASON_SKIPPED
    return REASON_INVALID_ENVELOPE


def enqueue_collector_action(
    envelope: Any,
    queue: Optional[List[Dict[str, Any]]] = None,
) -> Dict[str, Any]:
    """Append a collector action record IFF the envelope is a real trigger.

    Returns the action record (always — even when skipped — so callers can
    audit the decision). When trigger=False, the record is NOT pushed onto
    ``queue`` (sendfile-only must never produce collector work).
    """
    trigger = is_callback_action_trigger(envelope)
    reason = _classify_reason(envelope)
    record: Dict[str, Any] = {
        "schema": TRIGGER_SCHEMA,
        "is_trigger": trigger,
        "reason": reason,
        "task_id": envelope.get("task_id") if isinstance(envelope, dict) else None,
        "delivery_method": (
            envelope.get("delivery_method") if isinstance(envelope, dict) else None
        ),
        "registration_result_status": (
            normalize_status(
                envelope.get(
                    "registration_result_status",
                    envelope.get("registration_status"),
                )
            )
            if isinstance(envelope, dict)
            else None
        ),
        "callback_delivery_status": (
            envelope.get("callback_delivery_status")
            if isinstance(envelope, dict)
            else None
        ),
        "enqueued": False,
    }
    if trigger and queue is not None:
        queue.append(record)
        record["enqueued"] = True
    return record
