import json
from abc import ABC, abstractmethod

import requests
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers, status
from rest_framework.response import Response

import booking.models.models as models


class AbstractBookingService(ABC):
    """ Abstract class for Guestonline && Lastable booking services"""

    def __init__(self, service):
        self.service = None
        self.response = None
        if service not in [models.Booking.LASTABLE, models.Booking.GUESTONLINE]:
            raise Exception('Service %s is not implemented yet' % service)
        self.service = service
        if service == models.Booking.GUESTONLINE:
            self.token = settings.GUESTONLINE_TOKEN
            self.url = settings.GUESTONLINE_SERVICE
        elif service == models.Booking.LASTABLE:
            self.token = settings.LASTABLE_TOKEN
            self.url = settings.LASTABLE_SERVICE

    @staticmethod
    def get_certain_keys(d: dict, keys_to_preserve: set, required: set = {}, check: bool = True) -> dict:
        """ Helper """
        if len(required) == 0 and check:
            required = keys_to_preserve.copy()
        if required and check:
            diff = required - d.keys()
            if diff:
                raise serializers.ValidationError({field: _(f'This field is required') for field in diff})
        return {key: d[key] for key in d.keys() & keys_to_preserve}

    @abstractmethod
    def check_whether_booking_available(self, restaurant_id, date):
        """ checks whether booking is available """
        if date is None:
            raise serializers.ValidationError(detail='date query param is required')

    @abstractmethod
    def cancel_booking(self, payload):
        """ cancels booking and returns the result """
        pass

    @abstractmethod
    def create_booking(self, payload):
        """ returns pending booking id if created. otherwise False """
        pass

    @abstractmethod
    def update_booking(self, payload):
        """ updates pending booking with contacts """
        pass

    @abstractmethod
    def get_common_headers(self):
        pass

    @abstractmethod
    def get_booking_details(self, payload):
        """ fetches booking details from external service """
        pass


class GuestonlineService(AbstractBookingService):
    def __init__(self):
        super().__init__(models.Booking.GUESTONLINE)

    def get_common_headers(self):
        return {'X-Token': self.token, 'Content-type': 'application/json', 'Accept': 'application/json'}

    def check_whether_booking_available(self, restaurant_id, *args, **kwargs):
        url = f'{self.url}v1/periods'
        params = {'restaurant_id': restaurant_id, **kwargs}
        r = requests.get(url, headers=self.get_common_headers(), params=params)
        if not status.is_success(r.status_code):
            return False
        self.response = r.json()
        return True

    def commit_booking(self, payload, stripe_token = None):
        url = f'{self.url}v1/pending_bookings/{payload}/commit'
        if stripe_token:
            r = requests.put(url, headers=self.get_common_headers(),
                             data=json.dumps({'stripe_token': stripe_token}))
        else:
            r = requests.put(url, headers=self.get_common_headers())
        self.response = json.loads(r.content)
        if status.is_success(r.status_code) and self.response is None:
            raise serializers.ValidationError(detail='Booking already committed.')
        return status.is_success(r.status_code)

    def update_booking(self, payload):
        booking_id = payload.pop('pending_booking_id')
        url = f'{self.url}v1/pending_bookings/{booking_id}'
        payload['lastname'] = payload.pop('last_name')
        payload['firstname'] = payload.pop('first_name')
        payload['mobile_phone'] = payload.pop('phone')
        payload['user_locale'] = payload.pop('country_code')
        headers = self.get_common_headers()
        r = requests.put(url, headers=headers, data=json.dumps({'contact_info': payload}))
        response = r.json()
        self.response = response.get('prepayment')
        if not status.is_success(r.status_code):
            return Response(status=r.status_code, data=response)
        return status.is_success(r.status_code)

    def create_booking(self, payload):
        url = f'{self.url}v1/pending_bookings'
        payload['hour'] = payload.pop('booking_time')
        payload['persons'] = payload.pop('booked_persons_number')
        payload['date'] = payload.pop('booking_date')
        r = requests.post(url, headers=self.get_common_headers(), data=json.dumps(payload))
        if status.is_success(r.status_code):
            return json.loads(r.content)['id']
        else:
            return Response(status=r.status_code, data=r.json())

    def cancel_booking(self, payload):
        url = f'{self.url}v1/pending_bookings/{payload}'
        r = requests.delete(url, headers=self.get_common_headers())
        return status.is_success(r.status_code)

    def get_booking_details(self, payload):
        url = f'{self.url}v1/bookings/{payload}'
        r = requests.get(url, headers=self.get_common_headers())
        return json.loads(r.content)


class LastableService(AbstractBookingService):
    def __init__(self):
        super().__init__(models.Booking.LASTABLE)
        self.proxies = {
            'http': settings.LASTABLE_PROXY,
            'https': settings.LASTABLE_PROXY,
        }

    def create_booking(self, payload):
        url = f'{self.url}v1/partner/orders'
        payload['places'] = payload.pop('booked_persons_number')
        payload['hour'] = payload.pop('booking_time')
        payload['firstName'] = payload.pop('first_name')
        payload['lastName'] = payload.pop('last_name')
        r = requests.post(url, headers=self.get_common_headers(), proxies=self.proxies, data=json.dumps(payload))
        return json.loads(r.content)['data']['_id'] if status.is_success(r.status_code) else False

    def get_common_headers(self):
        return {'Authorization': f'Bearer {self.token}', 'Content-type': 'application/json',
                'Accept': 'application/json'}

    def check_whether_booking_available(self, restaurant_id, date):
        super().check_whether_booking_available(restaurant_id, date)
        url = f'{self.url}v1/restaurant/{restaurant_id}/offers'
        r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies)
        response = json.loads(r.content).get('data')
        if not status.is_success(r.status_code) or not response:
            return False
        self.response = response
        return True

    def commit_booking(self, payload):
        """ Lastable service has no pending booking to commit """
        return False

    def update_booking(self, payload):
        """ Lastable service has no pending booking to update """
        return False

    def cancel_booking(self, payload):
        url = f'{self.url}v1/partner/orders/{payload}'
        r = requests.delete(url, headers=self.get_common_headers(), proxies=self.proxies)
        return status.is_success(r.status_code)

    def get_booking_details(self, payload):
        url = f'{self.url}v1/partner/orders/{payload}'
        r = requests.get(url, headers=self.get_common_headers(), proxies=self.proxies)
        return json.loads(r.content)
