from django.shortcuts import get_object_or_404
from drf_yasg.utils import swagger_auto_schema
from rest_framework import generics, permissions, status, serializers
from rest_framework.response import Response

from booking.models.models import Booking, GuestonlineService, LastableService
from booking.serializers.web import (PendingBookingSerializer, UpdateBookingSerializer, GetBookingSerializer,
                                     CheckBookingSerializer, CommitBookingSerializer)
from establishment.models import Establishment
from notification.models import Subscriber
from utils.methods import get_user_ip


class CheckWhetherBookingAvailable(generics.GenericAPIView):
    """ Checks which service to use if establishmend is managed by any """

    _VALID_GUESTONLINE_PERIODS = {'lunch', 'dinner', 'afternoon', 'breakfast'}
    _GUESTONLINE_PERIODS_TO_PRIOR = {
        'breakfast': 1,
        'lunch': 2,
        'afternoon': 3,
        'dinner': 4,
    }

    permission_classes = (permissions.AllowAny,)
    serializer_class = CheckBookingSerializer
    pagination_class = None

    def _fill_period_template(self, period_template, period_name):
        period_template_copy = period_template.copy()
        period_template_copy['period'] = period_name
        return period_template_copy

    def _preprocess_guestonline_response(self, response):
        periods = response['periods']
        periods_by_name = {period['period']: period for period in periods if 'period' in period}
        if not periods_by_name:
            return response

        period_template = iter(periods_by_name.values()).__next__().copy()
        period_template.pop('total_left_seats')
        period_template['hours'] = []
        period_template.pop('period')

        processed_periods = [
            periods_by_name[period_name]
            if period_name in periods_by_name
            else self._fill_period_template(period_template, period_name)
            for period_name in CheckWhetherBookingAvailable._VALID_GUESTONLINE_PERIODS
        ]

        unnamed_periods = filter(lambda period: 'period' not in period, periods)
        for unnamed_period in unnamed_periods:
            processed_periods.append(unnamed_period)

        response['periods'] = sorted(processed_periods,
                                     key=lambda x: self._GUESTONLINE_PERIODS_TO_PRIOR[x.get('period', 'lunch')])

        return response

    def get(self, request, *args, **kwargs):
        is_booking_available = False
        establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id'])
        service = None
        date = request.query_params.get('date')
        g_service = GuestonlineService()
        l_service = LastableService()
        if establishment.lastable_id is not None and l_service \
                .check_whether_booking_available(establishment.lastable_id, date):
            is_booking_available = True
            service = l_service
            service.service_id = establishment.lastable_id
        elif establishment.guestonline_id is not None and g_service \
                .check_whether_booking_available(establishment.guestonline_id,
                                                 **g_service.get_certain_keys(request.query_params,
                                                                              {'date', 'persons'})):
            is_booking_available = True
            service = g_service
            service.service_id = establishment.guestonline_id

        response = {
            'available': is_booking_available,
            'type': service.service if service else None,
        }

        service_response = self._preprocess_guestonline_response(service.response) \
            if establishment.guestonline_id is not None \
            else service.response if service else None
        response.update({'details': service_response})
        if service_response is None:
            response['available'] = False
        return Response(data=response, status=200)


class CreatePendingBooking(generics.CreateAPIView):
    """ Creates pending booking """
    permission_classes = (permissions.AllowAny,)
    serializer_class = PendingBookingSerializer

    @swagger_auto_schema(operation_description="Request body params\n\n"
                                               "IN GUESTONLINE (type:G): {"
                                               "'restaurant_id', 'booking_time', "
                                               "'booking_date', 'booked_persons_number'}\n"
                                               "IN LASTABLE (type:L): {'booking_time', "
                                               "'booked_persons_number', 'offer_id' (Req), "
                                               "'email', 'phone', 'first_name', 'last_name'}")
    def post(self, request, *args, **kwargs):
        data = request.data.copy()
        if data.get('type') == Booking.LASTABLE and data.get("offer_id") is None:
            raise serializers.ValidationError(detail='Offer_id is required field for Lastable service')
        establishment = get_object_or_404(Establishment, pk=kwargs['establishment_id'])
        data['restaurant_id'] = Booking.get_booking_id_by_type(establishment, data.get('type'))
        service = Booking.get_service_by_type(request.data.get('type'))
        data['user'] = request.user.pk if request.user else None
        service_to_keys = {
            Booking.GUESTONLINE: {'restaurant_id', 'booking_time', 'booking_date', 'booked_persons_number', },
            Booking.LASTABLE: {'booking_time', 'booked_persons_number', 'offer_id', 'email', 'phone',
                               'first_name', 'last_name', },
        }
        data['pending_booking_id'] = service.create_booking(
            service.get_certain_keys(data.copy(), service_to_keys[data.get('type')]))
        if isinstance(data['pending_booking_id'], Response):
            return data['pending_booking_id']
        elif not data['pending_booking_id']:
            return Response(status=status.HTTP_403_FORBIDDEN, data='Unable to create booking')
        data['booking_id'] = data['pending_booking_id'] if data.get('type') == Booking.LASTABLE else None
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(status=status.HTTP_201_CREATED, data=serializer.data)


class CommitPendingBooking(generics.UpdateAPIView):
    """ Commit pending booking """
    queryset = Booking.objects.all()
    permission_classes = (permissions.AllowAny,)
    serializer_class = CommitBookingSerializer


class UpdatePendingBooking(generics.UpdateAPIView):
    """ Update pending booking with contacts """
    queryset = Booking.objects.all()
    permission_classes = (permissions.AllowAny,)
    serializer_class = UpdateBookingSerializer

    @swagger_auto_schema(operation_description="Request body params\n\n"
                                               "Required: 'email', 'phone', 'last_name', "
                                               "'first_name', 'country_code', 'pending_booking_id',"
                                               "Not req: 'note'")
    def patch(self, request, *args, **kwargs):
        instance = self.get_object()
        data = request.data.copy()
        service = Booking.get_service_by_type(instance.type)
        data['pending_booking_id'] = instance.pending_booking_id
        r = service.update_booking(service.get_certain_keys(data, {
            'email', 'phone', 'last_name', 'first_name', 'country_code', 'pending_booking_id', 'note',
        }, {
                                                                'email', 'phone', 'last_name', 'first_name',
                                                                'country_code', 'pending_booking_id',
                                                            }))
        if isinstance(r, Response):
            return r
        if data.get('newsletter'):
            Subscriber.objects.make_subscriber(email=data['email'], country_code=data['country_code'],
                                               locale=request.locale, ip_address=get_user_ip(request),
                                               user=None if request.user.is_anonymous else request.user)
        if service.response:
            # если есть предоплата, возвращаем фронту страйп-ключ для совершения оплаты и цену
            amount = service.response.get('amount')
            stripe_key = service.response.get('stripe_key')
            data = {
                'id': instance.pk,
                'amount': amount,
                'stripe_key': stripe_key,
                'type': instance.type,
            }
            serializer = self.get_serializer(data=data)
            serializer.is_valid(raise_exception=True)
            serializer.update(instance, data)
            return Response(status=status.HTTP_200_OK, data=data)
        service.commit_booking(data['pending_booking_id'])
        data = {
            'booking_id': service.response.get('id'),
            'id': instance.pk,
        }
        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        serializer.update(instance, data)
        return Response(status=status.HTTP_200_OK, data=serializer.data)


class CancelBooking(generics.DestroyAPIView):
    """ Cancel existing booking """
    queryset = Booking.objects.all()
    permission_classes = (permissions.AllowAny,)


class LastBooking(generics.RetrieveAPIView):
    """ Get last booking by user credentials """
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = GetBookingSerializer
    lookup_field = None

    def get_object(self):
        return Booking.objects.by_user(self.request.user).latest('modified')


class GetBookingById(generics.RetrieveAPIView):
    """ Returns booking by its id"""
    permission_classes = (permissions.AllowAny,)
    serializer_class = GetBookingSerializer
    queryset = Booking.objects.all()
