"""
Abstract base backend – defines the contract every delivery backend must fulfil.

Template Method Pattern:
  ``send()``  ← public hook called by the service layer
  ``_deliver()`` ← private hook implemented by each concrete backend
"""
from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from django.db.models import QuerySet

    from django_notify_hub.types import NotificationChannel, NotificationPayload

logger = logging.getLogger("django_notify_hub")


class AbstractNotificationBackend(ABC):
    """
    Contract that every notification delivery channel must implement.

    Subclass and override:
    - ``channel`` – the enum value this backend handles.
    - ``_deliver(payload, recipient)`` – actually send to one user.
    - ``_deliver_many(payload, recipients)`` – bulk delivery (optional optimisation).
    - ``setup()`` – one-time initialisation (credentials, connections, …).
    """

    #: Must be overridden to return the channel this backend handles.
    channel: "NotificationChannel"

    def __init__(self, **kwargs: Any) -> None:
        self._ready = False
        self.setup(**kwargs)
        self._ready = True

    # ── Template Method hooks ─────────────────────────────────────────────

    def setup(self, **kwargs: Any) -> None:  # noqa: B027  (intentionally empty)
        """
        One-time initialisation.  Override to open connections, load credentials, etc.
        Called automatically by ``__init__`` before any send.
        """

    @abstractmethod
    def _deliver(self, payload: "NotificationPayload", recipient: Any) -> bool:
        """
        Deliver *payload* to a single *recipient* (a Django User instance).

        Returns ``True`` on success, ``False`` on a non-fatal failure.
        Raise ``BackendError`` for hard failures.
        """

    def _deliver_many(
        self, payload: "NotificationPayload", recipients: "QuerySet | list[Any]"
    ) -> int:
        """
        Bulk delivery.  Default implementation calls ``_deliver`` in a loop.

        Override for backends that offer a native batch API (e.g. FCM multicast).
        Returns the number of successfully delivered notifications.
        """
        success = 0
        for recipient in recipients:
            try:
                if self._deliver(payload, recipient):
                    success += 1
            except Exception as exc:  # pragma: no cover
                logger.warning(
                    "Backend %s failed for recipient %s: %s",
                    self.__class__.__name__,
                    getattr(recipient, "pk", recipient),
                    exc,
                )
        return success

    # ── Public API ────────────────────────────────────────────────────────

    def send(self, payload: "NotificationPayload", recipient: Any) -> bool:
        """Send to a single recipient.  Wraps ``_deliver`` with logging."""
        logger.debug(
            "%s → send to %s",
            self.__class__.__name__,
            getattr(recipient, "pk", recipient),
        )
        return self._deliver(payload, recipient)

    def send_many(
        self, payload: "NotificationPayload", recipients: "QuerySet | list[Any]"
    ) -> int:
        """Send to multiple recipients.  Wraps ``_deliver_many`` with logging."""
        logger.debug("%s → send_many", self.__class__.__name__)
        return self._deliver_many(payload, recipients)

    def __repr__(self) -> str:  # pragma: no cover
        return f"<{self.__class__.__name__} channel={getattr(self, 'channel', '?')}>"

