try:
    # ContextDecorator was introduced in Python 3.2
    from contextlib import ContextDecorator
except ImportError:
    ContextDecorator = None
from functools import WRAPPER_ASSIGNMENTS, wraps

from django.db.models.signals import post_save, pre_delete

from . import algolia_engine


def available_attrs(fn):
    """
    Return the list of functools-wrappable attributes on a callable.
    This was required as a workaround for http://bugs.python.org/issue3445
    under Python 2.
    """
    return WRAPPER_ASSIGNMENTS


if ContextDecorator is None:
    # ContextDecorator was introduced in Python 3.2
    # See https://docs.python.org/3/library/contextlib.html#contextlib.ContextDecorator
    class ContextDecorator:
        """
        A base class that enables a context manager to also be used as a decorator.
        """
        def __call__(self, func):
            @wraps(func, assigned=available_attrs(func))
            def inner(*args, **kwargs):
                with self:
                    return func(*args, **kwargs)
            return inner


def register(model):
    """
    Register the given model class and wrapped AlgoliaIndex class with the Algolia engine:

    @register(Author)
    class AuthorIndex(AlgoliaIndex):
        pass

    """
    from algoliasearch_django import AlgoliaIndex, register

    def _algolia_engine_wrapper(index_class):
        if not issubclass(index_class, AlgoliaIndex):
            raise ValueError('Wrapped class must subclass AlgoliaIndex.')

        register(model, index_class)

        return index_class
    return _algolia_engine_wrapper


class disable_auto_indexing(ContextDecorator):
    """
    A context decorator to disable the auto-indexing behaviour of the AlgoliaIndex

    Can be used either as a context manager or a method decorator:
    >>> with disable_auto_indexing():
    >>>     my_object.save()

    >>> @disable_auto_indexing()
    >>> big_operation()
    """

    def __init__(self, model=None):
        if model is not None:
            self.models = [model]
        else:
            self.models = algolia_engine._AlgoliaEngine__registered_models

    def __enter__(self):
        for model in self.models:
            post_save.disconnect(
                algolia_engine._AlgoliaEngine__post_save_receiver,
                sender=model
            )
            pre_delete.disconnect(
                algolia_engine._AlgoliaEngine__pre_delete_receiver,
                sender=model
            )

    def __exit__(self, exc_type, exc_value, traceback):
        for model in self.models:
            post_save.connect(
                algolia_engine._AlgoliaEngine__post_save_receiver,
                sender=model
            )
            pre_delete.connect(
                algolia_engine._AlgoliaEngine__pre_delete_receiver,
                sender=model
            )