# Copyright (C) 2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import hashlib
from urllib import parse as urlparse

from django.core.cache import cache
from django.db import models
from django.utils.translation import gettext_lazy as _

from passerelle.base.models import BaseResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError

EVENTBOOK_SCHEMA = {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Vivaticket",
    "description": "",
    "type": "object",
    "required": [
        "id",
        "email",
        "start_datetime",
        "end_datetime",
        "event",
        "theme",
        "room",
        "quantity",
        "form_url",
    ],
    "properties": {
        "id": {
            "description": "formdata id",
            "type": "string",
        },
        "title": {
            "description": "user title",
            "type": "string",
        },
        "last_name": {
            "description": "user last name",
            "type": "string",
        },
        "first_name": {
            "description": "user first name",
            "type": "string",
        },
        "social_reason": {
            "description": "user social reason",
            "type": "string",
        },
        "address": {
            "description": "user address",
            "type": "string",
        },
        "zipcode": {
            "description": "user zipcode",
            "type": "string",
        },
        "city": {
            "description": "user city",
            "type": "string",
        },
        "country": {
            "description": "user country",
            "type": "string",
        },
        "phone": {
            "description": "user phone",
            "type": "string",
        },
        "mobile": {
            "description": "user mobile",
            "type": "string",
        },
        "email": {
            "description": "user email",
            "type": "string",
        },
        "start_datetime": {
            "description": "event start datetime",
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}$",
        },
        "end_datetime": {
            "description": "event end datetime",
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}$",
        },
        "event": {
            "description": "event id",
            "type": "string",
        },
        "theme": {
            "description": "theme id",
            "type": "string",
        },
        "room": {
            "description": "room id",
            "type": "string",
        },
        "quantity": {"description": "quantity", "type": "string", "pattern": "^[0-9]+$"},
        "booking_comment": {
            "description": "booking comment",
            "type": "string",
        },
        "room_comment": {
            "description": "room comment",
            "type": "string",
        },
        "form_url": {
            "description": "form url",
            "type": "string",
        },
        "school_level": {
            "description": "School Level code",
            "type": "string",
        },
    },
}


class VivaTicket(BaseResource):
    url = models.URLField(_('API URL'))
    login = models.CharField(_('API Login'), max_length=256)
    password = models.CharField(_('API Password'), max_length=256)

    category = _('Business Process Connectors')

    log_requests_errors = False

    class Meta:
        verbose_name = 'VivaTicket'

    @classmethod
    def get_verbose_name(cls):
        return cls._meta.verbose_name

    def check_status(self):
        response = self.requests.get(urlparse.urljoin(self.url, 'Settings/GetVersion'))
        response.raise_for_status()

    def get_apikey(self, renew=False):
        cache_key_name = 'vivaticket-%s-key' % self.id
        if not renew and cache.get(cache_key_name):
            return cache.get(cache_key_name)
        url = urlparse.urljoin(self.url, 'Connect/PostConnect')
        payload = {'Login': self.login, 'Password': self.password}
        response = self.requests.post(url, json=payload)
        if not response.ok:
            raise APIError(response.content)
        api_key = response.json()['Key']
        # api key is available for 30 minutes
        cache.set(cache_key_name, api_key, 60 * 30)
        return api_key

    def get(self, endpoint, **kwargs):
        url = urlparse.urljoin(self.url, endpoint)
        params = {'key': self.get_apikey()}
        params.update(kwargs)
        response = self.requests.get(url, params=params)
        # api key is expired
        if response.status_code == 401:
            params['key'] = self.get_apikey(True)
        else:
            return response
        return self.requests.get(url, params=params)

    def post(self, endpoint, payload, headers=None):
        url = urlparse.urljoin(self.url, endpoint)
        payload.update({'Key': self.get_apikey()})
        response = self.requests.post(url, json=payload, headers=headers)
        # api key is expired
        if response.status_code == 401:
            payload['key'] = self.get_apikey(True)
            return self.requests.post(url, json=payload, headers=headers)
        return response

    def get_list_of_settings(self, endpoint, **kwargs):
        response = self.get(endpoint, **kwargs)
        json = response.json()
        data = []
        for setting in json.get("ListOfSettings", []):
            data.append({'id': setting['Code'], 'text': setting['Label']})
        return {'data': data}

    @endpoint(perm='can_access', methods=['get'], description=_('Get event categories'))
    def events(self, request):
        return self.get_list_of_settings('Settings/GetEventCategory')

    @endpoint(perm='can_access', methods=['get'], description=_('Get rooms'))
    def rooms(self, request, event=None):
        query = {}
        if event is not None:
            query['eventCategory'] = event
        return self.get_list_of_settings('Settings/GetRooms', **query)

    @endpoint(perm='can_access', methods=['get'], description=_('Get themes'))
    def themes(self, request, room=None):
        query = {}
        if room is not None:
            query['room'] = room
        return self.get_list_of_settings('Settings/GetThemes', **query)

    @endpoint(name='school-levels', perm='can_access', methods=['get'], description=_('Get school levels'))
    def school_levels(self, request):
        return self.get_list_of_settings('Settings/GetSchoolLevel')

    def get_or_create_contact(self, data, name_id=None):
        contact_payload = {
            'Civility': data.get('title', ''),
            'LastName': data.get('last_name', ''),
            'FirstName': data.get('first_name', ''),
            'SocialReason': data.get('social_reason', ''),
            'Address1': data.get('address', ''),
            'ZipCode': data.get('zipcode', ''),
            'City': data.get('city', ''),
            'Country': data.get('country', ''),
            'Email': data['email'],
            'Phone': data.get('phone', ''),
            'Mobile': data.get('mobile', ''),
        }
        if name_id is not None:
            unhashed_external_code = name_id
        else:
            unhashed_external_code = data['email']
        external_code = hashlib.md5(unhashed_external_code.encode('utf-8')).hexdigest()[:20]
        contact_payload['ExternalCode'] = external_code
        response = self.get('Contact/Get', externalCode=external_code, email=data['email'])
        self.logger.debug('Got contact response: %r', response.text)
        if not response.ok:
            response = self.post('Contact/Post', {'Contact': contact_payload})
            self.logger.debug('Contact creation response: %r', response.text)
            response.raise_for_status()
            internal_code = response.json()['InternalCode']
        else:
            internal_code = response.json()['InternalCode']
            # update contact data
            url = urlparse.urljoin(self.url, 'Contact/Put')
            response = self.requests.put(
                url,
                params={'id': response.json()['InternalCode']},
                json={'Key': self.get_apikey(), 'Contact': contact_payload},
            )
        return {'InternalCode': internal_code}

    @endpoint(
        perm='can_access',
        description=_('Book an event'),
        post={
            'description': _('Creates a booking for an event'),
            'request_body': {'schema': {'application/json': EVENTBOOK_SCHEMA}},
        },
    )
    def book(self, request, post_data, nameid=None):
        booking = {
            'externalCode': post_data['id'],
            'startDateTime': post_data['start_datetime'],
            'endDateTime': post_data['end_datetime'],
            'comment': post_data.get('booking_comment', ''),
            'contact': self.get_or_create_contact(post_data, nameid),
            'roomList': [
                {
                    'eventCategoryCode': post_data['event'],
                    'roomCode': post_data['room'],
                    'themeCode': post_data['theme'],
                    'quantity': int(post_data['quantity']),
                    'startDateTime': post_data['start_datetime'],
                    'endDateTime': post_data['end_datetime'],
                    'comment': post_data.get('room_comment', ''),
                    'schoolLevelCode': post_data.get('school_level', ''),
                }
            ],
        }
        headers = {'X-Vivaticket-Form-URL': post_data['form_url']}
        r = self.post('Booking/Post', {'Booking': booking}, headers=headers)
        self.logger.debug('Book response: %r' % r.text)
        if not r.ok:
            raise APIError(r.text)
        return {'data': r.json()}
