"""Bridge self-monitoring event parser.

The bridge generates these events from internal subsystems (watcher,
scheduler, dispatcher) — the parser turns a flat payload dict into the
generic :class:`ServiceEvent` shape that the rest of the dispatch
pipeline expects.

Payload shape::

    {
        "failure_type": "poll_failures" | "deferred_backlog" | "target_failures",
        "subject_id":   int,        # tracker_id, target_id, or 0
        "subject_name": str,
        "count":        int,        # consecutive failures or pending count
        "threshold":    int,
        "last_error":   str,        # may be empty
        "details":      dict[str, Any],  # extra context
    }
"""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any

from notify_bridge_core.models.events import EventType, ServiceEvent
from notify_bridge_core.providers.base import ServiceProviderType


# Defensive cap on the persisted error message; very long tracebacks would
# bloat the EventLog details JSON column otherwise.
_MAX_ERROR_LEN = 1000


_FAILURE_TYPE_TO_EVENT: dict[str, EventType] = {
    "poll_failures": EventType.BRIDGE_SELF_POLL_FAILURES,
    "deferred_backlog": EventType.BRIDGE_SELF_DEFERRED_BACKLOG,
    "target_failures": EventType.BRIDGE_SELF_TARGET_FAILURES,
}


def build_event(
    payload: dict[str, Any],
    *,
    provider_name: str = "Bridge Self-Monitoring",
    timestamp: datetime | None = None,
) -> ServiceEvent | None:
    """Convert a self-monitoring payload dict into a ServiceEvent.

    Returns None for malformed payloads (unknown failure_type or missing
    keys) — the caller drops without raising so a misbehaving emitter
    can never tip over the dispatch pipeline.
    """
    if not isinstance(payload, dict):
        return None
    failure_type = payload.get("failure_type")
    event_type = _FAILURE_TYPE_TO_EVENT.get(str(failure_type) if failure_type else "")
    if event_type is None:
        return None

    subject_id = int(payload.get("subject_id") or 0)
    subject_name = str(payload.get("subject_name") or "")
    count = int(payload.get("count") or 0)
    threshold = int(payload.get("threshold") or 0)
    last_error = str(payload.get("last_error") or "")[:_MAX_ERROR_LEN]
    details = payload.get("details") if isinstance(payload.get("details"), dict) else {}

    when = timestamp or datetime.now(timezone.utc)

    return ServiceEvent(
        event_type=event_type,
        provider_type=ServiceProviderType.BRIDGE_SELF,
        provider_name=provider_name,
        # ``collection_id`` / ``collection_name`` are required fields on
        # ServiceEvent; we use the subject so quiet-hours / dedupe logic
        # treats different subjects as distinct streams.
        collection_id=str(subject_id),
        collection_name=subject_name or str(failure_type),
        timestamp=when,
        extra={
            "failure_type": str(failure_type),
            "subject_id": subject_id,
            "subject_name": subject_name,
            "count": count,
            "threshold": threshold,
            "last_error": last_error,
            "details": dict(details),
        },
    )
