# Copyright (C) 2019  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 csv
import io
from collections import OrderedDict

from django.contrib.postgres.fields import JSONField
from django.db import IntegrityError, models, transaction
from django.http import Http404
from django.utils.translation import gettext_lazy as _

from passerelle.base.models import BaseResource
from passerelle.utils import SFTPField
from passerelle.utils.api import APIError, endpoint

CSV_DELIMITER = ';'
FILES_ENCODING = 'latin-1'

APPLICANT_SCHEMA = OrderedDict(
    (
        (
            "form_id",
            {
                "type": "string",
            },
        ),
        (
            "demand_date",
            {
                "type": "string",
                "pattern": "^[0-9]{8}$",
            },
        ),
        (
            "demand_time",
            {
                "type": "string",
                "pattern": "^[0-9]{6}$",
            },
        ),
        (
            "producer_code",
            {
                "type": "integer",
            },
        ),
        ("invariant_number", {"type": "string", "maxLength": 10, "default": ""}),
        (
            "city_insee_code",
            {
                "type": "string",
            },
        ),
        (
            "street_rivoli_code",
            {
                "type": "string",
            },
        ),
        (
            "street_name",
            {
                "type": "string",
            },
        ),
        ("address_complement", {"type": "string", "maxLength": 32, "default": ""}),
        ("street_number", {"type": "integer", "default": 0}),
        ("bis_ter", {"type": "string", "maxLength": 3, "default": ""}),
        ("building", {"type": "string", "maxLength": 5, "default": ""}),
        ("hall", {"type": "string", "maxLength": 5, "default": ""}),
        ("appartment_number", {"type": "string", "maxLength": 5, "default": ""}),
        ("producer_social_reason", {"type": "string", "maxLength": 38, "default": ""}),
        ("producer_title_code", {"type": "integer", "default": 0}),
        ("producer_last_name", {"type": "string", "maxLength": 38, "default": ""}),
        ("producer_first_name", {"type": "string", "maxLength": 32, "default": ""}),
        ("producer_phone", {"type": "string", "maxLength": 20, "default": ""}),
        ("producer_email", {"type": "string", "maxLength": 50, "default": ""}),
        ("owner_last_name", {"type": "string", "maxLength": 38, "default": ""}),
        ("owner_first_name", {"type": "string", "maxLength": 32, "default": ""}),
        ("owner_phone", {"type": "string", "maxLength": 20, "default": ""}),
        ("owner_email", {"type": "string", "maxLength": 50, "default": ""}),
        ("activity_code", {"type": "integer", "default": 0}),
        ("family_members_number", {"type": "integer", "default": 0}),
        ("houses_number", {"type": "integer", "default": 0}),
        ("t1_flats_number", {"type": "integer", "default": 0}),
        ("t2_flats_number", {"type": "integer", "default": 0}),
        ("t3_flats_number", {"type": "integer", "default": 0}),
        ("t4_flats_number", {"type": "integer", "default": 0}),
        ("t5_flats_number", {"type": "integer", "default": 0}),
        ("t6_flats_number", {"type": "integer", "default": 0}),
        ("shops_number", {"type": "integer", "default": 0}),
        ("garden_size", {"type": "integer", "default": 0}),
        ("expected_date", {"type": "string", "pattern": "^[0-9]{8}$", "default": ""}),
        ("expected_time", {"type": "string", "pattern": "^[0-9]{4}$", "default": ""}),
        ("modification_code", {"type": "integer", "default": 0}),
        ("demand_reason_label", {"type": "string", "default": ""}),
        ("comment", {"type": "string", "maxLength": 500, "default": ""}),
    )
)

CARD_SCHEMA = OrderedDict(
    (
        (
            "card_subject",
            {
                "type": "integer",
            },
        ),
        (
            "card_type",
            {
                "type": "integer",
            },
        ),
        (
            "card_demand_reason",
            {
                "type": "integer",
            },
        ),
        (
            "cards_quantity",
            {
                "type": "integer",
            },
        ),
        (
            "card_number",
            {
                "type": "string",
                "maxLength": 20,
            },
        ),
        (
            "card_bar_code",
            {
                "type": "string",
                "maxLength": 20,
                "default": "",
            },
        ),
        (
            "card_code",
            {
                "type": "string",
                "maxLength": 20,
                "default": "",
            },
        ),
        (
            "card_validity_start_date",
            {
                "type": "string",
                "pattern": "^[0-9]{8}$",
                "default": "",
            },
        ),
        (
            "card_validity_end_date",
            {
                "type": "string",
                "pattern": "^[0-9]{8}$",
                "default": "",
            },
        ),
        (
            "card_comment",
            {
                "type": "string",
                "maxLength": 100,
                "default": "",
            },
        ),
    )
)

DEMAND_SCHEMA = APPLICANT_SCHEMA.copy()
DEMAND_SCHEMA.update(CARD_SCHEMA)

SCHEMA = {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Gesbac",
    "description": "",
    "type": "object",
    "required": [
        "form_id",
        "demand_date",
        "demand_time",
        "producer_code",
        "city_insee_code",
        "street_rivoli_code",
        "street_name",
        "card_subject",
        "card_type",
        "card_demand_reason",
        "cards_quantity",
    ],
    "properties": DEMAND_SCHEMA,
}


class Gesbac(BaseResource):
    outcoming_sftp = SFTPField(verbose_name=_('Outcoming SFTP'))
    incoming_sftp = SFTPField(verbose_name=_('Incoming SFTP'))
    output_files_prefix = models.CharField(_('Output files prefix'), blank=False, max_length=32)
    input_files_prefix = models.CharField(_('Input files prefix'), blank=False, max_length=32)

    category = _('Business Process Connectors')

    class Meta:
        verbose_name = 'Gesbac'

    def check_status(self):
        with self.outcoming_sftp.client() as out_sftp:
            out_sftp.listdir()
        with self.incoming_sftp.client() as in_sftp:
            in_sftp.listdir()

    def get_responses(self):
        data = []
        with self.incoming_sftp.client() as client:
            for csv_file in client.listdir():
                if not csv_file.startswith(self.input_files_prefix):
                    continue
                with client.open(csv_file, 'rb') as fd:
                    content = io.TextIOWrapper(fd, encoding=FILES_ENCODING)
                    for row in csv.reader(content, delimiter=CSV_DELIMITER):
                        data.append(row)
        for card_data in data:
            for form in self.form_set.filter(status='sent'):
                if card_data[1] == form.get_gesbac_id():
                    form.card_data = card_data
                    form.status = 'closed'
                    form.save()

    def hourly(self):
        super().hourly()
        self.get_responses()

    def send_demand(self, form_id):
        form = Form.objects.get(id=form_id)
        form.send()

    @endpoint(
        name='create-demand',
        perm='can_access',
        description=_('Create demand'),
        post={
            'description': _('Creates a demand file'),
            'request_body': {'schema': {'application/json': SCHEMA}},
        },
    )
    def create_demand(self, request, post_data):
        form_id = post_data['form_id']
        for counter in range(20):
            try:
                with transaction.atomic():
                    form = Form.objects.create(resource=self, form_id=form_id, counter=counter)
                    break
            except IntegrityError:
                continue
        else:
            raise APIError('fail: more than 20 demands')
        post_data['form_id'] = form.get_gesbac_id()
        data = []
        applicant_data = ['E']
        # get applicant attributes
        for name, value in APPLICANT_SCHEMA.items():
            if value['type'] == 'string':
                item = post_data.get(name, '')
            elif value['type'] == 'integer':
                item = post_data.get(name, 0)
            applicant_data.append(item)
        data.append(applicant_data)
        # get card attributes
        card_data = ['CARTE', post_data['form_id']]
        for name, value in CARD_SCHEMA.items():
            if value['type'] == 'string':
                item = post_data.get(name, '')
            elif value['type'] == 'integer':
                item = post_data.get(name, 0)
            card_data.append(item)
        data.append(card_data)
        form.demand_data = data
        form.save()
        self.add_job('send_demand', form_id=form.id)
        return {'data': {'filename': form.get_filename(), 'gesbac_id': form.get_gesbac_id()}}

    @endpoint(
        name='get-response',
        perm='can_access',
        description=_('Get response'),
        parameters={'gesbac_id': {'description': _('Gesbac demand identifier'), 'example_value': '420001'}},
    )
    def get_response(self, request, gesbac_id):
        try:
            response = self.form_set.filter(status='closed', gesbac_id=gesbac_id).latest()
            return {'data': response.card_data}
        except Form.DoesNotExist:
            raise Http404('No response found')


FORM_STATUSES = (('new', 'New'), ('sent', 'Sent'), ('closed', 'Closed'))


class Form(models.Model):
    resource = models.ForeignKey(Gesbac, on_delete=models.CASCADE)
    form_id = models.CharField(max_length=64)
    gesbac_id = models.CharField(max_length=64)
    counter = models.IntegerField(default=0)
    creation_datetime = models.DateTimeField(auto_now_add=True)
    filename = models.CharField(max_length=128, null=True)
    status = models.CharField(max_length=8, default='new', choices=FORM_STATUSES)
    demand_data = JSONField(default=dict)
    card_data = JSONField(default=dict)

    class Meta:
        get_latest_by = 'creation_datetime'
        unique_together = ('form_id', 'counter')

    def get_gesbac_id(self):
        if not self.gesbac_id:
            self.gesbac_id = self.form_id.replace('-', '0') + '%02d' % self.counter
            self.save()
        return self.gesbac_id

    def get_filename(self):
        if not self.filename:
            timestamp = self.creation_datetime.strftime('%y%m%d-%H%M%S')
            self.filename = '%s%s-%s.csv' % (
                self.resource.output_files_prefix,
                timestamp,
                self.get_gesbac_id(),
            )
            self.save()
        return self.filename

    def send(self):
        with self.resource.outcoming_sftp.client() as client:
            with client.open(self.get_filename(), mode='wb') as fd:
                fd = io.TextIOWrapper(fd, encoding=FILES_ENCODING)
                writer = csv.writer(fd, delimiter=CSV_DELIMITER)
                for row in self.demand_data:
                    writer.writerow(row)
        self.status = 'sent'
        self.save()
