from collections import namedtuple

from django.conf import settings
from django.db.transaction import on_commit
from django.shortcuts import get_object_or_404
from rest_framework import generics, status
from rest_framework.decorators import action
from rest_framework.response import Response

from gallery.tasks import delete_image
from search_indexes.documents import es_update
from news.models import News


# JWT
# Login base view mixins
class JWTGenericViewMixin:
    """JWT view mixin"""

    ACCESS_TOKEN_HTTP_ONLY = False
    ACCESS_TOKEN_SECURE = False

    REFRESH_TOKEN_HTTP_ONLY = False
    REFRESH_TOKEN_SECURE = False

    LOCALE_HTTP_ONLY = False
    LOCALE_SECURE = False

    COUNTRY_CODE_HTTP_ONLY = False
    COUNTRY_CODE_SECURE = False

    COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure', 'max_age'])

    def _put_data_in_cookies(self,
                             access_token: str = None,
                             refresh_token: str = None,
                             permanent: bool = None):
        """
        cookies it is list that contain namedtuples
        cookies would contain key, value and secure parameters.
        """
        COOKIES = []

        if hasattr(self, 'request'):
            if hasattr(self.request, 'locale'):
                COOKIES.append(self.COOKIE(key='locale',
                                           value=self.request.locale,
                                           http_only=self.ACCESS_TOKEN_HTTP_ONLY,
                                           secure=self.LOCALE_SECURE,
                                           max_age=settings.COOKIES_MAX_AGE if permanent else None))
            if hasattr(self.request, 'country_code'):
                COOKIES.append(self.COOKIE(key='country_code',
                                           value=self.request.country_code,
                                           http_only=self.COUNTRY_CODE_HTTP_ONLY,
                                           secure=self.COUNTRY_CODE_SECURE,
                                           max_age=settings.COOKIES_MAX_AGE if permanent else None))
            if access_token:
                COOKIES.append(self.COOKIE(key='access_token',
                                           value=access_token,
                                           http_only=self.ACCESS_TOKEN_HTTP_ONLY,
                                           secure=self.ACCESS_TOKEN_SECURE,
                                           max_age=settings.COOKIES_MAX_AGE if permanent else None))
            if refresh_token:
                COOKIES.append(self.COOKIE(key='refresh_token',
                                           value=refresh_token,
                                           http_only=self.REFRESH_TOKEN_HTTP_ONLY,
                                           secure=self.REFRESH_TOKEN_SECURE,
                                           max_age=settings.COOKIES_MAX_AGE if permanent else None))
        return COOKIES

    def _put_cookies_in_response(self, cookies: list, response: Response):
        """Update COOKIES in response from namedtuple"""
        for cookie in cookies:
            response.set_cookie(key=cookie.key,
                                value=cookie.value,
                                secure=cookie.secure,
                                httponly=cookie.http_only,
                                max_age=cookie.max_age,
                                domain=settings.COOKIE_DOMAIN)
        return response

    def _get_tokens_from_cookies(self, request, cookies: dict = None):
        """Get user tokens from cookies and put in namedtuple"""
        _cookies = request.COOKIES or cookies
        return [self.COOKIE(key='access_token',
                            value=_cookies.get('access_token'),
                            http_only=self.ACCESS_TOKEN_HTTP_ONLY,
                            secure=self.ACCESS_TOKEN_SECURE,
                            max_age=_cookies.get('max_age')),
                self.COOKIE(key='refresh_token',
                            value=_cookies.get('refresh_token'),
                            http_only=self.REFRESH_TOKEN_HTTP_ONLY,
                            secure=self.REFRESH_TOKEN_SECURE,
                            max_age=_cookies.get('max_age'))]


class CreateDestroyGalleryViewMixin(generics.CreateAPIView,
                                    generics.DestroyAPIView):
    """Mixin for creating and destroying entity linked with gallery."""

    def create(self, request, *args, **kwargs):
        """Overridden create method"""
        super().create(request, *args, **kwargs)
        return Response(status=status.HTTP_201_CREATED)

    def destroy(self, request, *args, **kwargs):
        """Override destroy method."""
        gallery_obj = self.get_object()
        if settings.USE_CELERY:
            on_commit(lambda: delete_image.delay(image_id=gallery_obj.image.id,
                                                 completely=False))
        else:
            on_commit(lambda: delete_image(image_id=gallery_obj.image.id,
                                           completely=False))
        # Delete an instances of Gallery model
        gallery_obj.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class BaseCreateDestroyMixinView(generics.CreateAPIView, generics.DestroyAPIView):
    """Base Create Destroy mixin."""

    _model = None
    serializer_class = None
    lookup_field = 'slug'

    def get_base_object(self):
        if 'slugs' in [f.name for f in self._model._meta.get_fields()]: # slugs instead of `slug`
            return get_object_or_404(self._model, slugs__values__contains=[self.kwargs['slug']])
        return get_object_or_404(self._model, slug=self.kwargs['slug'])

    def es_update_base_object(self):
        es_update(self.get_base_object())

    def perform_create(self, serializer):
        serializer.save()
        self.es_update_base_object()

    def perform_destroy(self, instance):
        instance.delete()
        self.es_update_base_object()


class FavoritesCreateDestroyMixinView(BaseCreateDestroyMixinView):
    """Favorites Create Destroy mixin."""

    def get_object(self):
        """
        Returns the object the view is displaying.
        """
        obj = self.get_base_object()
        favorites = get_object_or_404(obj.favorites.filter(user=self.request.user))
        # May raise a permission denied
        self.check_object_permissions(self.request, favorites)
        return favorites


class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView):
    """Carousel Create Destroy mixin."""

    lookup_field = 'id'

    def get_base_object(self):
        establishment_pk = self.kwargs.get('pk')
        establishment_slug = self.kwargs.get('slug')

        search_kwargs = {'id': establishment_pk} if establishment_pk else {'slug': establishment_slug}
        return get_object_or_404(self._model, **search_kwargs)

    def get_object(self):
        """
        Returns the object the view is displaying.
        """
        obj = self.get_base_object()
        carousels = get_object_or_404(obj.carousels.all())
        # May raise a permission denied
        # TODO: возможно нужны пермишены
        # self.check_object_permissions(self.request, carousels)
        return carousels


# BackOffice user`s views & viewsets
class BindObjectMixin:
    """Bind object mixin."""

    def get_serializer_class(self):
        if self.action == 'bind_object':
            return self.bind_object_serializer_class
        return self.serializer_class

    def perform_binding(self, serializer):
        raise NotImplemented

    def perform_unbinding(self, serializer):
        raise NotImplemented

    @action(methods=['post', 'delete'], detail=True, url_path='bind-object')
    def bind_object(self, request, pk=None):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        if request.method == 'POST':
            self.perform_binding(serializer)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        elif request.method == 'DELETE':
            self.perform_unbinding(serializer)
            return Response(status=status.HTTP_204_NO_CONTENT)
