import json
import logging
import operator
import os
import uuid
from functools import reduce
from pathlib import Path

import xlsxwriter
from flask import Blueprint, request, send_file
from flask_jwt_extended import jwt_required
from median.models import Historique, Patient, Magasin, Service, Product
from peewee import DoesNotExist, JOIN, fn
from ressources.astus.utils import generate_memory_excel_file

historic_blueprint = Blueprint('historic', __name__)

logger = logging.getLogger('median')


def get_all_historic(args):
    v_date_debut = args['date_debut']
    v_date_fin = args['date_fin']
    v_search = args['search']
    v_mags = args.get("magasin", [])
    v_mvts = args.get("type_mouvement", [])
    fractions = args.get("fractions", [])

    if (v_search is None) | (v_search == ''):
        v_search_list = []
    else:
        v_search_list = json.loads(v_search.replace("'", "\""))

    expr_list = [
        (Historique.chrono >= v_date_debut),
        (Historique.chrono <= v_date_fin),
    ]
    if len(v_mags) > 0:
        expr_list.append(Historique.poste << v_mags)

    if len(v_mvts) > 0:
        expr_list.append(Historique.type_mouvement << v_mvts)

    if len(fractions) > 0:
        expr_list.append(Historique.fraction << fractions)

    andexpr = reduce(operator.and_, expr_list)
    fullname = Patient.prenom + ' ' + Patient.nom
    #
    if len(v_search_list) > 0:

        lst = list(map(lambda s: (
            (Historique.info.contains(s["value"].strip())) |
            (Product.designation.contains(s["value"].strip())) |
            (Historique.utilisateur.contains(s["value"].strip())) |
            (Historique.liste.contains(s["value"].strip())) |
            (Historique.lot.contains(s["value"].strip())) |
            (Historique.adresse.contains(s["value"].strip())) |
            (fullname.contains(s["value"].strip())) |
            (Historique.serial.contains(s["value"].strip())) |
            (Magasin.libelle.contains(s["value"].strip())) |
            (fullname.contains(s["value"].strip())) |
            (Historique.poste.contains(s["value"].strip())) |
            (Service.libelle.contains(s["value"].strip()))

        ), v_search_list))
        search = reduce(operator.and_, lst)

        expr = reduce(operator.and_, [andexpr, search])
    else:
        expr = andexpr

    request = Historique.select(Historique.pk,
                                Historique.chrono, Historique.reference, Product.designation,
                                Historique.fraction, Historique.type_mouvement, Historique.adresse,
                                Historique.adresse_from, Historique.quantite_mouvement,
                                Historique.quantite_prescrite,
                                Historique.quantite_totale, Historique.info,
                                Historique.quantite_demande,
                                Historique.liste, Historique.service, Historique.lot,
                                Historique.ucd,
                                Historique.utilisateur, Historique.poste,
                                Historique.date_peremption, Historique.serial,
                                Historique.contenant, Patient.nom, Patient.prenom,
                                fullname.alias('NomPatient'),
                                Historique.id_pilulier, Magasin, Product.pk) \
        .join(Product, JOIN.LEFT_OUTER,
              on=(Product.reference == Historique.reference)) \
        .switch(Historique) \
        .join(Patient, JOIN.LEFT_OUTER, on=(Patient.ipp == Historique.ipp)) \
        .switch(Historique) \
        .join(Magasin, JOIN.LEFT_OUTER, on=(Magasin.type_mag == Historique.poste)) \
        .switch(Historique) \
        .join(Service, JOIN.LEFT_OUTER, on=(Service.code == Historique.service)) \
        .where(expr) \
        .order_by(Historique.chrono.desc())

    return request


def get_obj_historic(i):
    return {
        'pk': i.pk,
        'chrono': str(i.chrono),
        'reference': i.reference,
        'designation': (i.product.designation if hasattr(i, 'product') else 'Référence inconnue'),
        'fraction': i.fraction,
        'type_mouvement': i.type_mouvement,
        'adresse': i.adresse,
        'adresse_from': i.adresse_from,
        'quantite_mouvement': i.quantite_mouvement,
        'quantite_prescrite': (i.quantite_demande if (i.type_mouvement == 'CPE') or (i.type_mouvement == 'ENT')
                               else i.quantite_prescrite),
        'quantite_totale': i.quantite_totale,
        'info': i.info,
        'liste': i.liste,
        'service': i.service,
        'patient': (i.patient.nom + " " + i.patient.prenom if hasattr(i, 'patient') else ''),
        'pilulier': i.id_pilulier,
        'lot': i.lot,
        'ucd': i.ucd,
        'utilisateur': i.utilisateur,
        'poste': i.poste,
        'date_peremption': str(i.date_peremption or ''),
        'serial': i.serial,
        'contenant': i.contenant,
        'reference_pk': (i.product.pk if hasattr(i, 'product') else None),
    }


@historic_blueprint.route('count', methods=['POST'])
@jwt_required()
def get_count_historic():
    args = json.loads(request.data)
    try:
        total_stocks_count = get_all_historic(args)
        return {'recordsTotal': total_stocks_count.count(), }
    except Exception as error:
        logger.error(('historic raised an exception: ', error.args))
        return {'recordsTotal': 0, }


@historic_blueprint.route('', methods=['POST'])
@jwt_required()
def get_historic():
    args = json.loads(request.data)

    try:
        v_start = args['start']
        v_length = args['length']
        total_stocks_count = get_all_historic(args)
        paged_emplacements = total_stocks_count.limit(v_length).offset(v_start)

        listHistoric = []

        for hist in paged_emplacements:
            listHistoric.append(get_obj_historic(hist))

        return {
            'data': listHistoric,
        }

    except DoesNotExist:
        logger.error('Get historic items Datatables raised a DoesNotExist exception')
        return {
            'data': [],
        }
    except Exception as error:
        logger.error(('historic raised an exception: ', error.args))
        return {
            'data': [],
        }


@historic_blueprint.route('export', methods=['PATCH'])
@jwt_required()
def export():
    data = json.loads(request.data)
    headers = data['headers']

    stocks = get_all_historic(data)

    memory_file = generate_memory_excel_file(headers=headers, items=stocks, transform_function=get_obj_historic)

    return send_file(memory_file, as_attachment=True, download_name="historic.xlsx")


def get_Consommation(args):
    v_date_debut = args['date_debut']
    v_date_fin = args['date_fin']
    v_mags = args.get("magasin", [])

    expr_list = [
        (Historique.chrono >= v_date_debut),
        (Historique.chrono <= v_date_fin),
    ]
    if len(v_mags) > 0:
        expr_list.append(Historique.poste << v_mags)

    andexpr = reduce(operator.and_, expr_list)

    expr = ((Historique.type_mouvement == 'SOR') &
            (Historique.item_wms > 0) &
            andexpr)

    request_conso = Historique.select(Service.code, Service.libelle,
                                      Product.reference, Product.designation, Historique.fraction,
                                      fn.SUM(Historique.quantite_mouvement).alias('sum_mvt')) \
        .join(Product, JOIN.LEFT_OUTER,
              on=(Product.reference == Historique.reference)) \
        .switch(Historique) \
        .join(Magasin, JOIN.LEFT_OUTER, on=(Magasin.type_mag == Historique.poste)) \
        .switch(Historique) \
        .join(Service, JOIN.LEFT_OUTER, on=(Service.code == Historique.service)) \
        .where(expr) \
        .group_by(Service.code, Service.libelle,
                  Product.reference, Product.designation, Historique.fraction) \
        .order_by(Product.designation, Historique.fraction, Service.libelle)

    return request_conso


def get_obj_consommation(i):
    return {
        'wardCode': i.service.code,
        'wardLabel': i.service.libelle,
        'productCode': i.product.reference,
        'productLabel': i.product.designation,
        'productFraction': i.fraction,
        'quantities': i.sum_mvt
    }


@historic_blueprint.route('exportconso', methods=['PATCH'])
@jwt_required()
def export_consumption():
    data = json.loads(request.data)
    headers = data['headers']

    name = os.sep.join(
        [os.getcwd(), "tmp_export", "%s.xlsx" % uuid.uuid4()])
    consommations = get_Consommation(data)

    products = []
    wards = []

    for conso in consommations:
        obj = get_obj_consommation(conso)

        list_current = list(filter(lambda f: f['productCode'] == obj['productCode'] and
                                   f['productFraction'] == obj['productFraction'],
                                   products))
        if not any(list_current):
            current_product = {
                'productCode': obj['productCode'],
                'productLabel': obj['productLabel'],
                'productFraction': obj['productFraction'],
                'sum': 0,
                'wards': []
            }
            products.append(current_product)
        else:
            current_product = list_current[0]

        wards_current = list(filter(lambda f: f['code'] == obj['wardCode'],
                                    wards))
        if not any(wards_current):
            wards.append({
                'code': obj['wardCode'],
                'label': obj['wardLabel'],
            })

        current_product['wards'].append({
            'code': obj['wardCode'],
            'label': obj['wardLabel'],
            'qty': obj['quantities']
        })
        current_product['sum'] = current_product['sum'] + obj['quantities']

    generate_consumption_excel_file(name=name, wards=wards, items=products, translations=headers)
    return send_file(name, as_attachment=True)


def generate_consumption_excel_file(name, wards, items, translations):
    Path("tmp_export").mkdir(parents=True, exist_ok=True)
    writer = xlsxwriter.Workbook(name, {'constant_memory': True})
    logger.info(os.sep.join([os.getcwd(), "tmp_export", "%s.xlsx" % name]))

    worksheet = writer.add_worksheet()
    row = 0
    for obj in items:

        if row == 0:

            worksheet.write(row, 0, translations['product'])
            col_header = 1
            for ward in wards:

                if ward is not None:
                    col_name = "{code} - {label}".format(code=ward['code'], label=ward['label'])
                    if col_name is not None:
                        worksheet.write(row, col_header, col_name)
                col_header += 1
                worksheet.write(row, col_header, translations['total'])
            row += 1

        worksheet.write(row, 0, "{code} - {label} ({fraction})".format(code=obj['productCode'],
                                                                       label=obj['productLabel'],
                                                                       fraction=obj['productFraction']))
        col = 1

        for ward in wards:
            list_current = list(filter(lambda f: f['code'] == ward['code'],
                                       obj['wards']))

            if any(list_current):
                worksheet.write(row, col, list_current[0]['qty'])
            else:
                worksheet.write(row, col, 0)

            col += 1
        worksheet.write(row, col, obj['sum'])
        row += 1

    writer.close()
