# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2016  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/>.

from datetime import datetime

import lxml.etree
import pkg_resources
from django.db import models
from django.utils import dateformat
from django.utils.translation import gettext_lazy as _

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

TYPE_AGENDA = {"DECHET": "DECHETS VERTS", "ENCOMBRANT": "ENCOMBRANTS"}
C_TYPEPB = {"DECHET": "8006", "ENCOMBRANT": "8008"}
SYNDIC_C_TYPEPB = {"DECHET": "8007", "ENCOMBRANT": "8009"}

NS = '{http://isilog.fr}'

BOOKDATE_SCHEMA = {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "IWS",
    "description": "",
    "type": "object",
    "required": ["firstname", "lastname", "email_notif", "date", "token"],
    "properties": {
        "firstname": {
            "description": "Firstname",
            "type": "string",
        },
        "lastname": {
            "description": "Lastname",
            "type": "string",
        },
        "email": {
            "description": "Email",
            "type": "string",
        },
        "email_notif": {
            "description": "Email notification",
            "type": "boolean",
        },
        "description": {
            "description": "Description of the request",
            "type": "string",
        },
        "tel_number": {
            "description": "Telephone number",
            "type": "string",
        },
        "date": {
            "description": "Booking date",
            "type": "string",
        },
        "token": {
            "description": "Booking token",
            "type": "string",
        },
        "sms": {
            "description": "Send sms to user before the booked date",
        },
    },
}


class IWSConnector(BaseResource):
    wsdl_url = models.URLField(
        max_length=400, verbose_name=_('SOAP wsdl endpoint'), help_text=_('URL of the SOAP wsdl endpoint')
    )
    operation_endpoint = models.URLField(
        max_length=400,
        verbose_name=_('SOAP operation endpoint'),
        help_text=_('URL of SOAP operation endpoint'),
    )
    username = models.CharField(max_length=128, verbose_name=_('Service username'))
    password = models.CharField(max_length=128, verbose_name=_('Service password'), null=True, blank=True)
    database = models.CharField(max_length=128, verbose_name=_('Service database'))
    category = _('Business Process Connectors')

    class Meta:
        verbose_name = _('IWS connector')

    def _soap_call(self, iws_data, method):
        client = self.soap_client()
        header = client.get_element('%sIsiWsAuthHeader' % NS)
        header_value = header(IsiLogin=self.username, IsiPassword=self.password, IsiDataBaseID=self.database)
        client.set_default_soapheaders([header_value])
        service = client.create_service('%sIsiHelpDeskServiceSoap' % NS, self.operation_endpoint)

        self.logger.debug("calling %s method of iws", method, extra={'data': iws_data})
        IsiWsEntity = client.get_type('%sIsiWsEntity' % NS)
        ArrayOfIsiWsDataField = client.get_type('%sArrayOfIsiWsDataField' % NS)
        IsiWsDataField = client.get_type('%sIsiWsDataField' % NS)
        ll = []
        for field, value in iws_data.items():
            ll.append(IsiWsDataField(IsiField=field, IsiValue=value))

        soap_list = ArrayOfIsiWsDataField(ll)
        ws_entity = IsiWsEntity(IsiFields=soap_list)
        iws_res = getattr(service, method)(ws_entity)

        schema_root = lxml.etree.parse(
            pkg_resources.resource_stream('passerelle.contrib.iws', 'xsd/ReponseWS.xsd')
        )
        schema = lxml.etree.XMLSchema(schema_root)
        parser = lxml.etree.XMLParser(schema=schema, encoding='utf-8')
        try:
            tree = lxml.etree.fromstring(iws_res.encode('utf-8'), parser).getroottree()
        except lxml.etree.XMLSyntaxError:
            raise APIError("IWS response is not valid")
        result = {"status": tree.find('//Statut').text, "trace": tree.find('//Trace').text}
        fields = {}
        for data_field in tree.xpath('//IsiWsDataField'):
            fields[data_field.find('IsiField').text] = data_field.find('IsiValue').text
        result['fields'] = fields
        self.logger.debug("recieved data from %s method of iws", method, extra={'data': result})
        return result

    def _check_status(self, iws_res):
        if iws_res['status'] != 'responseOk':
            raise APIError('iws error, status: "%(status)s", trace: "%(trace)s"' % iws_res)

    @endpoint(
        methods=['get'],
        perm='can_access',
        example_pattern='{sti_code}/{request_type}/{volume}/',
        pattern=r'^(?P<sti_code>[0-9]{16}.?)/(?P<request_type>\w+)/(?P<volume>[0-9]+)/$',
        parameters={
            'sti_code': {'description': _('Address STI code'), 'example_value': '3155570464130003'},
            'request_type': {'description': _('DECHET or ENCOMBRANT'), 'example_value': 'DECHET'},
            'volume': {'description': _('Volume of waste'), 'example_value': '1'},
            'city': {'description': _('City'), 'example_value': 'TOULOUSE'},
            'session_id': {'description': _('Session identifier'), 'example_value': '7a896f464ede7b4e'},
            'syndic': {
                'description': _('Syndic'),
                'example_value': 'true',
                'type': 'bool',
            },
        },
        cache_duration=120,
    )
    def checkdate(self, request, sti_code, request_type, volume, city, session_id, syndic=False):
        if request_type not in ('DECHET', 'ENCOMBRANT'):
            raise APIError("request_type should be 'DECHET' or 'ENCOMBRANT'")
        iws_data = {
            'C_ORIGINE': 'TELESERVICE',
            'I_APP_TYPEDEM': request_type + 'SYNDIC' if syndic else request_type,
            'I_AG_TYPEAGENDA': TYPE_AGENDA[request_type],
            'I_AP_QTEAGENDA': volume,
            'C_STAPPEL': 'B',
            'C_NATURE': 'INC',
            'DE_SYMPAPPEL': 'booking description',
            'I_AP_COMMUNE': city,
            'I_AP_COMMUNEINTER': sti_code,
            'J_PRJACTPREV': '5',
            'C_EQUIPE': "VPVIGIE",
            'I_APP_DEMANDEUR': 'booking, demandeur',
            'I_AP_ADRESSEMAIL': 'booking@localhost',
            'C_TYPEPB': SYNDIC_C_TYPEPB[request_type] if syndic else C_TYPEPB[request_type],
        }
        iws_res = self._soap_call(iws_data, 'IsiAddAndGetCall')
        self._check_status(iws_res)
        iws_fields = iws_res['fields']
        token = iws_fields['NO_APPEL']
        if not token:
            raise APIError('iws error, missing token')
        dates = []
        result = {'data': dates}
        iws_dates = iws_fields['I_APP_DATESPOSSIBLES']
        if iws_dates == 'Aucune dates disponibles':
            return result
        for raw_date in iws_dates.split(';'):
            if raw_date:
                raw_date = raw_date.strip()
                date_obj = datetime.strptime(raw_date, '%d/%m/%Y').date()
                date_text = dateformat.format(date_obj, 'l d F Y')
                dates.append({"id": raw_date, "text": date_text, "token": token})
        return result

    @endpoint(
        perm='can_access',
        post={
            'description': _('Book date'),
            'request_body': {'schema': {'application/json': BOOKDATE_SCHEMA}},
            'input_example': {
                'firstname': 'jon',
                'lastname': 'doe',
                'email': 'jon.doe@jondoe.com',
                'description': 'a refrigerator',
                'tel_number': '0102030405',
                'date': '26/10/2018',
                'token': 'XBNDNFT34',
            },
        },
    )
    def bookdate(self, request, post_data):
        data = post_data
        iws_data = {
            'NO_APPEL': data['token'],
            'I_APP_DEMANDEUR': '%s, %s' % (data['lastname'], data['firstname']),
            'I_AP_DATRDVAGENDA': data['date'],
            'C_STAPPEL': 'E',
            'I_AP_SERVICE': 'OFFICE',
            'I_AP_SOURCE': 'USAGER',
            'C_ORIGINE': 'TELESERVICE',
            'C_BLOCAGE': 2,
            'I_AP_EMAIL': 'NON',
            'I_AP_ADRESSEMAIL': '',
            'I_AP_CNIL': 1,
            'I_AP_SMS': 'NON',
            'I_AP_TEL_DEMANDEU': '',
            'DE_SYMPAPPEL': data['description'] or '',
            'C_QUALIFICATIF': 'INTERVENTION',
        }
        if 'email' in data:
            iws_data['I_AP_ADRESSEMAIL'] = data['email']
        iws_data['I_AP_EMAIL'] = 'OUI' if data['email_notif'] else 'NON'
        if 'tel_number' in data:
            iws_data['I_AP_TEL_DEMANDEU'] = data['tel_number']
        if 'sms' in data:
            if any2bool(data['sms'], default=False):
                iws_data['I_AP_SMS'] = 'OUI'
            else:
                iws_data['I_AP_SMS'] = 'NON'
        iws_res = self._soap_call(iws_data, 'IsiUpdateAndGetCall')
        self._check_status(iws_res)
        return {'data': iws_res['fields']}
