"""
NotificationRepository – Repository Pattern.

All database access for Notification records lives here.
The service layer never touches the ORM directly.

This makes it easy to:
- Mock the repository in unit tests (no DB required).
- Swap the storage backend (e.g. Mongo, Redis) without touching business logic.
- Centralise query optimisation (select_related, indexes, etc.).
"""
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from django.db import transaction

from django_notify_hub.models import Notification, NotificationTemplate
from django_notify_hub.types import NotificationPayload, NotificationStatus

if TYPE_CHECKING:
    from django.db.models import QuerySet


class NotificationRepository:
    """
    Data-access object for :class:`~django_notify_hub.models.Notification`.

    All methods are class-methods so you can use the repository without
    instantiation: ``NotificationRepository.create(payload, user)``.
    """

    # ── Write operations ──────────────────────────────────────────────────

    @staticmethod
    def _resolve_lang(recipient: Any) -> str:
        for attr_name in ("language", "lang", "locale", "preferred_language"):
            value = getattr(recipient, attr_name, None)
            if isinstance(value, str) and value.strip():
                return value.strip()[:10]
        return "en"

    @classmethod
    def create(
        cls,
        payload: NotificationPayload,
        recipient: Any,
        channel: str = "fcm",
        status: str = NotificationStatus.PENDING.value,
    ) -> Notification:
        """Persist a single custom notification record."""
        return Notification.objects.create(
            to_user=recipient,
            lang=cls._resolve_lang(recipient),
            override_title=payload.title,
            override_body=payload.body,
            custom_data=payload.data,
            custom_priority=payload.priority.value,
            custom_channel=channel,
            status=status,
        )

    @classmethod
    def bulk_create(
        cls,
        payload: NotificationPayload,
        recipients: "QuerySet | list[Any]",
        channel: str = "fcm",
        status: str = NotificationStatus.PENDING.value,
    ) -> list[Notification]:
        """Persist one shared template plus one record per recipient."""
        recipient_list = list(recipients)
        if not recipient_list:
            return []

        with transaction.atomic():
            template = NotificationTemplate.objects.create(
                title=payload.title,
                body=payload.body,
                data=payload.data,
                priority=payload.priority.value,
                channel=channel,
            )
            objects = [
                Notification(
                    to_user=recipient,
                    template=template,
                    lang=cls._resolve_lang(recipient),
                    status=status,
                )
                for recipient in recipient_list
            ]
            return Notification.objects.bulk_create(objects)

    @classmethod
    def mark_read(cls, pk: int, user: Any) -> bool:
        """Mark one notification as read for a specific user.  Returns True if updated."""
        updated = Notification.objects.filter(pk=pk, to_user=user, is_read=False).update(
            is_read=True, status=NotificationStatus.READ.value
        )
        return updated > 0

    @classmethod
    def mark_many_read(cls, pks: list[int], user: Any) -> int:
        """Bulk-mark notifications as read.  Returns the number updated."""
        return Notification.objects.filter(pk__in=pks, to_user=user).update(
            is_read=True, status=NotificationStatus.READ.value
        )

    @classmethod
    def update_status(cls, pk: int, status: str) -> bool:
        updated = Notification.objects.filter(pk=pk).update(status=status)
        return updated > 0

    # ── Read operations ───────────────────────────────────────────────────

    @classmethod
    def get_for_user(cls, user: Any) -> "QuerySet[Notification]":
        return Notification.objects.filter(to_user=user).select_related("to_user", "template")

    @classmethod
    def get_unread_count(cls, user: Any) -> int:
        return Notification.objects.filter(to_user=user, is_read=False).count()

    @classmethod
    def get_stats(cls) -> dict[str, int]:
        """Return aggregate statistics (used by admin dashboard)."""
        from django.db.models import Count, Q
        from django.utils import timezone

        today = timezone.now().date()
        return Notification.objects.aggregate(
            total=Count("id"),
            unread=Count("id", filter=Q(is_read=False)),
            today=Count("id", filter=Q(created_at__date=today)),
            read_today=Count("id", filter=Q(is_read=True, updated_at__date=today)),
        )

    @classmethod
    def delete_old_read(cls, days: int = 30) -> int:
        """Delete read notifications older than *days*.  Returns deleted count."""
        from django.utils import timezone
        from datetime import timedelta

        threshold = timezone.now() - timedelta(days=days)
        deleted, _ = Notification.objects.filter(
            is_read=True, created_at__lt=threshold
        ).delete()
        return deleted

