from django.contrib.auth import get_user_model
from django.db.models import QuerySet

from apps.core.notifications.models import Notification, NotificationPreference
from apps.core.notifications.tasks import dispatch_notification_channel
from apps.core.notifications.template_resolver import resolve_template

User = get_user_model()


def _channel_enabled(user, channel: str, verb: str) -> bool:
    prefs = NotificationPreference.objects.filter(user=user, channel=channel)
    if not prefs.exists():
        return True
    specific = prefs.filter(verb=verb).first()
    if specific:
        return specific.enabled
    general = prefs.filter(verb="").first()
    return general.enabled if general else True


def send_notification(
    recipient,
    message: str,
    *,
    verb: str = Notification.Verb.GENERIC,
    channels: list[str] | None = None,
    email_context: dict | None = None,
    data: dict | None = None,
    **kwargs,
) -> Notification:
    """Create in-app notification and queue async outbound channels (email/SMS).

    ``data`` is persisted on the Notification row for verb-specific
    in-app rendering (e.g., inbox countdown cards). It is *separate*
    from ``email_context``, which is rendered into the email at send
    time and not stored. Callers that want both should pass both.
    """
    notification = Notification.objects.create(
        recipient=recipient,
        verb=verb,
        message=message,
        data=data or {},
        **kwargs,
    )
    if channels is None:
        want = [NotificationPreference.Channel.EMAIL]
    else:
        want = channels
    ctx = dict(email_context or {})
    if "body" not in ctx:
        ctx["body"] = message
    # Allow operations admins to override per-verb subject/body/html via the
    # NotificationTemplate model. Resolver returns None when no active row
    # exists for this verb, which preserves the original caller-supplied
    # email_context behaviour.
    rendered = resolve_template(verb, notification=notification, email_context=ctx)
    if rendered:
        ctx.update(rendered)
    for ch in want:
        if ch == NotificationPreference.Channel.IN_APP:
            continue
        if not _channel_enabled(recipient, ch, verb):
            continue
        dispatch_notification_channel.delay(notification.pk, ch, ctx)
    return notification


def bulk_notify(
    users: QuerySet | list,
    message: str,
    *,
    verb: str = Notification.Verb.GENERIC,
    email_context: dict | None = None,
    data: dict | None = None,
    **kwargs,
):
    if isinstance(users, QuerySet):
        user_iter = users.iterator()
    else:
        user_iter = users
    base_ctx = dict(email_context or {})
    base_data = dict(data or {})
    for u in user_iter:
        send_notification(
            u,
            message,
            verb=verb,
            email_context=base_ctx,
            data=base_data,
            **kwargs,
        )
