"""Establishment app views."""
from django.conf import settings
from django.contrib.gis.geos import Point
from django.shortcuts import get_object_or_404
from rest_framework import generics, permissions

from comment import models as comment_models
from comment.serializers import CommentRUDSerializer
from establishment import filters, models, serializers
from main import methods
from utils.pagination import PortionPagination
from utils.views import FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView


class EstablishmentMixinView:
    """Establishment mixin."""

    permission_classes = (permissions.AllowAny,)

    def get_queryset(self):
        """Overridden method 'get_queryset'."""
        qs = models.Establishment.objects.published() \
            .with_base_related() \
            .annotate_in_favorites(self.request.user)
        if self.request.country_code:
            qs = qs.by_country_code(self.request.country_code)
        return qs


class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView):
    """Resource for getting a list of establishments."""

    filter_class = filters.EstablishmentFilter
    serializer_class = serializers.EstablishmentListRetrieveSerializer

    def get_queryset(self):
        return super().get_queryset().with_schedule() \
            .with_extended_address_related().with_currency_related() \
            .with_certain_tag_category_related('category', 'restaurant_category') \
            .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \
            .with_certain_tag_category_related('shop_category', 'artisan_category')


class EstablishmentSimilarView(EstablishmentListView):
    """Resource for getting a list of similar establishments."""
    serializer_class = serializers.EstablishmentSimilarSerializer
    pagination_class = None

    def get_base_object(self):
        """
        Return base establishment instance for a getting list of similar establishments.
        """
        establishment = get_object_or_404(models.Establishment.objects.all(),
                                          slug=self.kwargs.get('slug'))
        return establishment

    def get_queryset(self):
        """Overridden get_queryset method."""
        return EstablishmentMixinView.get_queryset(self) \
                                     .has_location()


class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView):
    """Resource for getting a establishment."""

    lookup_field = 'slug'
    serializer_class = serializers.EstablishmentDetailSerializer

    def get_queryset(self):
        return super().get_queryset().with_extended_related()


class EstablishmentMobileRetrieveView(EstablishmentRetrieveView):
    serializer_class = serializers.MobileEstablishmentDetailSerializer

    def get_queryset(self):
        return super().get_queryset().prefetch_comments()


class EstablishmentRecentReviewListView(EstablishmentListView):
    """List view for last reviewed establishments."""

    pagination_class = PortionPagination

    def get_queryset(self):
        """Overridden method 'get_queryset'."""
        qs = super().get_queryset()
        query_params = self.request.query_params
        if 'longitude' in query_params and 'latitude' in query_params:
            longitude, latitude = query_params.get('longitude'), query_params.get('latitude')
        else:
            longitude, latitude = methods.determine_coordinates(self.request)
        if not longitude or not latitude:
            return qs.none()
        point = Point(x=float(longitude), y=float(latitude), srid=settings.GEO_DEFAULT_SRID)
        return qs.last_reviewed(point=point)


class RestaurantSimilarListView(EstablishmentSimilarView):
    """Resource for getting a list of similar restaurants."""

    def get_queryset(self):
        """Overridden get_queryset method"""
        qs = super(RestaurantSimilarListView, self).get_queryset()
        base_establishment = self.get_base_object()

        if base_establishment:
            return qs.similar_restaurants(base_establishment)[:settings.QUERY_OUTPUT_OBJECTS]
        else:
            return EstablishmentMixinView.get_queryset(self) \
                                         .none()


class WinerySimilarListView(EstablishmentSimilarView):
    """Resource for getting a list of similar wineries."""

    def get_queryset(self):
        """Overridden get_queryset method"""
        qs = EstablishmentSimilarView.get_queryset(self)
        base_establishment = self.get_base_object()

        if base_establishment:
            return qs.similar_wineries(base_establishment)[:settings.QUERY_OUTPUT_OBJECTS]
        else:
            return qs.none()


class ArtisanProducerSimilarListView(EstablishmentSimilarView):
    """Resource for getting a list of similar artisan/producer(s)."""

    def get_queryset(self):
        """Overridden get_queryset method"""
        qs = super(ArtisanProducerSimilarListView, self).get_queryset()
        base_establishment = self.get_base_object()

        if base_establishment:
            return qs.similar_artisans_producers(base_establishment)[:settings.QUERY_OUTPUT_OBJECTS]
        else:
            return qs.none()


class EstablishmentTypeListView(generics.ListAPIView):
    """Resource for getting a list of establishment types."""

    permission_classes = (permissions.AllowAny,)
    serializer_class = serializers.EstablishmentTypeBaseSerializer
    queryset = models.EstablishmentType.objects.all()


class EstablishmentCommentCreateView(generics.CreateAPIView):
    """View for create new comment."""
    lookup_field = 'slug'
    serializer_class = serializers.EstablishmentCommentCreateSerializer
    queryset = comment_models.Comment.objects.all()


class EstablishmentCommentListView(generics.ListAPIView):
    """View for return list of establishment comments."""

    permission_classes = (permissions.AllowAny,)
    serializer_class = serializers.EstablishmentCommentCreateSerializer

    def get_queryset(self):
        """Override get_queryset method"""

        establishment = get_object_or_404(models.Establishment, slug=self.kwargs['slug'])
        return establishment.comments.order_by('-created')


class EstablishmentCommentRUDView(generics.RetrieveUpdateDestroyAPIView):
    """View for retrieve/update/destroy establishment comment."""
    serializer_class = CommentRUDSerializer
    queryset = models.Establishment.objects.all()

    def get_object(self):
        """
        Returns the object the view is displaying.
        """
        queryset = self.filter_queryset(self.get_queryset())

        establishment_obj = get_object_or_404(queryset,
                                              slug=self.kwargs['slug'])
        comment_obj = get_object_or_404(establishment_obj.comments.by_user(self.request.user),
                                        pk=self.kwargs['comment_id'])

        # May raise a permission denied
        self.check_object_permissions(self.request, comment_obj)

        return comment_obj


class EstablishmentFavoritesCreateDestroyView(FavoritesCreateDestroyMixinView):
    """View for create/destroy establishment from favorites."""

    _model = models.Establishment
    serializer_class = serializers.EstablishmentFavoritesCreateSerializer


class EstablishmentCarouselCreateDestroyView(CarouselCreateDestroyMixinView):
    """View for create/destroy establishment from carousel."""

    lookup_field = 'slug'
    _model = models.Establishment
    serializer_class = serializers.EstablishmentCarouselCreateSerializer


class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIView):
    """Resource for getting list of nearest establishments."""

    serializer_class = serializers.EstablishmentGeoSerializer
    filter_class = filters.EstablishmentFilter

    def get_queryset(self):
        """Overridden method 'get_queryset'."""
        # todo: latitude and longitude
        lat = self.request.query_params.get('lat')
        lon = self.request.query_params.get('lon')
        radius = self.request.query_params.get('radius')
        unit = self.request.query_params.get('unit')

        qs = super(EstablishmentNearestRetrieveView, self).get_queryset()
        if lat and lon and radius and unit:
            center = Point(float(lon), float(lat))
            filter_kwargs = {'center': center, 'radius': float(radius), 'unit': unit}
            return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items()
                                                if v is not None})
        return qs
