import json
import logging
import operator
import os
import uuid
from functools import reduce
from pathlib import Path
from datetime import datetime, timedelta
from median.views import RawConfig

import xlsxwriter
from flask import Blueprint, request, send_file
from flask_jwt_extended import jwt_required
from median.constant import EcoType, TypeListe, AstusMachineType, PickingMachineType
# from marshmallow import Schema, fields
from median.models import FItem, FListe, Product, Magasin, Stock, Service, Seuil
# from peewee import DoesNotExist, JOIN
# from ressources.astus.utils import generate_excel_file

from common.status import HTTP_200_OK, HTTP_500_INTERNAL_SERVER_ERROR
from peewee import fn, JOIN

feasibility_blueprint = Blueprint('feasibility', __name__)

logger = logging.getLogger('median.feasibility')


def get_feasibility_detail_list(
        wards_code, search_list, type_servi,
        eco_types, thresholds, machine_types
):

    cfg = RawConfig('TOUS')
    nb_days = cfg.read('k_eco_nb_péremption_global').value
    new_date = datetime.now() + timedelta(days=nb_days)
    logger.info(f"number of days for pre expiry date: {nb_days}")
    if len(search_list) > 0:
        lst = list(map(lambda s: (
            (Product.designation.contains(s.strip())) |
            (Magasin.libelle.contains(s.strip())) |
            (Product.reference.contains(s.strip()))
        ), search_list))
        search = reduce(operator.and_, lst)
    else:
        search = True

    types_expr = list(map(lambda t: (Magasin.type_machine.startswith(t)), machine_types))
    types_expr = reduce(operator.or_, types_expr)

    if len(thresholds) == 1:
        if thresholds[0] == 0:  # avec seuil
            search = search & (Seuil.pk.is_null(False))
        elif thresholds[0] == 1:  # sans seuil
            search = search & (Seuil.pk.is_null(True))

    feasibility_list = FListe.select(
        Product.reference,
        Product.designation,
        Product.risque,
        FItem.readonly,
        # FListe.sous_secteur,
        Service.libelle,
        Service.code,
        Magasin.mag,
        Magasin.type_mag,
        Magasin.type_machine,
        Magasin.eco_type,
        Magasin.libelle,
        FItem.fraction,
        Seuil.stock_maxi,
        fn.SUM(FItem.qte_prescrite).alias('qte_prescrite')
    ).join(
        Service, on=FListe.service == Service.code
    ).switch(FListe).join(
        Magasin, on=True
    ).switch(FListe).join(
        FItem, on=(FItem.liste == FListe.liste) & (FItem.mode == FListe.mode)
    ).switch(FListe).join(
        Product, on=(Product.reference == FItem.reference)
    ).switch(FListe).join(
        Seuil, JOIN.LEFT_OUTER, on=(
            (Seuil.fraction == FItem.fraction) &
            (Seuil.reference == FItem.reference) &
            (Seuil.zone == Magasin.type_mag)
        )
    ).where(
        (Magasin.eco_type << eco_types) &
        (FListe.mode == TypeListe.Output.value) &
        (FListe.type_servi == type_servi) &
        (Service.code << wards_code) &
        types_expr &
        search
    ).group_by(
        Product.reference,
        Product.designation,
        FItem.readonly,
        Service.libelle,
        Service.code,
        FItem.fraction,
        Magasin.mag,
        Magasin.libelle,
        Magasin.type_mag,
        Seuil.stock_maxi
    ).order_by(
        Service.libelle, Magasin.libelle, Product.designation
    )

    list_res = []
    products = [{
        "storeCode": feasibility.magasin.mag,
        "storeLabel": feasibility.magasin.libelle,
        "reference": feasibility.product.reference,
        "type_machine": feasibility.magasin.type_machine,
        "eco_type": feasibility.magasin.eco_type,
        "designation": feasibility.product.designation,
        "fraction": feasibility.listeitemmodel.fraction,
        "qte_prescrite": feasibility.qte_prescrite,
        "drugs": feasibility.product.risque,
        "ifneeded": feasibility.listeitemmodel.readonly,
        # "subward": feasibility.sous_secteur,
        "stock_maxi": feasibility.seuil.stock_maxi if hasattr(feasibility, 'seuil') else None
    } for feasibility in feasibility_list]

    refs = list(map(lambda r: r['reference'], products))
    feasibility_stock = Stock.select(
        Stock.reference, Stock.fraction,
        Magasin.mag, fn.SUM(Stock.quantite).alias('total')
    ).join(
        Magasin, on=Magasin.mag == Stock.magasin
    ).where(
        (Stock.bloque == 0) & (Stock.reference << refs) & (Stock.date_peremption > new_date)
    ).group_by(
        Stock.reference, Stock.fraction, Magasin.mag
    )

    stocks = [{
        "storeCode": s.magasin.mag,
        "reference": s.reference,
        "total": s.total,
        "fraction": s.fraction,

    } for s in feasibility_stock]

    for product in products:
        obj_qteStock = list(filter(lambda s: (product['storeCode'] == s['storeCode']) &
                                             (product['reference'] == s['reference']) &
                                             (product['fraction'] == s['fraction']), stocks))
        qte_stock = 0
        if len(obj_qteStock) == 1:
            qte_stock = obj_qteStock[0]['total']

        list_stores = list(filter(lambda s: s['code'] == product['storeCode'], list_res))

        if not any(list_stores):
            store = {
                'label': product['storeLabel'],
                'code': product['storeCode'],
                'type_machine': product['type_machine'],
                'eco_type': product['eco_type'],
                'lines': []
            }
            list_res.append(store)
        else:
            store = list_stores[0]

        store['lines'].append({
            "reference": product['reference'],
            "designation": product['designation'],
            "fraction": product['fraction'],
            "qte_prescrite": product['qte_prescrite'],
            "ifneeded": product['ifneeded'],
            "drugs": product['drugs'],
            "stock_maxi": product['stock_maxi'],
            "qte_stock": qte_stock,
            "max_threshold": product['stock_maxi']
        })

    return list_res


@feasibility_blueprint.route('export', methods=['PATCH'])
@jwt_required()
def get_feasibility_export_excel():
    data = json.loads(request.data)
    wards_code = data['wards']
    search_list = data.get('criterias', [])
    translations = data['translations']
    thresholds = data.get('thresholds', [])

    listFeasibility = get_feasibility_detail_list(wards_code=wards_code,
                                                  thresholds=thresholds,
                                                  type_servi=_get_type_servi(data.get('equipmentType', '')),
                                                  search_list=search_list,
                                                  machine_types=_get_machine_types(
                                                      data.get('equipmentType', '')),
                                                  eco_types=_get_eco_types(data.get('equipmentType', '')), )

    Path("tmp_export").mkdir(parents=True, exist_ok=True)
    name = os.sep.join(
        [os.getcwd(), "tmp_export", "%s.xlsx" % uuid.uuid4()])
    writer = xlsxwriter.Workbook(name, {'constant_memory': True})
    logger.info(os.sep.join([os.getcwd(), "tmp_export", "%s.xlsx" % name]))
    for store in listFeasibility:
        worksheet = writer.add_worksheet(name=store['label'])
        row = 1
        worksheet.write(0, 0, translations.get('designation', None))
        worksheet.write(0, 1, translations.get('max_threshold', None))
        worksheet.write(0, 3, translations.get('stock_qty', None))
        worksheet.write(0, 2, translations.get('prescribed_qty', None))

        for line in store['lines']:
            worksheet.write(row, 0, "{code} - {label} ({fraction})".format(code=line['reference'],
                                                                           label=line['designation'],
                                                                           fraction=line['fraction']))
            worksheet.write(row, 1, line['max_threshold'])
            worksheet.write(row, 3, line['qte_stock'])
            worksheet.write(row, 2, line['qte_prescrite'])

            row = row + 1
    writer.close()
    return send_file(name, as_attachment=True)


@feasibility_blueprint.route('detail', methods=['POST'])
@jwt_required()
def get_feasibility_detail():
    try:
        data = json.loads(request.data)
        wards_code = data['wards']
        search_list = data.get('criterias', [])
        thresholds = data.get('thresholds', [])

        return ({
            "list": get_feasibility_detail_list(
                wards_code=wards_code,
                thresholds=thresholds,
                search_list=search_list,
                machine_types=_get_machine_types(
                    data.get('equipmentType', '')),
                type_servi=_get_type_servi(
                    data.get('equipmentType', '')),
                eco_types=_get_eco_types(data.get('equipmentType', ''))
            )
        }, HTTP_200_OK)

    except Exception as error:
        logger.error(error.args)
        return {
            'alertMessage': 'replenishment.line.delete.error',
            'param': []
        }, HTTP_500_INTERNAL_SERVER_ERROR


def _get_eco_types(equipment_type):
    eco_types = []

    if equipment_type.lower() == 'astus':
        eco_types = [EcoType.Astus.value]
    elif equipment_type.lower() == 'acced':
        eco_types = [EcoType.Cueillette.value]

    return eco_types


def _get_machine_types(equipment_type):
    machine_types = []

    if equipment_type.lower() == 'astus':
        machine_types = [e.value for e in AstusMachineType]
    elif equipment_type.lower() == 'acced':
        machine_types = [e.value for e in PickingMachineType]

    return machine_types


def _get_type_servi(equipment_type):
    type_servi = ''

    if equipment_type.lower() == 'astus':
        type_servi = 'EXOTIQUE'
    elif equipment_type.lower() == 'acced':
        type_servi = 'NOMINATIF'

    return type_servi


@feasibility_blueprint.route('', methods=['POST'])
@jwt_required()
def get_feasibility():
    try:
        data = json.loads(request.data)

        feasibility_list = FListe.select(
            Service.libelle,
            Service.code,
        ) \
            .join(Service, on=FListe.service == Service.code) \
            .switch(FListe) \
            .join(FItem, on=(FItem.liste == FListe.liste) & (FItem.mode == FListe.mode)) \
            .switch(FListe) \
            .where((FItem.readonly == 0)
                   & (FListe.mode == 'S')
                   & (FListe.type_servi == _get_type_servi(data.get('equipmentType', '')))) \
            .group_by(Service.libelle, Service.code) \
            .order_by(Service.libelle)

        list_res = []
        for feasibility in feasibility_list:

            list_ward = list(filter(lambda w: w['code'] == feasibility.service.code, list_res))

            if not any(list_ward):
                ward = {
                    'label': feasibility.service.libelle,
                    'code': feasibility.service.code,
                }
                list_res.append(ward)

        return {"list": list_res}, HTTP_200_OK

    except Exception as error:
        logger.error(error.args)
        return {
            'alertMessage': 'replenishment.line.delete.error',
            'param': []
        }, HTTP_500_INTERNAL_SERVER_ERROR
