# passerelle - uniform access to multiple data sources and services
# 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 json
import re

from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _

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

COMMUNE_EXTRA_MAPPING = {'Vaulx-en-Velin': 'VAULX'}

DEFAULT_MIN = 0
DEFAULT_MAX = 99999


def normalize_street(street):
    return slugify(re.sub(r"[' ()-]", ' ', street))


def normalize_commune(commune):
    if commune in COMMUNE_EXTRA_MAPPING:
        return COMMUNE_EXTRA_MAPPING[commune]
    return commune.upper()


class GrandLyonStreetSections(BaseResource):
    category = _('Geographic information system')

    class Meta:
        verbose_name = _('Sections of Grand Lyon Streets')

    @endpoint(
        perm='can_access',
        description=_('Get details on a section'),
        parameters={
            'streetname': {
                'description': _('Street name'),
                'example_value': 'Boulevard du Raquin',
            },
            'streetnumber': {
                'description': _('Street number'),
                'example_value': '12',
            },
            'commune': {
                'description': _('Collectivity'),
                'example_value': 'Chassieu',
            },
            'insee': {'description': _('INSEE Code'), 'example_value': '69271'},
        },
    )
    def section_info(self, request, streetname, streetnumber, commune=None, insee=None):
        sections = StreetSection.objects.filter(normalized_name=normalize_street(streetname))
        if commune:
            sections = sections.filter(nomcommune__startswith=normalize_commune(commune))
        if insee:
            sections = sections.filter(codeinsee=insee)
        if streetnumber and re.findall(r'\d+', streetnumber):
            streetnumber = int(re.findall(r'\d+', streetnumber)[0])
        else:
            # if no streetnumber, use the first section (it may happen street
            # number 1 doesn't exist, ex: rue des cuirassiers).
            streetnumber = sections[0].bornemindroite if len(sections) else 1

        default_match = None
        for section in sections:
            if streetnumber < section.bornemindroite and streetnumber < section.bornemingauche:
                continue
            if streetnumber > section.bornemaxdroite and streetnumber > section.bornemaxgauche:
                continue

            nomcommune = section.nomcommune
            if nomcommune.startswith('LYON '):
                # remove districts from commune name
                nomcommune = 'LYON'

            match = {
                'err': 0,
                'data': {
                    'domanialite': section.domanialite,
                    'codefuv': section.codefuv,
                    'codetroncon': section.codetroncon,
                    'nom': section.nom,
                    'nomcommune': nomcommune,
                    'nomcommuneorigine': section.nomcommune,  # with district
                    'codeinsee': section.codeinsee,
                },
            }
            if DEFAULT_MIN in (section.bornemindroite, section.bornemingauche) or DEFAULT_MAX in (
                section.bornemaxdroite,
                section.bornemaxgauche,
            ):
                default_match = match
                continue

            return match

        if default_match:
            return default_match

        return {'err': 1}

    def daily(self):
        super().daily()
        update_start = timezone.now()
        sections = self.requests.get(
            'https://download.data.grandlyon.com/ws/grandlyon/adr_voie_lieu.adraxevoie/all.json?maxfeatures=1000000'
        ).content
        for value in json.loads(sections).get('values'):
            if not value.get('codefuv') or not value.get('codetroncon'):
                continue
            section, dummy = StreetSection.objects.get_or_create(
                codefuv=value.get('codefuv'), codetroncon=value.get('codetroncon')
            )
            for attribute in ('nom', 'nomcommune', 'domanialite', 'codeinsee'):
                setattr(section, attribute, value.get(attribute) or '')
            section.nomcommune = normalize_commune(section.nomcommune)
            for attribute in ('bornemindroite', 'bornemingauche', 'bornemaxdroite', 'bornemaxgauche', 'gid'):
                if value.get(attribute) in (None, 'None'):
                    # data.grandlyon returned 'None' as a string at a time
                    if 'min' in attribute:
                        attribute_value = DEFAULT_MIN
                    elif 'max' in attribute:
                        attribute_value = DEFAULT_MAX
                    else:
                        attribute_value = None
                else:
                    attribute_value = int(value.get(attribute))
                setattr(section, attribute, attribute_value)
            section.save()
        StreetSection.objects.filter(last_update__lt=update_start).delete()


class StreetSection(models.Model):
    bornemindroite = models.PositiveIntegerField(null=True)
    bornemingauche = models.PositiveIntegerField(null=True)
    bornemaxdroite = models.PositiveIntegerField(null=True)
    bornemaxgauche = models.PositiveIntegerField(null=True)
    gid = models.PositiveIntegerField(null=True)
    nomcommune = models.CharField(max_length=50)
    nom = models.CharField(max_length=200)
    domanialite = models.CharField(max_length=50)
    codefuv = models.CharField(max_length=15)
    codetroncon = models.CharField(max_length=15)
    codeinsee = models.CharField(max_length=15, null=True)

    normalized_name = models.CharField(max_length=200)
    last_update = models.DateTimeField(auto_now=True)

    class Meta:
        # order by street number to be sure the first result for a given street
        # will be the lowest street number.
        ordering = ['normalized_name', 'nomcommune', 'bornemindroite', 'bornemingauche']

    def save(self, *args, **kwargs):
        if self.nom:
            self.normalized_name = normalize_street(self.nom)
        return super().save(*args, **kwargs)
