# -*- coding: utf-8 -*-
# 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 json

from dateutil.parser import parse as dateutil_parse

from django.db import models
from django.utils.translation import ugettext_lazy as _

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

from . import mdel

from .utils import zipdir, get_file_content_from_zip


DEMAND_TYPES = ['ILE-LA', 'RCO-LA', 'AEC-LA']

STATUS_MAPPING = {
    '100': 'closed',
    '20': 'rejected',
    '19': 'accepted',
    '17': 'information needed',
    '16': 'in progress',
    '15': 'invalid',
    '14': 'imported'
}

APPLICANTS = [
    {"id": "PersonneConcernee", "text": "La personne concernée par l'acte"},
    {"id": "PereMere", "text": "Son père ou sa mère"},
    {"id": "Conjoint", "text": "Son conjoint ou sa conjointe"},
    {"id": "Fils", "text": "Son fils ou sa fille"},
    {"id": "GrandPere", "text": "Son grand-père ou sa grand-mère"},
    {"id": "PetitFils", "text": "Son petit-fils ou sa petite-fille"},
    {"id": "Representant", "text": "Son représentant légal"},
    {"id": "Heriter", "text": "Son héritier"},
    {"id": "Autre", "text": "Autre"}]

CERTIFICATES = [
    {'id': 'NAISSANCE', 'text': 'Acte de naissance'},
    {'id': 'MARIAGE', 'text': 'Acte de mariage'},
    {'id': 'DECES', 'text': 'Acte de décès'}]

CERTIFICATE_TYPES = [
    {'id': 'COPIE-INTEGRALE', 'text': 'Copie intégrale'},
    {'id': 'EXTRAIT-AVEC-FILIATION', 'text': 'Extrait avec filiation'},
    {'id': 'EXTRAIT-SANS-FILIATION', 'text': 'Extrait sans filiation'},
    {'id': 'EXTRAIT-PLURILINGUE', 'text': 'Extrait plurilingue'}]


class MDEL(BaseResource):
    """Connector converting Publik's Demand into
    MDEL (XML based format) Demand.
    It's able to manage demand such as :
    - Inscription sur Liste Eletoral (ILE-LA)
    - Acte d'Etat Civil (AEC-LA)
    - Recensement Citoyen Obligatoire (RCO-LA)
    """
    category = _('Business Process Connectors')

    class Meta:
        verbose_name = 'Mes Demarches En Ligne'

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

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

    @endpoint(serializer_type='json-api', perm='can_access', methods=['post'])
    def create(self, request, *args, **kwargs):
        """Create a demand
        """
        formdata = json.loads(request.body)
        extra = formdata.pop('extra', {})
        fields = formdata.pop('fields', {})

        def flatten_payload(*subs):
            result = {}
            for sub in subs:
                result.update(sub)  # priority on last sub dict
            return result

        # flatten the payload
        formdata.update(flatten_payload(fields, extra))

        demand_type = formdata.pop('demand_type', '').upper()
        if demand_type not in DEMAND_TYPES:
            raise APIError('demand_type must be : %r' % DEMAND_TYPES)

        if 'display_id' not in formdata:
            raise APIError('display_id is required')

        if 'code_insee' not in formdata:
            raise APIError('code_insee is required')

        demand_id = formdata.pop('display_id')

        demand, created = Demand.objects.get_or_create(num=demand_id, flow_type=demand_type, resource=self)
        if not created:
            demand.step += 1

        demand.create_zip(formdata)

        demand.save()

        return {'demand_id': demand.demand_id}

    @endpoint(serializer_type='json-api', perm='can_access')
    def status(self, request, *args, **kwargs):
        """Return demand's statutes
        """
        demand_id = request.GET.get('demand_id', None)
        if not demand_id:
            raise APIError('demand_id is required')

        demand = Demand.objects.get(demand_id=demand_id, resource=self)

        status = demand.get_status()

        demand.save()
        return status

    @endpoint(serializer_type='json-api', perm='can_access')
    def applicants(self, request, without=''):
        return [item for item in APPLICANTS
                if item.get('id') not in without.split(',')]

    @endpoint(serializer_type='json-api', perm='can_access')
    def certificates(self, request):
        return CERTIFICATES

    @endpoint(name='certificate-types', serializer_type='json-api', perm='can_access')
    def certificate_types(self, request, without=''):
        return [item for item in CERTIFICATE_TYPES
                if item.get('id') not in without.split(',')]


class Demand(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    resource = models.ForeignKey(MDEL)
    num = models.CharField(
        max_length=64, null=False, primary_key=True, unique=True)
    flow_type = models.CharField(max_length=32, null=False)
    status = models.CharField(max_length=32, null=True)
    step = models.IntegerField(default=0)
    demand_id = models.CharField(max_length=128, null=True)

    def __unicode__(self):
        return '%s - %s - %s' % (self.resource.slug, self.demand_id, self.status)

    class Meta:
        unique_together = (('num', 'flow_type'))

    def save(self, *args, **kwargs):
        self.demand_id = '%s-%s' % (self.num, self.flow_type)
        super(Demand, self).save(*args, **kwargs)

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

    @property
    def filename(self):
        return '%s.zip' % self.name

    def create_zip(self, formdata):
        """
        Creates demand as zip folder
        """

        code_insee = formdata['code_insee']
        flow_type = self.flow_type
        demand_num = self.num

        resource_base_dir = mdel.get_resource_base_dir()

        inputs_dir = os.path.join(resource_base_dir, self.resource.slug,
                                  'inputs', self.name)

        attached_files = []

        if flow_type == 'ILE-LA':

            proofs = [('JI', 'justificatif_identite'), ('JD', 'justificatif_domicile')]

            for proof_code, proof_attribute in proofs:

                documents = [value for key, value in formdata.items()
                             if key.startswith(proof_attribute) and isinstance(value, dict) and 'filename' in value and 'content' in value]
                if not documents:
                    raise APIError('%s and all its attributes are required' % proof_attribute)
                for document in documents:
                    filename, b64_content = document.get('filename'), document.get('content')
                    attached_files.append(mdel.AttachedFile(proof_code, filename, b64_content))

            # process address additional information
            adresse_complement = []

            complement_keys = sorted([key for key in formdata if key.startswith('adresse_complement')])

            for key in complement_keys:
                adresse_complement.append(formdata[key])

            if adresse_complement:
                formdata['adresse_complement'] = ', '.join(adresse_complement)

            # get contact info
            contacts = [('TEL', 'contact_telephone'), ('EMAIL', 'contact_email')]

            for contact_code, contact_uri in contacts:
                uri = formdata.get(contact_uri)
                if uri:
                    formdata['contact_uri'] = uri
                    formdata['contact_code'] = contact_code

            # mdel protocol only supports two values here (prem & cci)
            # but we may want the form to have more specific values;
            # we name them (prem|cci)_suffix and squeeze the suffix here.
            if not formdata.get('anterieur_situation_raw', None):
                raise APIError('anterieur_situation_raw is required')

            formdata['anterieur_situation_raw'] = formdata.get('anterieur_situation_raw').split('_')[0]

            doc = mdel.ILEData(self.demand_id, formdata)
            doc.save(inputs_dir)

            for attached_file in attached_files:
                attached_file.save(inputs_dir)

        elif flow_type == 'RCO-LA':
            raise APIError('RCO-LA processing not implemented')

        else:
            # Set date format
            if formdata.get('aec_type_raw') != 'NAISSANCE' and not formdata.get('date_acte'):
                raise APIError('<date_acte> is required')
            if formdata.get('date_acte'):
                formdata['date_acte'] = dateutil_parse(formdata['date_acte']).date().isoformat()
            if formdata.get('titulaire_naiss_date'):
                formdata['titulaire_naiss_date'] = dateutil_parse(formdata['titulaire_naiss_date']).date().isoformat()
            if formdata.get('titulaire2_naiss_date'):
                formdata['titulaire2_naiss_date'] = dateutil_parse(formdata['titulaire2_naiss_date']).date().isoformat()

            # Ensuring that all titles are uppercase
            for key in formdata.keys():
                if 'civilite' in key:
                    formdata[key] = formdata[key].upper()

            # Merging street number and street name
            formdata['demandeur_adresse_voie'] = '%s %s' % (formdata.get('demandeur_adresse_num', ''),
                                                            formdata.get('demandeur_adresse_voie'))
            # Set foreign address if country is not France
            if formdata.get('demandeur_adresse_pays_raw') != 'FRA':
                formdata['demandeur_adresse_etrangere'] = '%s, %s, %s' % (formdata.pop('demandeur_adresse_voie', ''),
                                                                          formdata.pop('demandeur_adresse_ville', ''),
                                                                          formdata.pop('demandeur_adresse_code_postal', ''))

                formdata['demandeur_adresse_etrangere_pays_raw'] = formdata['demandeur_adresse_pays_raw']

            # Set aec_nature if aec_type_raw == DECES
            if formdata.get('aec_type_raw') == 'DECES' and not formdata.get('aec_nature_raw'):
                formdata['aec_nature'] = u'Copie integrale'
                formdata['aec_nature_raw'] = 'COPIE-INTEGRALE'

            # Set motif_demand if none
            if not formdata.get('motif_demande'):
                formdata['motif_demande'] = 'Autre'
                formdata['motif_demande_raw'] = 'Autre'

            # handling requester when 'Autre'
            if formdata.get('qualite_demandeur_autre') and not formdata.get('qualite_demandeur_raw'):
                formdata['qualite_demandeur'] = formdata['qualite_demandeur_autre']
                formdata['qualite_demandeur_raw'] = 'Autre'

            doc = mdel.AECData(self.demand_id, formdata)
            doc.save(inputs_dir)

        submission_date = formdata.get('receipt_time', None)

        message = mdel.Message(flow_type, demand_num, code_insee, date=submission_date)
        message.save(inputs_dir)

        description = mdel.Description(flow_type, demand_num, code_insee, date=submission_date,
                                       attached_files=attached_files)
        description.save(inputs_dir)

        # create zip file
        return zipdir(inputs_dir)

    def get_status(self):
        """Read demand' statuses from file
        """

        result = {
            'closed': False,
            'status': None,
            'comment': ''
        }

        namespace = {'ns2': 'http://finances.gouv.fr/dgme/pec/message/v1'}

        resource_base_dir = mdel.get_resource_base_dir()
        output_dir = os.path.join(resource_base_dir, self.resource.slug,
                                  'outputs')

        zfiles = [zfile for zfile in os.listdir(output_dir)
                  if zfile.startswith(self.demand_id) and zfile.endswith('.zip')]

        def fnum(zfile):
            """Returns response's file number
            filename MDEL format : <demand_id>-<flow_type>--<num>.zip
            """
            fname = os.path.splitext(zfile)[0]
            return int(fname.split('--')[-1])

        if zfiles:
            path = max(zfiles, key=fnum)  # get the file with the highest number
        else:
            return result

        self.step = fnum(path)

        path = os.path.join(output_dir, path)

        content = get_file_content_from_zip(path, 'message.xml')
        element = mdel.etree.fromstring(content)
        majs = element.findall('ns2:Body/*/*/*/ns2:Maj', namespace)

        statuses = []

        for maj in majs:
            etat = maj.findtext('ns2:Etat', namespaces=namespace)
            commentaire = maj.findtext('ns2:Commentaire', namespaces=namespace)

            if etat:
                statuses.append({
                    'etat': etat,
                    'commentaire': commentaire
                })

        statuses = sorted(statuses, key=lambda x: int(x['etat']))[-2:]

        for status in statuses:
            if status['etat'] == '100':
                result['closed'] = True
            else:
                result['status'] = STATUS_MAPPING[status['etat']]
                result['comment'] = status.get('commentaire', '')

        self.status = result['status']
        self.save()

        return result
