"""Notification models for template-backed and custom per-user records."""
from __future__ import annotations

from typing import Any

from django.conf import settings
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from django_notify_hub.types import NotificationPriority, NotificationStatus


def _localised_value(source: Any, lang: str) -> str:
    """Return a localised string from a dict-like or scalar source."""
    if isinstance(source, dict):
        return source.get(lang) or next(iter(source.values()), "")
    return str(source or "")


class NotificationTemplate(models.Model):
    """Shared notification content reused across many per-user records."""

    title = models.JSONField(
        verbose_name=_("title"),
        help_text=_(
            'Shared localised title dict, e.g. {"en": "Hello", "ar": "مرحبا"}'
        ),
    )
    body = models.JSONField(
        verbose_name=_("body"),
        help_text=_(
            'Shared localised body dict, e.g. {"en": "You have a new message."}'
        ),
    )
    data = models.JSONField(
        verbose_name=_("metadata"),
        default=dict,
        blank=True,
        help_text=_("Shared JSON payload delivered alongside the notification."),
    )
    priority = models.CharField(
        verbose_name=_("priority"),
        max_length=20,
        choices=[(p.value, p.name.title()) for p in NotificationPriority],
        default=NotificationPriority.NORMAL.value,
    )
    channel = models.CharField(
        verbose_name=_("channel"),
        max_length=50,
        default="fcm",
        help_text=_("Shared delivery channel used (fcm, email, websocket, …)."),
    )
    created_at = models.DateTimeField(verbose_name=_("created at"), default=timezone.now)

    class Meta:
        app_label = "django_notify_hub"
        verbose_name = _("Notification template")
        verbose_name_plural = _("Notification templates")
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"[{self.channel}] {self.get_title()[:40]}"

    def get_title(self, lang: str = "en") -> str:
        return _localised_value(self.title, lang)

    def get_body(self, lang: str = "en") -> str:
        return _localised_value(self.body, lang)


class Notification(models.Model):
    """
    Persisted notification record.

    Each record represents one notification delivered (or to be delivered)
    to one user.  Multi-user broadcasts are stored as N individual records
    so each user's read/deleted state is tracked independently.
    """

    # ── Recipients ────────────────────────────────────────────────────────
    to_user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="notifications",
        verbose_name=_("recipient"),
    )

    template = models.ForeignKey(
        "django_notify_hub.NotificationTemplate",
        on_delete=models.PROTECT,
        related_name="notifications",
        null=True,
        blank=True,
        verbose_name=_("template"),
    )
    lang = models.CharField(
        verbose_name=_("language"),
        max_length=10,
        default="en",
        help_text=_("Language code used when rendering shared template content."),
    )
    override_title = models.JSONField(
        verbose_name=_("override title"),
        null=True,
        blank=True,
        help_text=_("Optional per-record localised title override."),
    )
    override_body = models.JSONField(
        verbose_name=_("override body"),
        null=True,
        blank=True,
        help_text=_("Optional per-record localised body override."),
    )
    custom_data = models.JSONField(
        verbose_name=_("custom metadata"),
        null=True,
        blank=True,
        help_text=_("Per-record metadata used when no shared template is attached."),
    )
    custom_priority = models.CharField(
        verbose_name=_("priority"),
        max_length=20,
        choices=[(p.value, p.name.title()) for p in NotificationPriority],
        default="",
        blank=True,
    )
    custom_channel = models.CharField(
        verbose_name=_("channel"),
        max_length=50,
        default="",
        blank=True,
        help_text=_("Per-record delivery channel used when no shared template is attached."),
    )

    # ── State ─────────────────────────────────────────────────────────────
    status = models.CharField(
        verbose_name=_("status"),
        max_length=20,
        choices=[(s.value, s.name.title()) for s in NotificationStatus],
        default=NotificationStatus.PENDING.value,
    )
    is_read = models.BooleanField(verbose_name=_("read"), default=False)

    # ── Timestamps ────────────────────────────────────────────────────────
    created_at = models.DateTimeField(verbose_name=_("created at"), default=timezone.now)
    updated_at = models.DateTimeField(verbose_name=_("updated at"), auto_now=True)

    class Meta:
        app_label = "django_notify_hub"
        verbose_name = _("Notification")
        verbose_name_plural = _("Notifications")
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["to_user", "is_read"], name="notify_user_read_idx"),
            models.Index(fields=["to_user", "status"], name="notify_user_status_idx"),
            models.Index(fields=["created_at"], name="notify_created_idx"),
        ]

    def __str__(self) -> str:
        title_preview = self.get_title()[:40]
        return f"[{self.status}] {title_preview} → {self.to_user_id}"

    # ── Helpers ───────────────────────────────────────────────────────────

    @property
    def title(self) -> Any:
        if self.override_title is not None:
            return self.override_title
        if self.template_id:
            return self.template.title
        return {}

    @property
    def body(self) -> Any:
        if self.override_body is not None:
            return self.override_body
        if self.template_id:
            return self.template.body
        return {}

    @property
    def data(self) -> dict[str, Any]:
        if self.template_id:
            return self.template.data
        return self.custom_data or {}

    @property
    def priority(self) -> str:
        if self.template_id:
            return self.template.priority
        return self.custom_priority or NotificationPriority.NORMAL.value

    @property
    def channel(self) -> str:
        if self.template_id:
            return self.template.channel
        return self.custom_channel or "fcm"

    def get_title(self, lang: str | None = None) -> str:
        """Return title for *lang*, falling back to first available value."""
        active_lang = lang or self.lang or "en"
        return _localised_value(self.title, active_lang)

    def get_body(self, lang: str | None = None) -> str:
        """Return body for *lang*, falling back to first available value."""
        active_lang = lang or self.lang or "en"
        return _localised_value(self.body, active_lang)

    def mark_read(self) -> None:
        """Mark this notification as read and persist."""
        if not self.is_read:
            self.is_read = True
            self.status = NotificationStatus.READ.value
            self.save(update_fields=["is_read", "status", "updated_at"])

