# passerelle.contrib.teamnet_axel
# Copyright (C) 2015  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 logging
import base64
import xml.etree.ElementTree as ET
from datetime import datetime

from django.core.urlresolvers import reverse
from django.db import models
from django.utils.translation import ugettext_lazy as _

from jsonfield import JSONField

from passerelle.base.models import BaseResource

from . import soap
from .utils import normalize_person, normalize_invoice

logger = logging.getLogger('passerelle.contrib.teamnet_axel')

ADULT1 = '1'
ADULT2 = '2'
CHILD = '3'

DATE_IN_FORMAT = '%Y-%m-%dT%H:%M:%S'
DATE_OUT_FORMAT = '%d/%m/%Y %H:%M:%S'


class AxelException(Exception):
    pass


class Management(BaseResource):
    wsdl_url = models.CharField(max_length=128, blank=False,
            verbose_name=_('WSDL URL'),
            help_text=_('Teamnet Axel WSDL URL'))
    verify_cert = models.BooleanField(default=True,
            verbose_name=_('Check HTTPS Certificate validity'))
    username = models.CharField(max_length=128, blank=True,
            verbose_name=_('Username'))
    password = models.CharField(max_length=128, blank=True,
            verbose_name=_('Password'))
    keystore = models.FileField(upload_to='teamnet_axel', null=True, blank=True,
            verbose_name=_('Keystore'),
            help_text=_('Certificate and private key in PEM format'))

    billing_regies = JSONField(_('Mapping between regie ids and billing ids'))

    category = _('Business Process Connectors')

    class Meta:
        verbose_name = _('Teamnet Axel')

    @classmethod
    def get_icon_class(cls):
        return 'ressources'

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

    #
    # Axel SOAP call: getData
    #
    def get_data(self, operation, args):
        # args is a XML node (ElementTree.Element)
        portail = ET.Element('PORTAIL')
        portail.append(args)
        streamId = operation
        xmlParams = ET.tostring(portail, encoding='UTF-8')
        user = ''
        logger.debug('getData(streamId=%s, xmlParams=%s, user=%s)',
                streamId, xmlParams, user)
        result = soap.get_client(self).service.getData(streamId, xmlParams, user)
        logger.debug('getData(%s) result: %s', streamId, result)
        xml_result = ET.fromstring(result)
        if xml_result.find('RESULTAT/STATUS').text != 'OK':
            msg = xml_result.find('RESULTAT/COMMENTAIRES').text
            raise AxelException(msg)
        return xml_result.find('DATA')

    # Axel authentication
    def auth(self, login, pwd):
        '''return False or an AXEL user dict:
          {
              "login": "23060A",
              "estidentifie": true,
              "estbloque": false,
              "estchangement_mdp_requis": false,
              "nbechec": "0",
              "idpersonne": "47747",
              "idfamille": "23060",
          }
        '''
        xml_utilisateur = ET.Element('UTILISATEUR')
        ET.SubElement(xml_utilisateur, 'LOGIN').text = login
        ET.SubElement(xml_utilisateur, 'PWD').text = pwd
        try:
            data = self.get_data('ConnexionCompteFamille', xml_utilisateur)
        except AxelException:
            return False
        data = data.find('PORTAIL/UTILISATEUR')
        data = soap.xml_to_dict(data)
        for key, value in data.items():
            if key.startswith('est'):
                data[key] = value == 'true'
        if data.get('estbloque'):
            return False
        if not data.get('estidentifie'):
            return False
        return data

    def get_famille(self, idfamille, annee=None):
        xml_famille = ET.Element('FAMILLE')
        ET.SubElement(xml_famille, 'IDFAMILLE').text = idfamille
        if annee:
            ET.SubElement(xml_famille, 'ANNEE').text = annee
        data = self.get_data('DonneesFamille', xml_famille)
        xml_individus = data.findall('PORTAIL/INDIVIDUS')
        if not xml_individus:
            raise AxelException('PORTAIL/INDIVIDUS is empty')
        individus = [dict((k.lower(), v) for k, v in i.attrib.items()) for i in xml_individus]
        for individu in individus:
            individu['id'] = individu['idindividu']
            individu['text'] = '%(prenom)s %(nom)s' % individu
        adults = [normalize_person(i) for i in individus if i['indtype'] in (ADULT1, ADULT2)]
        children = [normalize_person(i) for i in individus if i['indtype'] == CHILD]
        return {
            'family': idfamille,
            'adults': adults,
            'children': children,
        }

    def get_teamnet_payable_invoices(self, regie_id, family_id):
        operation = 'FacturesApayerRegie'
        xml_invoices = ET.Element('LISTFACTURE')
        ET.SubElement(xml_invoices, 'IDREGIE').text = regie_id
        ET.SubElement(xml_invoices, 'IDFAMILLE').text = family_id
        data = self.get_data(operation, xml_invoices)
        xml_invoices = data.findall('PORTAIL/FACTURES')
        payable_invoices = {}
        if xml_invoices:
            for i in xml_invoices:
                payable_invoices.update(normalize_invoice(i.attrib, family_id))
        return payable_invoices

    def get_teamnet_historical_invoices(self, regie_id, family_id):
        """
        returns historical invoices for a given regie.
        The list contains also payable invoices.
        """
        operation = 'HistoriqueFacturesRegie'
        xml_invoices = ET.Element('LISTFACTURE')
        ET.SubElement(xml_invoices, 'IDREGIE').text = regie_id
        ET.SubElement(xml_invoices, 'IDFAMILLE').text = family_id
        ET.SubElement(xml_invoices, 'NBMOIS').text = '12'
        data = self.get_data(operation, xml_invoices)
        xml_invoices = data.findall('PORTAIL/FACTURES')
        historical_invoices = {}
        if xml_invoices:
            for i in xml_invoices:
                historical_invoices.update(normalize_invoice(i.attrib, family_id, historical=True))
        return historical_invoices

    def get_payable_invoices(self, regie_id, family_id):
        """
        returns payable invoices list sorted by creation date
        """
        if not family_id:
            return []
        invoices = self.get_teamnet_payable_invoices(regie_id, family_id)
        return sorted([p for i, p in invoices.iteritems()], key=lambda i: i['created'], reverse=True)

    def get_historical_invoices(self, regie_id, family_id):
        """
        Removes the invoices from get_teamnet_historical_invoices which are in
        get_teamnet_payable_invoices, as the Teamnet webservices mixes them.

        Returns historical invoices list sorted by creation date.
        """
        if not family_id:
            return []
        payable = self.get_teamnet_payable_invoices(regie_id, family_id)
        historical = self.get_teamnet_historical_invoices(regie_id, family_id)
        historical = [v for i, v in historical.iteritems() if i not in payable]
        return sorted(historical, key=lambda i: i['created'], reverse=True)

    def get_invoice(self, regie_id, invoice_id):
        family_id, i = invoice_id.split('-', 1)
        payable = self.get_teamnet_payable_invoices(regie_id, family_id)
        if invoice_id in payable:
            return payable[invoice_id]
        historical = self.get_teamnet_historical_invoices(regie_id, family_id)
        if invoice_id in historical:
            return historical[invoice_id]

    def get_invoice_pdf(self, invoice_id):
        family_id, invoice = invoice_id.split('-', 1)
        invoice_xml = ET.Element('FACTUREPDF')
        ET.SubElement(invoice_xml, 'IDFAMILLE').text = family_id
        ET.SubElement(ET.SubElement(invoice_xml, 'FACTURES'), 'NOFACTURE').text = invoice
        data = self.get_data('FacturesPDF', invoice_xml)
        pdf = data.find('PORTAIL/PDF')
        return base64.b64decode(pdf.get('FILE'))


    def pay_invoice(self, regie_id, invoice_id, transaction_id,
                    transaction_date, email=None):
        family_id, invoice = invoice_id.split('-', 1)
        payable_invoices = self.get_teamnet_payable_invoices(regie_id, family_id)

        if invoice_id not in payable_invoices:
            return False

        invoice_to_pay = payable_invoices[invoice_id]
        t_date = datetime.strptime(transaction_date, DATE_IN_FORMAT)

        payment_xml = ET.Element('PAIEMENT')
        ET.SubElement(payment_xml, 'IDDEMANDE')
        ET.SubElement(payment_xml, 'IDFAMILLE').text = family_id
        ET.SubElement(payment_xml, 'IDREGIEENC').text = self.billing_regies.get(regie_id)
        ET.SubElement(payment_xml, 'MODEREGLEMENT').text = 'PAY'
        ET.SubElement(payment_xml, 'MONTANT').text = str(invoice_to_pay['amount'])
        ET.SubElement(payment_xml, 'URL')
        if email:
            ET.SubElement(payment_xml, 'COURRIEL').text = email
        else:
            ET.SubElement(payment_xml, 'COURRIEL')
        ET.SubElement(payment_xml, 'REFPAIEMENT').text = transaction_id
        ET.SubElement(payment_xml, 'DATEENVOI').text = t_date.strftime(DATE_OUT_FORMAT)
        ET.SubElement(payment_xml, 'DATERETOUR').text = t_date.strftime(DATE_OUT_FORMAT)
        ET.SubElement(payment_xml, 'CODERETOUR')
        ET.SubElement(ET.SubElement(payment_xml, 'FACTURE'), 'NOFACTURE').text = invoice
        data = self.get_data('PaiementFactures', payment_xml)
        return data.text


class Link(models.Model):
    resource = models.ForeignKey(Management)
    nameid = models.CharField(blank=False, max_length=256)
    login = models.CharField(blank=False, max_length=128)
    pwd = models.CharField(blank=False, max_length=128)
