import datetime
import json

from dateutil.relativedelta import relativedelta
from flask import Blueprint, request, session, send_file
from flask_jwt_extended import jwt_required
from median.models import Magasin, Product, Adresse, Stock, Reform, Cip, Ucd, Historique, \
    Gpao, Config
from median.models.products import ContainerFormat
from median.views import RawConfig
from peewee import DoesNotExist, JOIN, fn, Value
from common.status import HTTP_200_OK
import os
import uuid
from ressources.astus.utils import generate_excel_file

astus_filled_blueprint = Blueprint('astus_fill', __name__)


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

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

    stocks = get_all_replenishment(None)
    generate_excel_file(name, headers, stocks, get_obj_replenish)

    return send_file(name, as_attachment=True)


def get_obj_replenish(stock):
    obj = {
        'reference': stock.reference,
        'information': stock.information,
        'designation': stock.designation,
        'nb': str(stock.drawer),
        'format': stock.format,
        'adresse': stock.adresse,
        'totalPosage': str(stock.totalPosage),
        'maxQtyDrawer': str(stock.maxQtyDrawer),
        'maxQty': str(stock.maxQty),
        'stockAstus': str(stock.stockAstus),
        'MaxContainer': str(stock.MaxContainer),
        'WarningThreshold': str(stock.WarningThreshold),
    }
    return obj


def get_all_replenishment(mag, ref=None):
    A_remplir1 = Adresse.select(Adresse.emplacement.alias('reference'),
                                Adresse.adresse.alias('adresse'),
                                Adresse.format.alias('format'),
                                Value('Vide').alias('information'),
                                (fn.Count(Adresse.emplacement)).alias('nb')
                                ) \
        .where((Adresse.bloque_reappro == 0) &
               (Adresse.adresse != '') &
               ((mag is None) or (Adresse.magasin == mag.mag)) &
               (Adresse.etat == 'L')) \
        .group_by(Adresse.emplacement, Adresse.format)

    A_remplir2 = Stock.select(Stock.reference.alias('reference'),
                              Stock.adresse.alias('adresse'),
                              ContainerFormat.code.alias('format'),
                              Value('TAMPON').alias('information'),
                              (-1 * fn.Count(Stock.reference)).alias('nb')) \
        .join(ContainerFormat, on=((ContainerFormat.type_tray.is_null(False)) & (ContainerFormat.type_tray != '') &
                                   (Stock.contenant.startswith(ContainerFormat.type_tray)))) \
        .where(((mag is None) or (Stock.magasin == mag.mag)) &
               (Stock.zone_admin == 'TMP')) \
        .group_by(Stock.reference, ContainerFormat.type_tray)

    nb_peremption = RawConfig().read('k_eco_nb_péremption_astus')
    nb_peremption = nb_peremption.value if nb_peremption is not None else 10

    date_max = (datetime.datetime.now() + datetime.timedelta(days=nb_peremption)).strftime('%Y-%m-%d')
    a_remplir3 = Stock.select(Stock.reference.alias('reference'),
                              Stock.adresse.alias('adresse'),
                              ContainerFormat.code.alias('format'),
                              Value('Perime').alias('information'),
                              (fn.Count(Stock.reference)).alias('nb')) \
        .join(ContainerFormat, on=((ContainerFormat.type_tray.is_null(False)) & (ContainerFormat.type_tray != '') &
                                   (Stock.contenant.startswith(ContainerFormat.type_tray)))) \
        .where(((mag is None) or (Stock.magasin == mag.mag)) &
               (Stock.date_peremption < date_max) &
               (Stock.zone_admin != 'TMP')) \
        .group_by(Stock.reference, ContainerFormat.type_tray)

    A_remplir = A_remplir1 \
        .union_all(A_remplir2) \
        .union_all(a_remplir3)

    query = A_remplir \
        .select_from(A_remplir.c.reference, Product.designation.alias('designation'), A_remplir.c.information,
                     A_remplir.c.nb, A_remplir.c.format, A_remplir.c.adresse,
                     fn.SUM(A_remplir.c.nb).alias('drawer'),
                     (Adresse
                      .select(fn.SUM(Adresse.quantite_posage) * (Product.warn_pct / 100))
                      .where(((mag is None) or (Adresse.magasin == mag.mag)) &
                             (Adresse.emplacement == A_remplir.c.reference))).alias('WarningThreshold'),
                     fn.IFNULL(Reform.select(Reform.capacity)
                               .where((Reform.reference == A_remplir.c.reference) &
                                      (Reform.format == A_remplir.c.format)), -1
                               ).alias('MaxContainer'),
                     Stock.select(fn.IFNULL(fn.SUM(Stock.quantite), 0))
                     .where(((mag is None) or (Stock.magasin == mag.mag)) &
                            ((mag is None) or (Stock.zone_admin == mag.mag)) &
                            (Stock.reference == A_remplir.c.reference))
                     .alias('stockAstus'),

                     Adresse
                     .select(Adresse.quantite_posage)
                     .where((Adresse.emplacement == A_remplir.c.reference) &
                            (Adresse.format == A_remplir.c.format) &
                            ((mag is None) or (Adresse.magasin == mag.mag))).limit(1).alias('maxQtyDrawer'),
                     (fn.SUM(A_remplir.c.nb) *
                      Adresse
                      .select(Adresse.quantite_posage)
                      .where((Adresse.emplacement == A_remplir.c.reference) &
                             (Adresse.format == A_remplir.c.format) &
                             ((mag is None) or (Adresse.magasin == mag.mag))).limit(1)
                      ).alias('maxQty'),

                     Adresse.select(fn.SUM(Adresse.quantite_posage))
                     .where(((mag is None) or (Adresse.magasin == mag.mag)) &
                            (Adresse.emplacement == A_remplir.c.reference))
                     .alias('totalPosage')

                     ) \
        .join(Product, on=(Product.reference == A_remplir.c.reference)) \
        .join(Adresse, JOIN.LEFT_OUTER, on=(Adresse.adresse == A_remplir.c.adresse)) \
        .where(((ref is None) | (A_remplir.c.reference == ref)) &
               ((Adresse.bloque_reappro == 0) | (Adresse.bloque_reappro.is_null()))) \
        .group_by(A_remplir.c.reference, A_remplir.c.format) \
        .order_by(Product.designation) \
        .having(fn.SUM(A_remplir.c.nb) > 0)

    return query


@astus_filled_blueprint.route('/<string:astus_pk>', methods=['GET'])
@jwt_required()
def get_by_astus(astus_pk):
    try:
        mag = Magasin.get(pk=astus_pk)

        query = get_all_replenishment(mag)

        listStock = []

        for stock in query:
            listStock.append(get_obj_replenish(stock))

        return {'data': listStock}, HTTP_200_OK
    except DoesNotExist:
        return {'message': 'Magasin does not exist'}, 400


@astus_filled_blueprint.route('/<string:astus_pk>/testdrawer', methods=['PATCH'])
@jwt_required()
def testDrawer(astus_pk):
    data = json.loads(request.data)

    dataMatrix = data['code']
    askedFormats = data['format']

    ReqFormat = ContainerFormat.select().where(ContainerFormat.type_tray == dataMatrix[0:5])

    if (ReqFormat.first() is None) | (ReqFormat.count == 0):
        return {
            'alertMessage': 'astus.missing.format',
            'param': [dataMatrix]
        }, HTTP_200_OK
    elif (ReqFormat.first() is not None) & \
         (not any(list(filter(lambda f: f == ReqFormat.first().code, askedFormats)))):
        return {
            'alertMessage': 'astus.wrong.format',
            'param': [ReqFormat.first().code, askedFormats]
        }, HTTP_200_OK

    req = Stock.select(Magasin.libelle, Stock.adresse) \
        .join(Magasin, on=(Stock.magasin == Magasin.mag)) \
        .where((Magasin.eco_type == 'A') & (Stock.contenant == dataMatrix))

    if req.count() == 0:
        return {
            'format': ReqFormat.first().code,
        }, HTTP_200_OK
    else:
        return {
            'alertMessage': "FRM_REPL_SCANBAC_CHECK",
            'param': [dataMatrix, req.first().magasin.libelle, req.first().adresse]
        }, HTTP_200_OK


def CipValid(cip):
    try:
        req = Cip.select(Ucd.reference) \
            .join(Ucd, on=(Cip.ucd == Ucd.ucd)) \
            .where(Cip.cip == cip).get()
        res = req.ucd.reference.zfill(6)
    except DoesNotExist:
        res = ''
    return res


def UcdValid(ucd):
    try:
        req = Ucd.select(Ucd.reference).where(Ucd.ucd == ucd).get()
        res = req.reference.zfill(6)
    except DoesNotExist:
        res = ''
    return res


@astus_filled_blueprint.route('/<string:astus_pk>/testmed', methods=['GET'])
@jwt_required()
def testMed(astus_pk):
    data = request.args
    codeScann = data.get('code')
    reference = data.get('reference')

    CIP13 = ''
    UCD7 = ''
    UCD13 = ''
    NumSer = 'N/A'
    DateExp = ""
    NumLot = ""

    DebCIP13 = codeScann.find('0103400')
    DebUCD13 = codeScann.find('0203400')
    DebUCD7 = codeScann.find('0200000')

    # On n'a ni trouvé le début d'un code CIP13, ni un UCD13 ou 7 dans le code datamatrix
    if DebCIP13 == - 1 & DebUCD13 == - 1 & DebUCD7 == - 1:
        return {
            'alertMessage': 'astus.scanmed.error.unknown',
        }, HTTP_200_OK

    codeScann = codeScann.replace('\n', '')

    if DebCIP13 > 0:  # Pour gérer les codes datamatrix qui ne respectent pas la norme GS1 ]d2 au début du code
        vbCutString = codeScann[DebCIP13:codeScann.Length - DebCIP13]
    elif DebUCD13 > 0:
        vbCutString = codeScann[DebUCD13: codeScann.Length - DebUCD13]
    elif DebUCD7 > 0:
        vbCutString = codeScann[DebUCD7: codeScann.Length - DebUCD7]
    else:
        vbCutString = codeScann

    if DebCIP13 != -1:
        CIP13 = vbCutString[3: 13 + 3]
        ref = CipValid(CIP13)

    elif DebUCD13 != -1:
        UCD13 = vbCutString[3: 13 + 3]
        ref = UcdValid(UCD13)
    else:
        UCD7 = vbCutString[9: 7 + 9]
        ref = UcdValid(UCD7)

    if ref == '':
        return {
            'alertMessage': 'astus.scanmed.error.wrong.barcode',
        }, HTTP_200_OK
    elif (reference is not None) and (reference != ref):
        return {
            'alertMessage': 'astus.scanmed.error.wrong.product',
        }, HTTP_200_OK

    if reference is None:
        mag = Magasin.get(pk=astus_pk)
        req = get_all_replenishment(mag, ref)
        if not any(req):
            return {
                'alertMessage': 'astus.scanmed.error.wrong.product.list',
            }, HTTP_200_OK

    while len(vbCutString) > 1:
        vbCodeEnCours = vbCutString[0: 2]
        if vbCodeEnCours == "00":
            vbCutString = vbCutString[20:]
        elif vbCodeEnCours == "01":  # code cip
            vbCutString = vbCutString[16:]
        elif vbCodeEnCours == "02":  # code ucd
            vbCutString = vbCutString[16:]
        elif vbCodeEnCours == "11":  # date de production
            vbCutString = vbCutString[8:]
        elif vbCodeEnCours == "15":  # utiliser avant
            try:
                vbCutString = vbCutString[8:]
            except Exception:
                vbCutString = vbCutString[len(vbCutString):]

        elif vbCodeEnCours == "17":
            # Date de péremption
            DateTMP = vbCutString[2: 8]
            year = DateTMP[0:2]
            month = DateTMP[2:4]
            date = DateTMP[4:7]
            if date != "00":
                DateExp = datetime.datetime.strptime(DateTMP, '%y%m%d')
            else:
                DateTMP = f'{year}{month}01'

                DateExp = datetime.datetime.strptime(DateTMP, '%y%m%d')

                DateExp = DateExp + relativedelta(months=1) - datetime.timedelta(days=1)

            vbCutString = vbCutString[8:]
        elif vbCodeEnCours == "37":  # fraction
            if vbCutString.find(chr(29)) != -1:
                LongNumFraction = vbCutString.find(chr(29)) - 2
                vbCutString = vbCutString[LongNumFraction + 3:]
            elif vbCutString.find(chr(255)) != -1:
                LongNumFraction = vbCutString.find(chr(255)) - 3
                vbCutString = vbCutString[LongNumFraction + 4:]
            else:
                LongNumFraction = len(vbCutString) - 2
                vbCutString = vbCutString[LongNumFraction + 2:]
        elif vbCodeEnCours == "91":  # num série ECO-DEX
            if vbCutString.find(chr(29)) != -1:
                LongNumSerie = vbCutString.find(chr(29)) - 2
                vbCutString = vbCutString[LongNumSerie + 3:]
            elif vbCutString.find(chr(255)) != -1:
                LongNumSerie = vbCutString.find(chr(255)) - 3
                vbCutString = vbCutString[LongNumSerie + 4:]
            else:
                LongNumSerie = len(vbCutString) - 2
                vbCutString = vbCutString[LongNumSerie + 2:]

        elif vbCodeEnCours == "10":  # numéro de lot taille variable (20max)
            # Si chr(29) ou chr(255), on a atteind la fin du nom de lot (voir norme GS1)
            if vbCutString.find(chr(29)) != -1:  # 124?
                LongNumLot = vbCutString.find(chr(29)) - 2  # chr(124)  ?
                NumLot = vbCutString[2: LongNumLot + 2]
                vbCutString = vbCutString[LongNumLot + 3:]
            elif vbCutString.find(chr(255)) != -1:
                LongNumLot = vbCutString.find(chr(255)) - 3
                NumLot = vbCutString[2: LongNumLot + 2]
                vbCutString = vbCutString[LongNumLot + 4]
            else:
                LongNumLot = len(vbCutString) - 2
                NumLot = vbCutString[2: LongNumLot + 2]
                vbCutString = vbCutString[LongNumLot + 2:]
        elif vbCodeEnCours == "21":  # sérialisation
            # Si chr(29) ou chr(255), on a atteind la fin du n°série (voir norme GS1)
            if vbCutString.find(chr(29)) != -1:  # 124 ?
                LongNumSerialisation = vbCutString.find(chr(29)) - 2  # chr(124) - 2?
                if LongNumSerialisation > 2:
                    NumSer = vbCutString[2: LongNumSerialisation + 2]
                else:
                    NumSer = "N/A"

                vbCutString = vbCutString[-1 * (len(vbCutString) - LongNumSerialisation - 3):]
                # right(vbCutString, vbCutString.Length - LongNumSerialisation - 3)
            elif vbCutString.find(chr(255)) != -1:
                LongNumLot = vbCutString.find(chr(255)) - 3
                vbCutString = vbCutString[LongNumLot + 4:]
            else:
                LongNumLot = len(vbCutString) - 2
                NumSer = vbCutString[2: LongNumLot]
                # le numéro de sérialisaiton peut avoir jusqu'a 20 caractères
                if len(vbCutString) > 22:  # SI supérieur a 2+20 alors y'a autre choses dans le numéro de sérialisation
                    vbCutString = vbCutString[22:]
                else:
                    vbCutString = vbCutString[len(vbCutString):]

        else:
            vbCutString = vbCutString[len(vbCutString) - 1:]

    if DateExp != "":
        nb_peremption = RawConfig().read('k_eco_nb_péremption_astus')
        nb_peremption = nb_peremption.value if nb_peremption is not None else 10
        date_max = datetime.datetime.now() + datetime.timedelta(days=nb_peremption)
        if DateExp < date_max:
            return {
                'alertMessage': 'astus.scanmed.error.expired',
            }, HTTP_200_OK

    return {
        'reference': ref,
        'batch_number': NumLot,
        'serial': NumSer,
        'expirationDate': DateExp,
        'cip': CIP13,
        'ucd': (UCD13 != -1 if UCD13 else UCD7),
    }, HTTP_200_OK


def get_expiration_date():
    nb_peremption = RawConfig().read('k_eco_nb_péremption_astus')
    nb_peremption = nb_peremption.value if nb_peremption is not None else 10
    return datetime.datetime.now() + datetime.timedelta(days=nb_peremption)


@astus_filled_blueprint.route('/<string:astus_pk>/verifmed', methods=['GET'])
@jwt_required()
def verifMed(astus_pk):
    data = request.args
    cip = data.get('cip')
    expiration_date = data.get('expiration_date')
    batch = data.get('batch')
    serial = data.get('serial')
    reference = data.get('reference')

    expiration = datetime.datetime.strptime(expiration_date, '%Y-%m-%d')

    if expiration < get_expiration_date():
        return {
            'alertMessage': 'astus.scanmed.error.expired',
        }, HTTP_200_OK

    product_ref = CipValid(cip)
    if product_ref == '':
        return {
            'alertMessage': 'astus.scanmed.error.unknown.product',
        }, HTTP_200_OK

    if (reference is not None) and product_ref != reference:
        if product_ref == '':
            return {
                'alertMessage': 'astus.scanmed.error.wrong.product',
            }, HTTP_200_OK

    if reference is None:
        mag = Magasin.get(pk=astus_pk)
        req = get_all_replenishment(mag, product_ref)
        if not any(req):
            return {
                'alertMessage': 'astus.scanmed.error.wrong.product.list',
            }, HTTP_200_OK

    return {
        'reference': product_ref,
        'batch_number': batch,
        'serial': serial,
        'expirationDate': expiration_date,
        'cip': cip,
    }, HTTP_200_OK


@astus_filled_blueprint.route('/<string:astus_pk>/ask', methods=['POST'])
@jwt_required()
def ask(astus_pk):
    data = json.loads(request.data)
    mag = Magasin.get(pk=astus_pk)

    Gpao.create(
        chrono=datetime.datetime.now(),
        etat='A',
        ref=data['reference'],
        qte=int(data['maxQty']),
        type_mvt='R',
        designation=data['designation'],
        id_robot=mag.id_robot,
        id_zone=mag.id_zone,
        utilisateur=session['username'],
    )

    return {
    }, HTTP_200_OK


@astus_filled_blueprint.route('/<string:astus_pk>/askend', methods=['POST'])
@jwt_required()
def askEnd(astus_pk):
    date = datetime.datetime.now()
    Magasin.update(last_reap=date) \
        .where(Magasin.pk == astus_pk) \
        .execute()
    return {
        'last_reap': date
    }, HTTP_200_OK


@astus_filled_blueprint.route('/<string:astus_pk>/supplydrawer', methods=['POST'])
@jwt_required()
def supplyDrawer(astus_pk):
    data = json.loads(request.data)

    mag = Magasin.get(pk=astus_pk)
    userName = session['username']
    k_ua_pec = Config.select(Config.value).where(Config.propriete == 'k_ua_pec').get()

    k_ua_entree = Config.select(Config.value).where(Config.propriete == 'k_ua_entree').get()

    reqCip = Cip.select() \
        .join(Ucd, on=(Cip.ucd == Ucd.ucd)) \
        .where((Cip.cip == data['selectedCIP']) &
               (Ucd.reference == data['reference']))

    if reqCip.count() > 0:
        ucd = reqCip.first()
    else:
        return {
            'alertMessage': "astus.scanmed.wrong.cip",
        }, HTTP_200_OK

    expiration = datetime.datetime.strptime(data['selectedExpirationDate'], '%Y-%m-%dT%H:%M:%S').date()
    Stock.create(
        reference=data['reference'],
        quantite=data['selectedQtyDrawer'],
        lot=data['selectedBatch'],
        date_peremption=expiration,
        date_entree=datetime.datetime.now(),
        contenant=data['selectedDrawer'],
        magasin=mag.mag,
        ucd=ucd.ucd,
        adresse=mag.mag,
        zone_admin='TMP',
        capa=data['maxContainer'],
    )

    reqTotal = Stock.select(fn.IFNULL(fn.SUM(Stock.quantite), 0).alias('total')).where(
        Stock.reference == data['reference']).get()

    Historique.create(
        chrono=datetime.datetime.now(),
        reference=data['reference'],
        adresse=mag.mag,
        magasin=mag.mag,
        quantite_mouvement=data['selectedQtyDrawer'],
        quantite_totale=reqTotal.total,
        type_mouvement='ENT',
        lot=data['selectedBatch'],
        pmp=0,
        commentaire='CREATION BAC TAMPON VIA ASTUS PC',
        date_peremption=expiration,
        contenant=data['selectedDrawer'],
        poste='MEDIANWEB',
        ucd=ucd.ucd,
        info='CREATION BAC TAMPON',
        fraction=100,
        utilisateur=userName
    )

    Gpao.create(
        chrono=datetime.datetime.now(),
        poste='MEDIANWEB',
        etat='A',
        ref=data['reference'],
        qte=int(data['selectedQtyDrawer']) + int(data['selectedQtylost']),
        lot=data['selectedBatch'],
        type_mvt='E',
        ucd=ucd.ucd,
        dest=k_ua_entree.value,
        tperemp=expiration,
        id_robot=mag.id_robot,
        id_zone=mag.id_zone,
        magasin=mag.mag,
    )

    if int(data['selectedQtylost']) > 0:
        Historique.create(
            chrono=datetime.datetime.now(),
            reference=data['reference'],
            adresse=mag.mag,
            magasin=mag.mag,
            quantite_mouvement=-1 * int(data['selectedQtylost']),
            quantite_totale=reqTotal.total,
            type_mouvement='INV',
            lot=data['selectedBatch'],
            pmp=0,
            commentaire='PERTE CREATION BAC VIA ASTUS PC',
            date_peremption=expiration,
            contenant=data['selectedDrawer'],
            poste='MEDIANWEB',
            ucd=ucd.ucd,
            info='PERTE EN CREATION BAC',
            fraction=100,
            dest=k_ua_pec.value,
            utilisateur=userName
        )

        Gpao.create(
            chrono=datetime.datetime.now(),
            etat='A',
            ref=data['reference'],
            qte=-1 * int(data['selectedQtylost']),
            fraction=100,
            lot=data['selectedBatch'],
            type_mvt='I',
            ucd=ucd.ucd,
            dest=k_ua_entree,
            tperemp=expiration,
            id_robot=mag.id_robot,
            id_zone=mag.id_zone,
            magasin=mag.mag,
        )

    return {
    }, HTTP_200_OK
