import json
from functools import wraps

from django.contrib.auth import SESSION_KEY
from django.contrib.messages import api, constants
from django.db import models
from django.utils import timezone
from django.utils.functional import SimpleLazyObject


def _positional(count):
    """
    Only allows ``count`` positional arguments to the decorated callable

    Will be removed as soon as we drop support for Python 2.
    """

    def _dec(fn):
        @wraps(fn)
        def _fn(*args, **kwargs):
            if len(args) > count:  # pragma: no cover
                raise TypeError(
                    "Only %s positional argument%s allowed"
                    % (count, "" if count == 1 else "s")
                )
            return fn(*args, **kwargs)

        return _fn

    return _dec


@_positional(4)
def add_message(user, level, message, extra_tags="", deliver_once=True, meta=None):
    from user_messages.models import Message

    Message.objects.create(
        level=level or 20,  # INFO
        message=message,
        extra_tags=extra_tags,
        _metadata=json.dumps(meta or {}),
        deliver_once=deliver_once,
        **{"user" if isinstance(user, models.Model) else "user_id": user}
    )


@_positional(0)
def get_messages(request=None, user=None):
    assert bool(request) != bool(user), "Pass exactly one of request or user"
    _nonlocal = (user,)

    def fetch():
        messages = []
        (user,) = _nonlocal

        if request is not None:
            messages.extend(api.get_messages(request))
            if request.session.get(SESSION_KEY) and request.user.is_authenticated:
                user = request.user

        if user is not None:
            from user_messages.models import Message

            user_messages = Message.objects.filter(
                user=user, delivered_at__isnull=True
            ).order_by("pk")
            messages.extend(user_messages)
            if any(m.deliver_once for m in user_messages):
                user_messages.filter(deliver_once=True).update(
                    delivered_at=timezone.now()
                )

        return messages

    return SimpleLazyObject(fetch)


def _create_shortcut(level):
    @_positional(3)
    def helper(user, message, extra_tags="", deliver_once=True, meta=None):
        add_message(
            user,
            level,
            message,
            extra_tags=extra_tags,
            deliver_once=deliver_once,
            meta=meta,
        )

    return helper


debug = _create_shortcut(constants.DEBUG)
info = _create_shortcut(constants.INFO)
success = _create_shortcut(constants.SUCCESS)
warning = _create_shortcut(constants.WARNING)
error = _create_shortcut(constants.ERROR)