# Copyright (C) 2017  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 stat

from django.core.files.storage import default_storage
from django.utils.dateparse import parse_date
from django.utils.encoding import force_bytes
from lxml import etree
from lxml import objectify as xobject

from passerelle.utils.files import atomic_write
from passerelle.utils.jsonresponse import APIError

CERTIFICATE_TYPES = [
    {"id": "NAI", "text": "Naissance"},
    {"id": "MAR", "text": "Mariage"},
    {"id": "REC", "text": "Reconnaissance"},
    {"id": "DEC", "text": "Décès"},
]

SEXES = [{"id": "M", "text": "Homme"}, {"id": "F", "text": "Femme"}, {"id": "NA", "text": "Autre"}]

TITLES = [
    {"id": "M", "text": "Monsieur"},
    {"id": "Mme", "text": "Madame"},
    {"id": "Mlle", "text": "Mademoiselle"},
]

DOCUMENT_TYPES = [
    {"id": "CPI", "text": "Copie intégrale"},
    {"id": "EXTAF", "text": "Extrait avec filiation"},
    {"id": "EXTSF", "text": "Extrait sans filiation"},
    {"id": "EXTPL", "text": "Extrait plurilingue"},
]

CONCERNED = [{"id": "reconnu", "text": "Reconnu"}, {"id": "auteur", "text": "Auteur"}]

ORIGINS = [
    {"id": "internet", "text": "Internet"},
    {"id": "guichet", "text": "Guichet"},
    {"id": "courrier", "text": "Courrier"},
]


def is_clean(element):
    if not element.getchildren() and element.text is None:
        return False
    return all(is_clean(child) for child in element.iterchildren())


class BaseType:
    """Base data binding object"""

    tagname = None

    def __repr__(self):
        return '<%s>' % self.tagname

    @classmethod
    def make_element(cls, tagname, value=None, namespace=None, nsmap=None):
        M = xobject.ElementMaker(annotate=False, namespace=namespace, nsmap=nsmap)
        return M(tagname, value)

    @property
    def xml(self):
        if isinstance(self, (SimpleType, DateType)):
            return self.make_element(self.tagname, self.value, namespace=self.namespace)
        tag = self.make_element(self.tagname, namespace=self.namespace, nsmap=self.nsmap)
        for subelt in self.sequence:
            attr = getattr(self, subelt, None)
            if not attr:
                continue
            if isinstance(attr, str):
                tag.append(self.make_element(subelt, attr, namespace=self.namespace))
            else:
                xml = attr.xml
                if not is_clean(xml):
                    continue
                tag.append(xml)
        xobject.deannotate(tag, xsi_nil=True, cleanup_namespaces=True)
        return tag

    def __str__(self):
        return etree.tostring(self.xml, pretty_print=True)


class CityWebType(BaseType):
    namespace = 'http://tempuri.org/XMLSchema.xsd'
    nsmap = {'xs': 'http://tempuri.org/XMLSchema.xsd'}


class SimpleType(CityWebType):
    """Data binding class for SimpleType"""

    allowed_values = None

    def __init__(self, value):
        if value not in self.allowed_values:
            raise APIError('<%s> value (%s) not in %s' % (self.tagname, value, self.allowed_values))
        self.value = value


class DateType(CityWebType):
    def __init__(self, value):
        try:
            self.value = parse_date(value)
        except (ValueError,) as exc:
            raise APIError('Invalid date (%s) for <%s>: %s' % (value, self.tagname, exc))
        if not self.value:
            raise APIError('Invalid date (%s) for <%s>' % (value, self.tagname))
        self.value = self.value.isoformat()


class ComplexType(CityWebType):
    """Data binding class for ComplexType"""

    sequence = None
    pattern = None

    def __init__(self, data):
        if self.pattern:
            data = self.extract_by_pattern(data)
        self.data = data

    def extract_by_pattern(self, data):
        data = {key: value for key, value in data.items() if self.pattern in key}
        data = {key.replace(self.pattern, ''): value for key, value in data.items()}
        return data


class BirthDate(DateType):
    tagname = 'date'


class StartDate(DateType):
    tagname = 'dateDebut'


class EndDate(DateType):
    tagname = 'dateFin'


# SIMPLE TYPES


class Document(SimpleType):
    tagname = 'natureDocument'
    allowed_values = list(map(lambda x: x['id'], DOCUMENT_TYPES))


class Origin(SimpleType):
    tagname = 'origine'
    allowed_values = list(map(lambda x: x['id'], ORIGINS))


class Title(SimpleType):
    tagname = 'genre'
    allowed_values = list(map(lambda x: x['id'], TITLES))


class Sex(SimpleType):
    tagname = 'sexe'
    allowed_values = list(map(lambda x: x['id'], SEXES))


class Certificate(SimpleType):
    tagname = 'natureEvenement'
    allowed_values = list(map(lambda x: x['id'], CERTIFICATE_TYPES))


class ConcernedKind(SimpleType):
    tagname = 'typeInteresse'
    allowed_values = ('reconnu', 'auteur')


# COMPLEX TYPES


class Names(ComplexType):
    tagname = 'noms'
    sequence = ('nomDeFamille', 'nomUsgae', 'typeUsage')

    def __init__(self, data):
        super().__init__(data)
        if self.data.get('lastname'):
            self.nomDeFamille = self.data['lastname']
            self.nomUsage = self.data.get('usual_name', '')
            self.typeUsage = self.data.get('name_usage', '')


class Place(ComplexType):
    tagname = 'lieu'
    sequence = ('ville', 'province', 'pays')

    def __init__(self, data):
        super().__init__(data)
        if self.data.get('city'):
            self.ville = self.data['city']
            self.province = self.data.get('county', '')
            self.pays = self.data.get('country', '')


class Address(ComplexType):
    tagname = 'adresse'
    sequence = ('ligneAdr1', 'ligneAdr2', 'codePostal', 'lieu', 'mail', 'tel')
    pattern = 'address_'

    def __init__(self, data):
        super().__init__(data)
        self.ligneAdr1 = self.data.get('street', '')
        self.ligneAdr2 = self.data.get('complement', '')
        self.codePostal = self.data.get('zipcode', '')
        self.lieu = Place(self.data)
        self.mail = self.data.get('email', '')
        self.tel = self.data.get('phone', '')


class Birth(ComplexType):
    tagname = 'naissance'
    sequence = ('date', 'lieu')
    pattern = 'birth_'

    def __init__(self, data):
        super().__init__(data)
        birthdate = self.data.get('date', None)
        if birthdate:
            self.date = BirthDate(birthdate)
        self.lieu = Place(self.data)


class EventDate(ComplexType):
    tagname = 'dateEvenement'
    sequence = ('dateDebut', 'dateFin')

    def __init__(self, data):
        super().__init__(data)
        self.dateDebut = StartDate(self.data['event_date_start'])
        if data.get('event_date_end', None):
            self.dateFin = EndDate(self.data['event_date_end'])


class EventPlace(Place):
    tagname = 'lieuEvenement'
    pattern = 'event_'


class Person(ComplexType):
    sequence = ('noms', 'prenoms', 'genre', 'adresse', 'sexe', 'pere', 'mere', 'naissance')

    def __init__(self, data):
        super().__init__(data)
        self.noms = Names(self.data)
        self.prenoms = self.data.get('firstnames', '')
        if self.data.get('title', None):
            self.genre = Title(self.data['title'])
        self.adresse = Address(self.data)
        if self.data.get('sex', None):
            self.sexe = Sex(self.data['sex'])
        self.naissance = Birth(self.data)


class ApplicantPerson(Person):
    tagname = 'individu'
    pattern = 'applicant_'
    sequence = ('noms', 'prenoms', 'genre', 'adresse')


class Parent(Person):
    sequence = ('noms', 'prenoms', 'genre')

    def __init__(self, data, pattern):
        self.pattern = pattern
        super().__init__(data)
        sex = self.data.get('sex')
        if sex:
            if sex == 'M':
                self.tagname = 'pere'
            else:
                self.tagname = 'mere'
        else:
            if pattern.startswith('parent1'):
                self.tagname = 'pere'
            else:
                self.tagname = 'mere'


class ConcernedCommon(Person):
    sequence = ('noms', 'prenoms', 'genre', 'sexe', 'parent1', 'parent2', 'naissance')

    def __init__(self, data):
        super().__init__(data)
        self.parent1 = Parent(self.data, 'parent1_')
        self.parent2 = Parent(self.data, 'parent2_')


class Concerned(ConcernedCommon):
    tagname = 'interesse'
    pattern = 'concerned_'


class Partner(ConcernedCommon):
    tagname = 'conjoint'
    pattern = 'partner_'


class Applicant(ComplexType):
    tagname = 'demandeur'
    sequence = ('qualiteDemandeur', 'individu')

    def __init__(self, data):
        self.qualiteDemandeur = data.get('applicant_status', '')
        self.individu = ApplicantPerson(data)


class Event(ComplexType):
    tagname = 'evenement'
    sequence = ('interesse', 'conjoint', 'natureEvenement', 'typeInteresse', 'dateEvenement', 'lieuEvenement')

    def __init__(self, data):
        certificate_type = data['certificate_type']
        self.interesse = Concerned(data)
        if certificate_type == 'MAR':
            self.conjoint = Partner(data)
        self.natureEvenement = Certificate(data['certificate_type'])
        if data.get('concerned_kind', None):
            self.typeInteresse = ConcernedKind(data['concerned_kind'])
        self.dateEvenement = EventDate(data)
        self.lieuEvenement = EventPlace(data)


class CivilStatusApplication(ComplexType):
    tagname = 'demandeEtatCivil'
    sequence = (
        'identifiant',
        'demandeur',
        'natureDocument',
        'nbExemplaire',
        'dateDemande',
        'evenement',
        'motif',
        'origine',
        'commentaire',
    )

    def __init__(self, data):
        self.identifiant = data['application_id']
        self.demandeur = Applicant(data)
        self.natureDocument = Document(data['document_type'])
        self.nbExemplaire = data['document_copies']
        self.dateDemande = data['application_time']
        self.evenement = Event(data)
        self.motif = data.get('application_reason', '')
        if data.get('application_origin', None):
            self.origine = Origin(data['application_origin'])
        self.commentaire = data.get('application_comment', '')

    def save(self, path):
        basename = os.path.join(path, self.identifiant)
        filename = basename + '.xml'
        content = etree.tostring(self.xml, pretty_print=True)
        filepath = default_storage.path(filename)
        dirname = os.path.dirname(filepath)
        if not os.path.exists(dirname):
            os.makedirs(dirname)
        with atomic_write(filepath) as fd:
            fd.write(force_bytes(content))
        # set read only permission for owner and group
        os.chmod(filepath, stat.S_IRUSR | stat.S_IRGRP)
        return filepath
