# Passerelle - uniform access to data 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/>.

import os
import base64
import datetime
from xml.etree import ElementTree as etree
from xml.dom import minidom

from dateutil.parser import parse as dateutil_parse

from django.core.files.storage import default_storage
from django.core.files.base import ContentFile

from utils import ElementFactory

from passerelle.utils.jsonresponse import APIError


def get_resource_base_dir():
    return default_storage.path('mdel')


class AttachedFile(object):

    def __init__(self, code, filename, b64_content):
        if code not in ('JI', 'JD'):
            raise APIError('%s is not a valid code (JI or JD)' % code)
        self.filename = self.title = filename
        self.content = base64.b64decode(b64_content)
        self.code = code

    def save(self, directory):
        """Writes file into directory
        """
        path = os.path.join(directory, self.filename)
        default_storage.save(path, ContentFile(self.content))


class MDELBase(object):

    def to_string(self):
        raw_string = etree.tostring(self.xml, encoding='utf-8')
        parsed_string = minidom.parseString(raw_string)
        return parsed_string.toprettyxml(indent='\t')

    def save(self, subfolder, filename):
        """Save object as xml file
        """
        folder = os.path.join(get_resource_base_dir(), subfolder)
        path = os.path.join(folder, filename)
        default_storage.save(path, ContentFile(self.to_string()))


class Common(MDELBase):
    xmlns = "http://finances.gouv.fr/dgme/gf/composants/teledemarchexml/donnee/metier"

    def __init__(self, *args, **kwargs):
        self.flow_type = args[0]
        self.num = args[1]
        super(Common, self).__init__()

    def save(self, folder):
        return super(Common, self).save(folder, self.filename)

    @property
    def code(self):
        return '%s-%s' % (self.num, self.flow_type)

    def teledemarche_xml(self, num, date=None, platform_id=1):
        """Returns XML Teledemarche Element
        """
        if not date:
            date = datetime.datetime.utcnow().isoformat()
        else:
            date = dateutil_parse(date).isoformat()

        teledemarche = etree.Element('Teledemarche')
        teledemarche.extend([
            ElementFactory('NumeroTeledemarche', text=num),
            ElementFactory('Date', text=date),
            ElementFactory('IdentifiantPlateforme', text=platform_id),
        ])
        return teledemarche

    def routage_xml(self, code_insee):
        """Returns XML Routage Element
        """
        routage = etree.Element('Routage')
        donnee = etree.Element('Donnee')
        donnee.extend([
            ElementFactory('Id', text='CodeINSEE'),
            ElementFactory('Valeur', text=code_insee)
        ])
        routage.append(donnee)
        return routage

    def piecejointe_xml(self, attached_files=[]):
        """Returns XML PieceJointe Element
        """
        if not attached_files:
            return

        for f in attached_files:
            piece_jointe = etree.Element('PieceJointe')
            piece_jointe.extend([
                ElementFactory('Intitule', text=f.title),
                ElementFactory('Code', text=f.code),
                ElementFactory('Fichier', text=f.filename)
            ])

            yield piece_jointe

    def document_xml(self, code, form_files=[]):
        """Returns a XML Document Element
        """
        document = ElementFactory('Document')
        document.extend([
            ElementFactory('Code', text=code),
            ElementFactory('Nom', text=code),
            ElementFactory('FichierFormulaire')
        ])

        if not form_files:
            form_files.append(self.code + '-doc-.xml')

        for f in form_files:
            document.find('FichierFormulaire').append(
                ElementFactory('FichierDonnees', text=f)
            )

        return document


class Message(Common):
    """Class descrbing a message.xml file
    """
    filename = 'message.xml'
    ns_prefix = 'ns2'
    ns_uri = 'http://finances.gouv.fr/dgme/pec/message/v1'

    def __init__(
            self, flow_type, num, zip_code, date=None,
            platform_id='1', form_files=[], attached_files=[]):

        super(Message, self).__init__(flow_type, num)

        etree._namespace_map[self.ns_uri] = self.ns_prefix

        root = ElementFactory('Message', namespace=self.ns_uri)
        root.set('xmlns', self.xmlns)

        # HEADERS
        header = ElementFactory('Header', namespace=self.ns_uri)
        routing = ElementFactory('Routing', namespace=self.ns_uri)
        routing.extend([
            ElementFactory('MessageId', text=num, namespace=self.ns_uri),
            ElementFactory('FlowType', text=flow_type, namespace=self.ns_uri),
        ])
        header.append(routing)

        # BODY
        body = ElementFactory('Body', namespace=self.ns_uri)
        content = ElementFactory('Content', namespace=self.ns_uri)
        aller = ElementFactory('Aller', namespace=self.ns_uri)
        aller.extend([
            self.teledemarche_xml(num, date, platform_id),
            self.routage_xml(zip_code),
            self.document_xml(self.code, form_files)
        ])

        [aller.append(f) for f in self.piecejointe_xml(attached_files) if f]

        content.append(aller)
        body.append(content)

        root.extend([header, body])

        self.xml = root


class Description(Common):
    """Class describing a -ent-.xml file
    """

    def __init__(
            self, flow_type, num, zip_code, date=None,
            platform_id='1', form_files=[], attached_files=[]):

        super(Description, self).__init__(
            flow_type, num, zip_code, date,
            platform_id, form_files, attached_files
        )

        root = ElementFactory('EnteteMetierEnveloppe', attrib={'xmlns': self.xmlns})

        root.extend([
            self.teledemarche_xml(num, date, platform_id),
            self.routage_xml(zip_code),
            self.document_xml(self.code, form_files)
        ])

        for f in self.piecejointe_xml(attached_files):
            root.append(f)

        self.xml = root

    @property
    def filename(self):
        return '%s-ent-.xml' % self.code


class Data(MDELBase):
    """Class describing a -doc.xml file
    """

    def __init__(self, demand_id, data):
        self.data = self.data_validator(data)
        self.demand_id = demand_id

    @property
    def filename(self):
        return '%s-doc-.xml' % self.demand_id

    def save(self, folder):
        return super(Data, self).save(folder, self.filename)

    def data_validator(self, data):
        """Checks if required keys are present
        """
        missing_field = set(self.required).difference(set(data.keys()))
        if missing_field:
            raise APIError('field %s is required' % missing_field.pop())

        return data

    @property
    def xml(self):
        elements = {}

        for key, value in self.data.items():
            # ignore non mapped keys
            if self.mapping.get(key, None):
                elements[self.mapping[key]] = value

        root = ElementFactory(self.root_element)

        for key, value in elements.items():
            path = key.split('_')
            root.append(json_to_xml(path, value, root), allow_new=False)

        return root


class ILEData(Data):

    mapping = {
        'nom_famille': 'Inscription_Electeur_Noms_NomFamille',
        'nom_usage': 'Inscription_Electeur_Noms_NomUsage',
        'prenoms': 'Inscription_Electeur_Prenoms_Prenom',
        'date_naissance': 'Inscription_Electeur_DateDeNaissance',
        'lieunaiss_localite': 'Inscription_Electeur_LieuDeNaissance_Localite',
        'lieunaiss_division_territoriale': 'Inscription_Electeur_LieuDeNaissance_DivisionTerritoriale',
        'lieunaiss_code_postal': 'Inscription_Electeur_LieuDeNaissance_CodePostal',
        'lieunaiss_pays_raw': 'Inscription_Electeur_LieuDeNaissance_Pays',
        'sexe_raw': 'Inscription_Electeur_Sexe',
        'nationalite_raw': 'Inscription_Electeur_Nationalite',
        'adresse_num_voie': 'Inscription_Electeur_AdresseDeLElecteur_NumeroVoie',
        'adresse_type_voie': 'Inscription_Electeur_AdresseDeLElecteur_TypeVoie',
        'adresse_nom_voie': 'Inscription_Electeur_AdresseDeLElecteur_NomVoie',
        'adresse_complement': 'Inscription_Electeur_AdresseDeLElecteur_Complement',
        'adresse_code_postal': 'Inscription_Electeur_AdresseDeLElecteur_CodePostal',
        'adresse_localite': 'Inscription_Electeur_AdresseDeLElecteur_Localite',
        'adresse_division_territoriale': 'Inscription_Electeur_AdresseDeLElecteur_DivisionTerritoriale',
        'contact_uri': 'Inscription_Electeur_MethodeDeContact_URI',
        'contact_code': 'Inscription_Electeur_MethodeDeContact_CanalCode',
        'inscription_liste_type_raw': 'Inscription_TypeDeListe',
        'inscription_localite': 'CommuneDInscription_Localite',
        'inscription_division_territoriale': 'CommuneDInscription_DivisionTeritoriale',
        'insription_code_insee': 'CommuneDInscription_CodeInsee',
        'inscription_date': 'DateDInscription',
        'inscription_type': 'TypeDInscription',
        'anterieur_situation_raw': 'SituationElectoraleAnterieure_SituationDeLElecteur',
        'anterieur_consulat': 'SituationElectoraleAnterieure_ConsulatDePrecedenteInscription_AmbassadeOuPosteConsulaire',
        'anterieur_consulat_pays_raw': 'SituationElectoraleAnterieure_ConsulatDePrecedenteInscription_Pays',
        'anterieur_localite': 'SituationElectoraleAnterieure_CommuneDePrecedenteInscription_Localite',
        'anterieur_division_territoriale': 'SituationElectoraleAnterieure_CommuneDePrecedenteInscription_DivisionTerritoriale',
        'anterieur_ue_localite': 'SituationElectoraleAnterieure_PaysUeDerniereInscription_Localite',
        'anterieur_ue_division_territoriale': 'SituationElectoraleAnterieure_PaysUeDerniereInscription_DivisionTerritoriale',
        'anterieur_ue_localite_pays_raw': 'SituationElectoraleAnterieure_PaysUeDerniereInscription_Pays'
    }

    required = [
        'nom_famille',
        'nom_usage',
        'prenoms',
        'date_naissance',
        'lieunaiss_localite',
        'lieunaiss_code_postal',
        'lieunaiss_pays_raw',
        'sexe_raw',
        'nationalite_raw',
        'adresse_num_voie',
        'adresse_nom_voie',
        'adresse_code_postal',
        'adresse_localite',
        'inscription_liste_type',
        'anterieur_situation_raw'
    ]

    def __init__(self, demand_id, data):
        self.root_element = 'AvisDInscription'

        # only possible value : vol as 'Inscription Volontaire'
        data['inscription_type'] = 'vol'

        super(ILEData, self).__init__(demand_id, data)


class AECData(Data):

    mapping = {
        'aec_nature': 'DemandeActe_NatureActe_Libelle',
        'aec_nature_raw': 'DemandeActe_NatureActe_Code',
        'aec_type': 'DemandeActe_TypeActe_Libelle',
        'aec_type_raw': 'DemandeActe_TypeActe_Code',
        'code_insee': 'DemandeActe_LieuActe_CodeINSEE',
        'code_postal': 'DemandeActe_LieuActe_CodePostal',
        'demandeur_adresse_adresse': 'DemandeActe_Demandeur_AdresseEtrangere_Adresse',
        'demandeur_adresse_batiment': 'DemandeActe_Demandeur_AdresseFrancaise_Batiment',
        'demandeur_adresse_boite_postal': 'DemandeActe_Demandeur_AdresseFrancaise_BoitePostale',
        'demandeur_adresse_code_postal': 'DemandeActe_Demandeur_AdresseFrancaise_CodePostal',
        'demandeur_adresse_etage': 'DemandeActe_Demandeur_AdresseFrancaise_Etage',
        'demandeur_adresse_ville': 'DemandeActe_Demandeur_AdresseFrancaise_Ville',
        'demandeur_adresse_etrangere': 'DemandeActe_Demandeur_AdresseEtrangere_Adresse',
        'demandeur_adresse_etrangere_pays_raw': 'DemandeActe_Demandeur_AdresseEtrangere_Pays',
        'demandeur_adresse_voie': 'DemandeActe_Demandeur_AdresseFrancaise_Voie',
        'demandeur_civilite': 'DemandeActe_Demandeur_Civilite',
        'demandeur_email': 'DemandeActe_Demandeur_Courriel',
        'demandeur_nom': 'DemandeActe_Demandeur_Nom',
        'demandeur_prenom': 'DemandeActe_Demandeur_Prenom',
        'demandeur_telephone': 'DemandeActe_Demandeur_Telephone',
        'nombre_exemplaires': 'DemandeActe_NombreExemplaires',
        'motif_demande': 'DemandeActe_MotifDemande_Libelle',
        'motif_demande_raw': 'DemandeActe_MotifDemande_Commentaire',
        'qualite_demandeur': 'DemandeActe_Demandeur_QualiteDemandeur_Libelle',
        'qualite_demandeur_raw': 'DemandeActe_Demandeur_QualiteDemandeur_Code',
        'date_acte': 'DemandeActe_DateActe',
        'titulaire_civilite': 'DemandeActe_Titulaire_Civilite',
        'titulaire_mere_nom': 'DemandeActe_Titulaire_Filiation_Mere_Nom',
        'titulaire_mere_prenoms': 'DemandeActe_Titulaire_Filiation_Mere_Prenoms',
        'titulaire_naiss_date': 'DemandeActe_Titulaire_DateDeNaissance',
        'titulaire_naiss_departement_raw': 'DemandeActe_Titulaire_DepartementDeNaissance',
        'titulaire_nationalite_raw': 'DemandeActe_Titulaire_Nationalite',
        'titulaire_naiss_pays_raw': 'DemandeActe_Titulaire_PaysDeNaissance',
        'titulaire_naiss_ville': 'DemandeActe_Titulaire_LieuDeNaissance',
        'titulaire_nom': 'DemandeActe_Titulaire_NomNaissance',
        'titulaire_pere_nom': 'DemandeActe_Titulaire_Filiation_Pere_Nom',
        'titulaire_pere_prenoms': 'DemandeActe_Titulaire_Filiation_Pere_Prenoms',
        'titulaire_prenoms': 'DemandeActe_Titulaire_Prenoms',
        'titulaire2_civilite': 'DemandeActe_Titulaire2_Civilite',
        'titulaire2_mere_nom': 'DemandeActe_Titulaire2_Filiation_Mere_Nom',
        'titulaire2_mere_prenoms': 'DemandeActe_Titulaire2_Filiation_Mere_Prenoms',
        'titulaire2_naiss_date': 'DemandeActe_Titulaire2_DateDeNaissance',
        'titulaire2_naiss_departement_raw': 'DemandeActe_Titulaire2_DepartementDeNaissance',
        'titulaire2_nationalite_raw': 'DemandeActe_Titulaire2_Nationalite',
        'titulaire2_naiss_pays_raw': 'DemandeActe_Titulaire2_PaysDeNaissance',
        'titulaire2_naiss_ville': 'DemandeActe_Titulaire2_LieuDeNaissance',
        'titulaire2_nom': 'DemandeActe_Titulaire2_NomNaissance',
        'titulaire2_pere_nom': 'DemandeActe_Titulaire2_Filiation_Pere_Nom',
        'titulaire2_pere_prenoms': 'DemandeActe_Titulaire2_Filiation_Pere_Prenoms',
        'titulaire2_prenoms': 'DemandeActe_Titulaire2_Prenoms',
        'ville': 'DemandeActe_LieuActe_Ville'
    }

    required = [
        'aec_nature',
        'aec_nature_raw',
        'aec_type',
        'aec_type_raw',
        'code_insee',
        'code_postal',
        'demandeur_civilite',
        'demandeur_email',
        'demandeur_nom',
        'demandeur_prenom',
        'nombre_exemplaires',
        'motif_demande',
        'motif_demande_raw',
        'qualite_demandeur',
        'qualite_demandeur_raw',
        'titulaire_civilite',
        'titulaire_mere_nom',
        'titulaire_mere_prenoms',
        'titulaire_nom',
        'titulaire_pere_nom',
        'titulaire_pere_prenoms',
        'titulaire_prenoms'
    ]

    def __init__(self, demand_id, data):
        self.root_element = 'EnveloppeMetierType'

        super(AECData, self).__init__(demand_id, data)


def json_to_xml(path, value, parent):

    if isinstance(path, list) and len(path) > 1:
        parent_child = parent.find(path[0])
        if parent_child is not None:
            element = parent_child
            path.pop(0)
        else:
            element = ElementFactory(path.pop(0))

        element.append(json_to_xml(path, value, element), allow_new=False)
    else:
        element = ElementFactory(path[0], text=value)

    return element
