# Passerelle - uniform access to data and services
# Copyright (C) 2015-2020  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 xml.etree.ElementTree as ET

try:
    import phpserialize
except ImportError:
    phpserialize = None

from django.db import models
from django.utils.encoding import force_str
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


def deep_bytes2str(obj):
    if obj is None or isinstance(obj, (int, str)):
        return obj
    if isinstance(obj, bytes):
        try:
            return obj.decode('utf-8')
        except UnicodeDecodeError:
            return obj
    if isinstance(obj, list):
        return [deep_bytes2str(x) for x in obj]
    if isinstance(obj, dict):
        new_d = {}
        for k, v in obj.items():
            new_d[force_str(k)] = deep_bytes2str(v)
        return new_d
    return obj


def phpserialize_loads(s):
    try:
        return deep_bytes2str(phpserialize.loads(s.encode('utf-8')))
    except ValueError:
        truncated = s[:128] if isinstance(s, str) else s
        raise APIError(f'Could not deserialize GDC response {truncated!r}', data={'content': s})


class Gdc(BaseResource):
    service_url = models.CharField(
        max_length=128, blank=False, verbose_name=_('Service URL'), help_text=_('GDC Web Service URL')
    )

    category = _('Business Process Connectors')

    class Meta:
        verbose_name = _('GDC Web Service')

    def call_soap(self, action, *args, **kwargs):
        def escape(s):
            return force_str(s).replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;')

        params = []
        for i, arg in enumerate(args):
            params.append(
                '<v%(i)s xsi:type="xsd:string">%(value)s</v%(i)s>' % {'i': i + 1, 'value': escape(arg)}
            )
        for key, value in kwargs.items():
            if value is None:
                params.append('<%s xsi:null="1"/>' % key)
                continue
            type_ = 'int' if isinstance(value, int) else 'string'
            params.append(
                '<%(key)s xsi:type="xsd:%(type)s">%(value)s</%(key)s>'
                % {'key': key, 'type': type_, 'value': escape(value)}
            )

        data = """<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
>
<SOAP-ENV:Body>
<%(action)s SOAP-ENC:root="1">
%(params)s
</%(action)s>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>""" % {
            'action': action,
            'params': '\n'.join(params),
        }
        resp = self.requests.post(
            self.service_url,
            data=data.encode('utf-8'),
            headers={'SOAPAction': '"%s"' % action, 'Content-type': 'text/xml; charset=UTF-8'},
        )

        return ET.ElementTree(ET.fromstring(resp.content))

    @endpoint()
    def communes(self, request, *args, **kwargs):
        resp = self.call_soap('getListeCommune')
        soap_result = phpserialize_loads(resp.findall('.//listeCommune')[0].text)
        result = []
        for k, v in soap_result.items():
            result.append({'id': k, 'text': force_str(v, 'utf-8')})
        result.sort(key=lambda x: x['id'])
        return result

    @endpoint()
    def objets(self, request, *args, **kwargs):
        resp = self.call_soap('getListeObjet')
        soap_result = phpserialize_loads(resp.findall('.//listeObjet')[0].text)
        result = []
        for k, v in soap_result.items():
            result.append({'id': k, 'text': force_str(v, 'utf-8')})
        result.sort(key=lambda x: x['id'])
        return result
