"""Webhook decorators."""
from __future__ import absolute_import, unicode_literals

from itertools import chain

from six import iteritems as items, itervalues as values
from six.moves import UserDict


class webhook_model(UserDict):
    """Decorates models to send webhooks based changes to that model.

    Keyword Arguments:
        on_create (~thorn.Event): Event to dispatch whenever an instance of
            this model is created (``post_save``).
        on_change (~thorn.Event): Event to dispatch whenever an instance of
            this model is changed (``post_save``).
        on_delete (~thorn.Event): Event to dispatch whenever an instance of
            this model is deleted (``post_delete``).
        on_$event (~thorn.Event): Additional user defined events.,

        sender_field (str):
            Default field used as a sender for events, e.g. ``"account.user"``,
            will use ``instance.account.user``.

            Individual events can override the sender field user.
        reverse (Callable):
            A :class:`thorn.reverse.model_reverser` instance (or any callable
            taking an model instance as argument), that describes how to get
            the URL for an instance of this model.

            Individual events can override the reverser used.

    Examples:
        Simple article model, where the URL reference is retrieved
        by ``reverse('article-detail', kwargs={'uuid': article.uuid})``:

        .. code-block:: python

            @webhook_model(
                on_create=ModelEvent('article.created'),
                on_change=ModelEvent('article.changed'),
                on_delete=ModelEvent('article.removed'),
                on_deactivate=ModelEvent(
                    'article.deactivate', deactivated__eq=True,
                )
                reverse=model_reverser('article-detail', uuid='uuid'),
            )
            class Article(models.Model):
                uuid = models.UUIDField()

        The URL may not actually exist after deletion, so maybe we want
        to point the reference to something else in that special case,
        like a category that can be reversed by doing
        ``reverse('category-detail', args=[article.category.name])``.

        We can do that by having the ``on_delete`` event override
        the reverser used for that event only:

        .. code-block:: python

            @webhook_model(
                on_create=ModelEvent('article.created'),
                on_change=ModelEvent('article.changed'),
                on_delete=ModelEvent(
                    'article.removed',
                    reverse=model_reverser('category-detail', 'category.name'),
                ),

                on_hipri_delete=ModelEvent(
                    'article.internal_delete', priority__gte=30.0,
                ).dispatches_on_delete(),

                reverse=model_reverser('article-detail', uuid='uuid'),
            )
            class Article(model.Model):
                uuid = models.UUIDField()
                category = models.ForeignKey('category')
    """

    def __init__(self,
                 on_create=None, on_change=None, on_delete=None,
                 reverse=None, sender_field=None, **kwargs):
        self.reverse = reverse
        self.sender_field = sender_field
        self.events = {}

        self.update_events(
            {k: v for k, v in items(kwargs) if k.startswith('on_')},
            on_create=on_create and on_create.dispatches_on_create(),
            on_change=on_change and on_change.dispatches_on_change(),
            on_delete=on_delete and on_delete.dispatches_on_delete(),
        )

    def update_events(self, events, **kwargs):
        self.events.update(self.connect_events(events, **kwargs))

    def connect_events(self, events, **kwargs):
        return {
            k: self.contribute_to_event(v)
            for k, v in chain(items(events), items(kwargs))
        }

    def contribute_to_event(self, event):
        if event:
            if event.reverse is None:
                event.reverse = self.reverse
            event.sender_field = self.sender_field
        return event

    def __call__(self, model):
        [event.connect_model(model) for event in values(self.events) if event]
        model.webhook_events = self
        return model

    @property
    def data(self):
        # for UserDict
        return self.events
