"""
Core type definitions for django-notify-hub.

Includes enums for priority/channel/status and the immutable
NotificationPayload dataclass used throughout the pipeline.
"""
from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable


class NotificationPriority(str, Enum):
    """Delivery priority hint passed to backends that support it."""

    LOW = "low"
    NORMAL = "normal"
    HIGH = "high"
    CRITICAL = "critical"


class NotificationChannel(str, Enum):
    """
    Logical channel names.  Map to backend classes via DJANGO_NOTIFY['BACKENDS'].
    """

    FCM = "fcm"
    EMAIL = "email"
    WEBSOCKET = "websocket"
    DUMMY = "dummy"


class NotificationStatus(str, Enum):
    """Delivery status recorded per notification record."""

    PENDING = "pending"
    SENT = "sent"
    FAILED = "failed"
    READ = "read"


# ──────────────────────────────────────────────────────────────────────────────
# Payload – the central value object flowing through the whole pipeline
# ──────────────────────────────────────────────────────────────────────────────

LocalizedStr = dict[str, str]  # e.g. {"en": "Hello", "ar": "مرحباً"}
TitleArg = LocalizedStr | Callable[[Any], LocalizedStr]
BodyArg = LocalizedStr | Callable[[Any], LocalizedStr]
DataArg = dict[str, Any] | Callable[[Any], dict[str, Any]] | None


@dataclass(frozen=True)
class NotificationPayload:
    """
    Immutable value object representing a fully-resolved notification.

    title / body must be ``{"lang_code": "text"}`` dicts.
    data is arbitrary JSON-serialisable metadata (e.g. ``{"object_id": "42"}``).
    """

    title: LocalizedStr
    body: LocalizedStr
    data: dict[str, Any] = field(default_factory=dict)
    priority: NotificationPriority = NotificationPriority.NORMAL
    channels: tuple[NotificationChannel, ...] = (NotificationChannel.FCM,)

    # ── Convenience constructors ───────────────────────────────────────────

    @classmethod
    def build(
        cls,
        title: TitleArg,
        body: BodyArg,
        related_object: Any,
        data: DataArg = None,
        priority: NotificationPriority = NotificationPriority.NORMAL,
        channels: tuple[NotificationChannel, ...] = (NotificationChannel.FCM,),
    ) -> "NotificationPayload":
        """
        Resolve callables against *related_object* and return a frozen payload.
        """
        resolved_title = title(related_object) if callable(title) else title
        resolved_body = body(related_object) if callable(body) else body

        if data is None:
            try:
                resolved_data = {
                    "object_id": str(related_object.pk),
                    "model_name": type(related_object).__name__,
                }
            except AttributeError:
                # related_object is a plain object() sentinel (no pk) — use empty data.
                resolved_data = {}
        elif callable(data):
            resolved_data = data(related_object)
        else:
            resolved_data = data

        return cls(
            title=resolved_title,
            body=resolved_body,
            data=resolved_data,
            priority=priority,
            channels=channels,
        )

    def get_title(self, lang: str = "en") -> str:
        """Return title for the given language, falling back to first available."""
        return self.title.get(lang) or next(iter(self.title.values()), "")

    def get_body(self, lang: str = "en") -> str:
        """Return body for the given language, falling back to first available."""
        return self.body.get(lang) or next(iter(self.body.values()), "")

