"""
SignalRegistry – Observer Pattern for automatic model-event notifications.

Key improvements over original:
- Per-registration state (no class-level shared dicts → no race conditions).
- Lazy model loading (safe to call before Django app registry is ready).
- Bug fix: `create_notifications_for_multi_instances` now iterates (not lazy map).
- Proper recipient resolution with clear error messages.
"""
from __future__ import annotations

import logging
from copy import deepcopy
from functools import reduce
from typing import TYPE_CHECKING, Any, Callable

from django.apps import apps
from django.db.models import signals

from django_notify_hub.exceptions import InvalidRecipientError, RecipientFieldError, SignalRegistrationError
from django_notify_hub.types import BodyArg, DataArg, TitleArg

if TYPE_CHECKING:
    from django.contrib.auth.base_user import AbstractBaseUser
    from django.db.models import Model, QuerySet

logger = logging.getLogger("django_notify_hub")


class _ModelSignalHandler:
    """
    Encapsulates all state for one signal registration.

    Each call to ``SignalRegistry.on_create`` / ``on_update`` produces a new
    independent handler instance with its own ``_old_instances`` dict.
    This eliminates the shared-class-state race condition in the original code.
    """

    def __init__(
        self,
        title: TitleArg,
        body: BodyArg,
        recipients: Any,
        data: DataArg = None,
        condition: Callable[[Any, Any], bool] | None = None,
        service: Any = None,
    ) -> None:
        self.title = title
        self.body = body
        self.recipients = recipients
        self.data = data
        self.condition = condition
        self._old_instances: dict[Any, Any] = {}  # instance.pk → deep-copied old instance

        from django_notify_hub.services.notification import NotificationService
        self._service = service or NotificationService()

    # ── Signal handlers ────────────────────────────────────────────────

    def handle_post_save_create(self, sender: type, instance: Any, created: bool, **kwargs: Any) -> None:
        if created:
            self._notify(instance)

    def handle_pre_save(self, sender: type, instance: Any, **kwargs: Any) -> None:
        """Save a snapshot of the instance before the update."""
        if instance.pk is not None:
            try:
                old = sender.objects.get(pk=instance.pk)
                self._old_instances[instance.pk] = deepcopy(old)
            except sender.DoesNotExist:
                pass

    def handle_post_save_update(self, sender: type, instance: Any, created: bool, **kwargs: Any) -> None:
        if created:
            return
        old = self._old_instances.pop(instance.pk, None)
        if old is None:
            return
        if self.condition is None or self.condition(old, instance):
            self._notify(instance)

    # ── Internal ───────────────────────────────────────────────────────

    def _notify(self, instance: Any) -> None:
        from django_notify_hub.types import NotificationPayload

        payload = NotificationPayload.build(
            title=self.title,
            body=self.body,
            related_object=instance,
            data=self.data,
        )
        resolved = self._resolve_recipients(instance)
        if isinstance(resolved, list):
            self._service.send_many(payload, resolved)
        else:
            self._service.send(payload, resolved)

    def _resolve_recipients(self, instance: Any) -> Any:
        recipients = self.recipients
        if isinstance(recipients, str):
            try:
                user = reduce(getattr, recipients.split("."), instance)
            except AttributeError as exc:
                raise RecipientFieldError(recipients, type(instance).__name__) from exc
            return user
        return recipients


class SignalRegistry:
    """
    Public API for registering model-level notification triggers.

    Usage::

        from django_notify_hub.services.registry import SignalRegistry

        SignalRegistry.on_create(
            app_label="myapp", model_name="Order",
            title={"en": "New order"}, body={"en": "Order placed"},
            recipients=admin_user,
        )

        SignalRegistry.on_update(
            app_label="myapp", model_name="Order",
            title={"en": "Order updated"}, body=lambda i: {"en": f"Status: {i.status}"},
            recipients="customer",
            condition=lambda old, new: old.status != new.status,
        )
    """

    # Tracks every (signal, model, uid) registered so tests can call disconnect_all().
    _registered_uids: list[tuple[Any, type, str]] = []

    @classmethod
    def disconnect_all(cls) -> None:
        """Disconnect every signal registered through this registry and clear the list.

        Call this in test teardown to prevent signal handlers from leaking
        across test cases.
        """
        for sig, model, uid in cls._registered_uids:
            sig.disconnect(sender=model, dispatch_uid=uid)
        cls._registered_uids.clear()

    @staticmethod
    def _get_model(app_label: str, model_name: str) -> type:
        try:
            return apps.get_model(app_label, model_name)
        except (LookupError, ValueError) as exc:
            raise SignalRegistrationError(app_label, model_name, str(exc)) from exc

    @classmethod
    def on_create(
        cls,
        app_label: str,
        model_name: str,
        title: TitleArg,
        body: BodyArg,
        recipients: Any,
        data: DataArg = None,
    ) -> None:
        """Send a notification whenever a new instance of *model* is created."""
        model = cls._get_model(app_label, model_name)
        handler = _ModelSignalHandler(title=title, body=body, recipients=recipients, data=data)
        uid = f"django_notify_hub:create:{app_label}.{model_name}:{id(handler)}"
        signals.post_save.connect(handler.handle_post_save_create, sender=model, weak=False, dispatch_uid=uid)
        cls._registered_uids.append((signals.post_save, model, uid))
        logger.debug("SignalRegistry: registered on_create for %s.%s", app_label, model_name)

    @classmethod
    def on_update(
        cls,
        app_label: str,
        model_name: str,
        title: TitleArg,
        body: BodyArg,
        recipients: Any,
        condition: Callable[[Any, Any], bool] | None = None,
        data: DataArg = None,
    ) -> None:
        """Send a notification when an existing instance of *model* is updated."""
        model = cls._get_model(app_label, model_name)
        handler = _ModelSignalHandler(
            title=title, body=body, recipients=recipients, data=data, condition=condition
        )
        uid_pre = f"django_notify_hub:pre_save:{app_label}.{model_name}:{id(handler)}"
        uid_post = f"django_notify_hub:update:{app_label}.{model_name}:{id(handler)}"
        signals.pre_save.connect(handler.handle_pre_save, sender=model, weak=False, dispatch_uid=uid_pre)
        signals.post_save.connect(handler.handle_post_save_update, sender=model, weak=False, dispatch_uid=uid_post)
        cls._registered_uids.append((signals.pre_save, model, uid_pre))
        cls._registered_uids.append((signals.post_save, model, uid_post))
        logger.debug("SignalRegistry: registered on_update for %s.%s", app_label, model_name)

