import datetime
import json
import socket
import operator
import requests
import time
from datetime import date
from urllib.parse import urlparse

from flask import Blueprint, request, session
from flask_jwt_extended import jwt_required

from median.models import (
    Magasin, Config, Product, ListeItemModel, Poste,
    ListeModel, Service, Stock, LotRetire,
    Ucd, LogEchangeRiedl, Printer)
from median.constant import TypeListe, EtatListe
from median.views import PrinterView, PrinterViewException
from common.status import (HTTP_200_OK, HTTP_201_CREATED, HTTP_415_UNSUPPORTED_MEDIA_TYPE, HTTP_400_BAD_REQUEST,
                           HTTP_405_METHOD_NOT_ALLOWED, HTTP_404_NOT_FOUND, HTTP_204_NO_CONTENT,
                           HTTP_500_INTERNAL_SERVER_ERROR)
from common.riedl import get_url_dispatcher, add_reference_inventory
from common.exception import RiedlDispatcherException
from common.templating import render_template
from median.views import RawConfig, RiedlView
from peewee import DoesNotExist
from playhouse.shortcuts import model_to_dict
from common.util import logger
from common.exception import RiedlPrinterException, PrinterException
from common.log import log_riedl
from common.util import get_counter, is_multiple
from functools import reduce

riedl_blueprint = Blueprint('riedl', __name__)


def get_diff(rield, base_riedl):
    obj = {}
    if rield['axis_state'] != base_riedl['axis_state']:
        obj['pk'] = rield['pk']
        obj['axis_state'] = rield['axis_state']

    if rield['dispatcher_state'] != base_riedl['dispatcher_state']:
        obj['pk'] = rield['pk']
        obj['dispatcher_state'] = rield['dispatcher_state']

    if rield['dispatcher_date'] != base_riedl['dispatcher_date']:
        obj['pk'] = rield['pk']
        obj['dispatcher_date'] = rield['dispatcher_date']

    if rield['door_state'] != base_riedl['door_state']:
        obj['pk'] = rield['pk']
        obj['door_state'] = rield['door_state']

    if rield['gripper_state'] != base_riedl['gripper_state']:
        obj['pk'] = rield['pk']
        obj['gripper_state'] = rield['gripper_state']

    if rield['mode_state'] != base_riedl['mode_state']:
        obj['pk'] = rield['pk']
        obj['mode_state'] = rield['mode_state']
    return obj


def get_riedls():
    mags = Magasin \
        .select(Magasin.pk, Magasin.mag, Magasin.type_mag, Magasin.avatar,
                Magasin.eco_type, Magasin.libelle, Magasin.type_machine,
                Config.select(Config.value)
                .where((Config.poste == Magasin.type_mag) & (Config.cle == 'status') &
                       (Config.propriete == 'k_rdl_state_axis')).alias('axis_state'),
                Config.select(Config.value)
                .where((Config.poste == Magasin.type_mag) & (Config.cle == 'status') &
                       (Config.propriete == 'k_rdl_state_dispatcher')).alias('dispatcher_state'),
                Config.select(Config.value)
                .where((Config.poste == Magasin.type_mag) & (Config.cle == 'status') &
                       (Config.propriete == 'k_rdl_state_dispatcher_date')).alias('dispatcher_date'),
                Config.select(Config.value)
                .where((Config.poste == Magasin.type_mag) & (Config.cle == 'status') &
                       (Config.propriete == 'k_rdl_state_door')).alias('door_state'),
                Config.select(Config.value)
                .where((Config.poste == Magasin.type_mag) & (Config.cle == 'status') &
                       (Config.propriete == 'k_rdl_state_gripper')).alias('gripper_state'),
                Config.select(Config.value)
                .where((Config.poste == Magasin.type_mag) & (Config.cle == 'status') &
                       (Config.propriete == 'k_rdl_state_mode')).alias('mode_state'),
                Config.select(Config.value)
                .where((Config.poste == Magasin.type_mag) & (Config.cle == 'status') &
                       (Config.propriete == 'k_rdl_maintenance')).alias('maintenance_state'),
                ) \
        .where(Magasin.eco_type == 'L') \
        .order_by(Magasin.type_mag)
    return [{
        'pk': m.pk,
        'mag': m.mag,
        'type_mag': m.type_mag,
        'eco_type': m.eco_type,
        'libelle': m.libelle,
        'type_machine': m.type_machine,
        'axis_state': m.axis_state,
        'dispatcher_state': m.dispatcher_state,
        'dispatcher_date': m.dispatcher_date,
        'door_state': m.door_state,
        'gripper_state': m.gripper_state,
        'mode_state': m.mode_state,
        'maintenance_state': m.maintenance_state,
        'avatar': m.avatar,

    } for m in mags]


def _rield_maintenance(type_mag, val):
    try:
        config = (Config.select()
                  .where((Config.poste == type_mag) &
                         (Config.cle == 'status') &
                         (Config.propriete == 'k_rdl_maintenance')).get())
    except DoesNotExist:
        config = Config()
        config.poste = type_mag
        config.cle = 'status'
        config.propriete = 'k_rdl_maintenance'

    config.value = val
    config.save()
    return {}, HTTP_204_NO_CONTENT


@riedl_blueprint.route('/<string:type_mag>/maintenance', methods=['PUT'])
@jwt_required()
def set_riedl_maintenance(type_mag):
    _rield_maintenance(type_mag=type_mag, val=1)
    return {}, HTTP_204_NO_CONTENT


@riedl_blueprint.route('/<string:type_mag>/active', methods=['PUT'])
@jwt_required()
def set_riedl_active(type_mag):
    _rield_maintenance(type_mag=type_mag, val=0)
    return {}, HTTP_204_NO_CONTENT


@riedl_blueprint.route('/<string:type_mag>/inventory', methods=['PUT'])
@jwt_required()
def generate_global_inventory(type_mag):
    try:
        rdl = RiedlView(type_mag)
        current_liste, _ = rdl.generate_inventory(session['username'])
    except Exception:
        logger.error("generate_global_inventory: Error when generate inventory for %s" % type_mag)
        return {'message': 'reference.rield.inventory.error.internal'}, HTTP_400_BAD_REQUEST

    try:
        url = get_url_dispatcher(type_mag)
    except RiedlDispatcherException:
        logger.error("get_inventory_riedl: Error when retrieve dispatcher URL")
        return {'message': 'reference.rield.inventory.error.internal'}, HTTP_400_BAD_REQUEST

    try:
        logger.info('Call list id %s (%s)' % (str(current_liste.pk), current_liste.liste))
        resp = requests.patch('%s/RiedlVMessage' % url, json={
            'id': current_liste.pk,
        }, verify=False)
        logger.info('Return dispatcher code: %s -> %s %s' % (resp.status_code, resp.url, resp.request.method))
    except (requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError):
        logger.error("get_inventory_riedl: Timeout on dispatcher dispatcher connection")
        return {'message': 'reference.rield.inventory.dispatcher.timeout'}, HTTP_400_BAD_REQUEST

    return {'message': 'reference.rield.inventory.success'}, HTTP_201_CREATED


@riedl_blueprint.route('/inventory', methods=['POST'])
@jwt_required()
def get_inventory_riedl():
    """Generate an inventory for the reference and all GTIN related"""
    try:
        data = json.loads(request.data)

        product_mag = data.get('mag', None)
        product_code = data.get('product', None)
    except Exception:
        logger.error("get_inventory_riedl: Error when parse arguments")
        return {'message': 'reference.rield.inventory.error.internal'}, HTTP_500_INTERNAL_SERVER_ERROR

    gtin_version_zero = []

    logger.info("Inventory request for %s on %s by %s" % (product_code, product_mag, session['username']))

    try:
        mag = Magasin.get(mag=product_mag)
    except DoesNotExist:
        logger.error("get_inventory_riedl: Error when parse arguments")
        return {'message': 'reference.rield.inventory.error.mag'}, HTTP_500_INTERNAL_SERVER_ERROR

    try:
        url = get_url_dispatcher(mag.type_mag)
    except RiedlDispatcherException:
        logger.error("get_inventory_riedl: Error when retrieve dispatcher URL")
        return {'message': 'reference.rield.inventory.error.internal'}, HTTP_500_INTERNAL_SERVER_ERROR

    try:
        pro = Product.get(reference=product_code)
        gtins = pro.gtin_list()
        for g in gtins:
            if g.dossier == "0" and g.cip not in gtin_version_zero:
                gtin_version_zero.append(g.cip)
        logger.info('Ask inventory to this GTIN: ' + ','.join(gtin_version_zero))
        logger.info("Url of the dispatcher: %s" % url)
        listDate = time.strftime("%Y%m%d-%H%M%S")

        # Create a list and item for this product, 1 item per CIP
        current_liste: ListeModel = None
        for gt in gtin_version_zero:
            current_liste = add_reference_inventory(
                mag.type_mag, product_code,
                gt, session['username'],
                listDate
            )

        try:
            items = ListeItemModel.select(ListeItemModel).where(
                ListeItemModel.mode == TypeListe.Inventory.value, ListeItemModel.liste == current_liste.liste
            ).order_by(ListeItemModel.item)
            logger.info("Number of GTIN to inventory %i" % len(items))
            logger.info("Send list to the dispatcher")
            if not items:
                raise RiedlDispatcherException()

            logger.info('Call list id %s (%s)' % (str(current_liste.pk), current_liste.liste))
            resp = requests.patch('%s/RiedlVMessage' % url, json={
                'id': current_liste.pk,
            }, verify=False)
            logger.info('Return dispatcher code: %s -> %s %s' % (resp.status_code, resp.url, resp.request.method))
        except Exception:
            raise RiedlDispatcherException()

    except RiedlDispatcherException:
        return {'message': 'reference.rield.inventory.error.internal'}, HTTP_500_INTERNAL_SERVER_ERROR

    return {'message': 'reference.rield.inventory.success'}, HTTP_201_CREATED


@riedl_blueprint.route('/all', methods=['GET'])
@jwt_required()
def get_all_riedl():
    return {
        'data': get_riedls()
    }, HTTP_200_OK


# TODO: remove this, use printer module
zpl_template = """^XA
    ^LS{offset}^FS
    ^FS^FS
    ^PON^FS
    ^KL3^FS

    ^FO50,30^GB372,731,4,B,2^FS
    ^FO106,96^A0R,52,36^FD{libelle1}^FS
    ^FO56,96^A0R,52,36^FD{libelle2}^FS
    ^FO360,336^A0R,38,28^FDPC: {gtin}^FS
    ^FO312,336^A0R,38,28^FDSN: {serial}^FS
    ^FO264,336^A0R,38,28^FDEXP: {expiry:%Y-%m}^FS
    ^FO216,336^A0R,38,28^FDLOT: {batch}^FS
    ^FO166,550^A0R,52,36^FDQTE: {quantity}^FS
    ^FO200,100^BXR,7,200,26,26,6,_^FD_101{gtin}17{expiry:%y%m%d}10{batch}_192{fraction}_1711{cip}^FS
    ^FO120,720^A0N,30,30^FDMEDIAN / DEENOVA^FS

    ^FO450,30^GB372,731,4,B,2^FS
    ^FO506,96^A0R,52,36^FD{libelle1}^FS
    ^FO456,96^A0R,52,36^FD{libelle2}^FS
    ^FO760,336^A0R,38,28^FDPC: {gtin}^FS
    ^FO712,336^A0R,38,28^FDSN: {serial}^FS
    ^FO664,336^A0R,38,28^FDEXP: {expiry:%Y-%m}^FS
    ^FO616,336^A0R,38,28^FDLOT: {batch}^FS
    ^FO566,550^A0R,52,36^FDQTE: {quantity}^FS
    ^FO600,100^BXR,7,200,26,26,6,_^FD_101{gtin}17{expiry:%y%m%d}10{batch}_192{fraction}_1711{cip}^FS
    ^FO520,720^A0N,30,30^FDMEDIAN / DEENOVA^FS

    ^FO850,30^GB372,731,4,B,2^FS

    ^FO906,96^A0R,52,36^FD{libelle1}^FS
    ^FO856,96^A0R,52,36^FD{libelle2}^FS
    ^FO1160,336^A0R,38,28^FDPC: {gtin}^FS
    ^FO1112,336^A0R,38,28^FDSN: {serial}^FS
    ^FO1064,336^A0R,38,28^FDEXP: {expiry:%Y-%m}^FS
    ^FO1016,336^A0R,38,28^FDLOT: {batch}^FS
    ^FO966,550^A0R,52,36^FDQTE: {quantity}^FS
    ^FO1000,100^BXR,7,200,26,26,6,_^FD_101{gtin}17{expiry:%y%m%d}10{batch}_192{fraction}_1711{cip}^FS
    ^FO920,720^A0N,30,30^FDMEDIAN / DEENOVA^FS

    ^PQ1^FS
    ^XZ
"""


@riedl_blueprint.route('/return', methods=['GET'])
@jwt_required()
def get():
    return {'message': 'ApiRiedlReturn::get not implemented'}, HTTP_405_METHOD_NOT_ALLOWED


@riedl_blueprint.route('/return', methods=['POST'])
@jwt_required()
def post():
    if not request.is_json:
        return {'message': 'Content-Type must be application/json'}, HTTP_415_UNSUPPORTED_MEDIA_TYPE

    content = request.get_json()

    try:
        ward = Service.get(code=content.get('ward', ''))
    except DoesNotExist:
        err_message = "Ward [%s] not found" % content.get('ward', '')
        logger.error(err_message)
        return {'message': err_message}, HTTP_400_BAD_REQUEST

    try:
        drug = Product.get(pk=content.get('drugs', ''))
    except DoesNotExist:
        err_message = "Drugs [%s] not found" % content.get('drugs', '')
        logger.error(err_message)
        return {'message': err_message}, HTTP_400_BAD_REQUEST

    try:
        mag = Magasin.get(type_mag=content.get('riedl', ''))
    except DoesNotExist:
        err_message = "Riedl [%s] not found" % content.get('riedl', '')
        logger.error(err_message)
        return {'message': err_message}, HTTP_400_BAD_REQUEST

    batch = content.get('batch', '-')

    if any(LotRetire.select(LotRetire.pk)
                    .join(Ucd, on=LotRetire.ucd == Ucd.ucd)
                    .where((LotRetire.lot == batch) & (Ucd.reference == drug.reference))):
        return {'message': 'riedl.error.withdrawnbatch'}, HTTP_400_BAD_REQUEST

    quantity = content.get('quantity', 0)
    batch = content.get('batch', '-')
    expiry = content.get('expiry', None)
    cip = content.get('cip', '')

    try:
        expiry = date.fromisoformat(expiry)
        today = datetime.datetime.now()

        if expiry <= today.date():
            return {'message': 'riedl.error.expiration_date.sup'}, HTTP_400_BAD_REQUEST

    except ValueError:
        err_message = 'expiry date error, need yyyy-mm-dd, receive %s' % (expiry,)
        logger.error(err_message)
        return {'message': err_message}, HTTP_400_BAD_REQUEST

    rdlview = RiedlView(mag.type_mag)
    ret_id = rdlview.create_ward_return(ward.code, drug.reference, quantity, batch, expiry, "", cip)

    return {'message': 'ApiRiedlReturn::get success', 'id': ret_id.pk}, HTTP_201_CREATED


@riedl_blueprint.route('/return', methods=['PUT'])
@jwt_required()
def put():
    return {'message': 'ApiRiedlReturn::put not implemented'}, HTTP_405_METHOD_NOT_ALLOWED


@riedl_blueprint.route('/return', methods=['DELETE'])
@jwt_required()
def delete():
    return {'message': 'ApiRiedlReturn::delete not implemented'}, HTTP_405_METHOD_NOT_ALLOWED


@riedl_blueprint.route('/printer', methods=['POST'])
@jwt_required()
def printer():
    if not request.is_json:
        return {'message': 'Content-Type must be application/json'}, HTTP_415_UNSUPPORTED_MEDIA_TYPE

    content = request.get_json()

    poste = content.get("poste", "")
    mode = content.get("mode", "")
    mode_id = int(content.get("id", 0))
    mode_test = int(content.get("test", 0))
    logger.info("printing mode: %s -> id: %i" % (mode, mode_id))

    try:
        Poste.get(poste=poste)
    except DoesNotExist:
        err_message = "Poste not found: %s" % poste
        logger.error(err_message)
        return {'message': err_message}, HTTP_400_BAD_REQUEST

    if mode not in ('list', 'item'):
        err_message = "Incorrect mode: %s" % mode
        logger.error(err_message)
        return {'message': err_message}, HTTP_400_BAD_REQUEST

    mag: Magasin = None
    default_printer: Printer = None
    try:
        mag = Magasin.get(Magasin.type_mag == poste, Magasin.eco_type == 'L')

    except DoesNotExist:
        logger.error("No warehouse found for %s" % poste)

    # try:
    #     printer_cfg = _printer_configuration(poste)
    # except RiedlPrinterException as e:
    #     err_message = e.message
    #     logger.error(err_message)
    #     return {'message': err_message}, HTTP_400_BAD_REQUEST
    try:
        default_printer = PrinterView.default_printer(mag)
        logger.info(f"Default printer for {mag.mag} is {default_printer.name}")

        with PrinterView(default_printer, 3, session.get("user_id", 0)) as p:
            tmpl, def_dict = p.printer_template("box_uncomplete")
            # import pdb; pdb.set_trace()
            if mode == 'list':
                # List mode detect, we print all items
                try:
                    lst_model = ListeModel.get(pk=mode_id)
                except DoesNotExist:
                    err_message = "List not exists: %i" % mode_id
                    logger.error(err_message)
                    return {'message': err_message}, HTTP_400_BAD_REQUEST

                itms = ListeItemModel.select(ListeItemModel).where(
                    ListeItemModel.liste == lst_model.liste, ListeItemModel.mode == lst_model.mode
                )
                for line in itms:
                    try:
                        if not mode_test:
                            zpl_content = render_template(tmpl, _compose_label_from_item(line, 0), json.loads(def_dict))
                            p.send(zpl_content)
                    except RiedlPrinterException as e:
                        err_message = e.message
                        logger.error(err_message)
                        return {'message': err_message}, HTTP_400_BAD_REQUEST

            if mode == 'item':
                # Print only one item
                try:
                    item_model = ListeItemModel.get(pk=mode_id)
                except DoesNotExist:
                    err_message = "Item not exists: %i" % mode_id
                    logger.error(err_message)
                    return {'message': err_message}, HTTP_400_BAD_REQUEST

                try:
                    if not mode_test:
                        zpl_content = render_template(
                            tmpl, _compose_label_from_item(item_model, 0), json.loads(def_dict))
                        p.send(zpl_content)

                except RiedlPrinterException as e:
                    err_message = e.message
                    logger.error(err_message)
                    return {'message': err_message}, HTTP_400_BAD_REQUEST

    except PrinterViewException as e:
        logger.error(f"PrinterViewException: {str(e)}")
    except RiedlPrinterException:
        logger.error("Error when retrieve printer configuration!")
    except PrinterException:
        logger.error("send to printer failed !")
    except Exception as e:
        logger.error("Config Key k_rdl_print_container cannot be retrieve")
        return {'message': str(e)}, HTTP_400_BAD_REQUEST

    return {"message": "Print send sucessfully", "printer": poste}, HTTP_201_CREATED


def _compose_label_from_item(
        itemId: ListeItemModel = None, offset=0, zpl_templ_file=''
) -> dict:
    """Compose a label in ZPL language"""
    try:
        logger.debug(f"Compose label for product {itemId.reference}")
        pro = Product.get(reference=itemId.reference)
    except DoesNotExist:
        logger.error(f"No product found for {itemId.reference}")
        return None

    label_datas = {
        'offset': offset,
        'reference': itemId.reference or '-',
        'libelle1': pro.designation[:30],
        'libelle2': pro.designation[30:60],
        'gtin': "{0:014}".format(int(itemId.contenant)),
        'cip': "{0:013}".format(int(itemId.cip)),
        'serial': '',
        'ucd': itemId.info or '',
        'expiry': itemId.tperemp,
        'batch': itemId.lot or '',
        'quantity': itemId.qte_dem or 0,
        'fraction': itemId.fraction or 100
    }
    logger.debug(label_datas)
    return label_datas


# TODO remove this, use printer configuration
def _printer_configuration(riedl=None) -> tuple:
    """
    Retrieve defautl printer associate to the RIEDL
    """
    try:
        cfg = RawConfig(riedl)
        printer_number = cfg.read('k_rdl_printer').value
    except Exception:
        printer_number = 1

    try:
        printer_key = "k_rdl_printer_adr_%i" % printer_number
        name_key = "k_rdl_printer_name_%i" % printer_number
        offset_key = "k_rdl_printer_offset_%i" % printer_number
        template_key = "k_rdl_tk_template_%i" % printer_number
        cfg = RawConfig('TOUS')

        pr_addr = cfg.read(printer_key).value
        pr_name = cfg.read(name_key).value
        pr_offset = cfg.read(offset_key).value
        pr_template = cfg.read(template_key).value
    except Exception as ex:
        logger.error('error retrieve printer conf %s' % repr(ex))
        raise RiedlPrinterException("Error to retrieve printer configuration!")
    return pr_addr, pr_name, pr_offset, pr_template


# TODO remove this, use printer configuration
def _send_to_printer(printer_address=None, printer_content=None):
    """Send ZPL content to the printer"""
    try:
        printer_url = urlparse(printer_address)
        host_port = printer_url.netloc.split(':')
        logger.info("Send to the printer %s:%s" % (host_port[0], host_port[1]))
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # TODO: add timeout as parameter
        s.settimeout(5)
        s.connect((host_port[0], int(host_port[1])))
        s.send(bytes(printer_content, "utf-8"))
        s.close()
    except Exception as e:
        logger.error("Printer error: %s" % str(e))
        raise RiedlPrinterException("Error to send label to printer configuration!" + str(e))
    return True


@riedl_blueprint.route('command_sorties', methods=['GET'])
@jwt_required()
def get_command():
    return "1"


@riedl_blueprint.route('/command_sorties', methods=['POST'])
@jwt_required()
def update_commande():
    """Send list to this order"""

    args = json.loads(request.data)
    liste = args['liste']
    priority = args.get('priority', 3)
    exit_out = args.get('exit', 1)
    res = {
        'liste': liste,
    }

    # Récupération de la liste pour savoir sur quel RIEDL la lancer
    try:
        logger.info("Check list %s informations" % liste)
        lst = ListeModel.get(
            ListeModel.mode == TypeListe.Output.value,
            ListeModel.liste == liste,
            ListeModel.zone_deb == 'RIEDL')

        # Check if Riedl is in maintenance mode
        logger.info("Check maintenance mode for launch %s" % liste)
        maintenance = Config.get_or_none(poste=lst.zone_fin, cle='status', propriete='k_rdl_maintenance')
        if maintenance and maintenance.value == "1":
            return {
                'message': 'Riedl is in maintenance mode, please retry later!',
                'i18n': 'riedl.command.unload.maintenance.detect'
            }, HTTP_400_BAD_REQUEST

        dispatcher = Config.get_or_none(poste=lst.zone_fin, cle='status', propriete='k_rdl_state_dispatcher')
        if dispatcher and dispatcher.value != "connect":
            return {
                'message': 'Dispatcher and MMS are not connected, please check and retry later!',
                'i18n': 'riedl.command.unload.dispatcher.not.connected'
            }, HTTP_400_BAD_REQUEST

        if lst.selectionne:
            # List already launch, we reject execution
            return {'message': 'list %s already launch for unload!' % (liste,)}, HTTP_400_BAD_REQUEST

        logger.info("Check Riedl command")
        if not lst.interface:
            # Generate a new counter
            logger.info("No RIEDL command number found, generate a new one")
            lst.interface = get_counter("RIEDL_ORDERCODE")

        if not lst.id_plateau:
            logger.info("Generate a container number")
            # Retrieve prefix
            tray_prefix = ""
            try:
                cfg = RawConfig('TOUS')
                tray_prefix = cfg.read('k_rdl_prefix_container').value
            except Exception:
                tray_prefix = ""
            # Retrieve code
            lst.id_plateau = '%s%010d' % (tray_prefix, int(get_counter("RIEDL_CONTAINER")))
        logger.info("New container number %s" % lst.id_plateau)
        lst.no_pilulier = exit_out
        lst.pos_pilulier = priority
        lst.ddeb = datetime.datetime.now()
        lst.etat = EtatListe.EnCours.value
        lst.username = session['username']
        lst.selectionne = 1
        lst.save()
        logger.info("List ready to launch")
    except DoesNotExist:
        logger.error('list %s for rield does not exists' % (liste,))
        return {'message': 'list %s for rield does not exists' % (liste,)}, HTTP_404_NOT_FOUND

    mag = None
    try:
        mag = Magasin.get(Magasin.type_mag == lst.zone_fin, Magasin.eco_type == 'L')
    except DoesNotExist:
        # TODO: THis case is not possible
        logger.error("No warehouse found for %s" % lst.zone_fin)

    dest_print = False
    try:
        dest = Service.get(code=lst.service)
        dest_print = dest.riedl_unload_auto_print
    except DoesNotExist:
        logger.error("No ward found for %s" % lst.service)

    try:
        itms = ListeItemModel.select(ListeItemModel).where(
            ListeItemModel.liste == lst.liste, ListeItemModel.mode == lst.mode
        )
        # for each items, check if quantity is a multiple of the full box
        for it in itms:
            stk = Stock.select(Stock).where(
                Stock.magasin == mag.mag, Stock.reference == it.reference,
                Stock.bloque == 0, Stock.quantite == Stock.capa).order_by(Stock.date_peremption)
            for st in stk:
                if is_multiple(it.qte_dem, st.quantite):
                    logger.info("Quantity for %s is a multiple (%i, %i)" % (
                        it.reference, int(it.qte_dem), int(st.quantite)))
                    it.num_face = 1
                    it.save()
                else:
                    logger.info("Quantity for %s is a not multiple (%i, %i)" % (
                        it.reference, int(it.qte_dem), int(st.quantite)))
                break  # parse only the first stock to retrieve quantity

    except DoesNotExist:
        logger.error("parse item return an error")

    res['riedl'] = lst.zone_fin
    res['exit'] = exit_out
    res['priority'] = priority

    # Check if we need to print the container label
    try:
        cfg = RawConfig('TOUS')
        if cfg.read('k_rdl_print_container').value == '1' or dest_print:
            # Print the label
            default_printer: Printer = PrinterView.default_printer(mag)
            logger.info(f"Default printer for {mag.mag} is {default_printer.name}")
            with PrinterView(default_printer, 3, session["user_id"]) as p:
                tmpl, _ = p.printer_template("container")
                zpl_content = render_template(tmpl, _compose_container_dict(lst, 0, 0))
                p.send(zpl_content)

            # TODO: in the future, use render templte with 3 arguments
    except PrinterViewException as e:
        logger.error(f"PrinterViewException: {str(e)}")
    except RiedlPrinterException:
        logger.error("Error when retrieve printer configuration!")
    except PrinterException:
        logger.error("send to printer failed !")
    except Exception:
        logger.error("Config Key k_rdl_print_container cannot be retrieve")

    try:
        # We launch teh standard list
        _launch_liste(lst.zone_fin, lst)
        log_riedl(
            session['username'], 'out_send_liste',
            'Lancement de la liste %s (%s)' % (lst.liste, lst.pk)
        )
    except ApiRiedlException:
        logger.error('dispatcher URL for rield %s does not exists' % (lst.zone_fin,))
        return {'message': 'dispatcher URL for rield %s does not exists' % (lst.zone_fin,)}, HTTP_404_NOT_FOUND

    return res


def _compose_container_dict(list_obj: ListeModel, label_shift: int = 0, label_top: int = 0):
    label_datas = {
        'label_shift': label_shift,
        'label_top': label_top,
        'container_code': list_obj.id_plateau,
        'ward_code': list_obj.service,
        'ward_label': '-',
    }
    try:
        serv = Service.get(code=list_obj.service, type_dest='SERVICE')
        label_datas['ward_label'] = serv.libelle
    except DoesNotExist:
        label_datas['ward_label'] = 'Inconnu'
    return label_datas


def _launch_liste(r_poste, list_obj):
    # Récupération de l'URL du dispatcher
    try:
        cfg = Config.get(poste=r_poste, cle='cfg', propriete='k_rdl_dispatcher_url')
    except DoesNotExist:
        logger.error('dispatcher URL for rield %s does not exists' % (r_poste,))
        raise ApiRiedlException('dispatcher URL for rield %s does not exists' % (r_poste,))

    # Envoi de la requete avec requests
    try:
        url = cfg.value
        if url.endswith('/'):
            url = url[:-1]
        requests.post('%s/RiedlListUpdate' % (url,), json={'id': list_obj.pk}, timeout=10)
    except requests.exceptions.RequestException as e:
        logger.error('Call dispatcher  %s failed' % (cfg.value,))
        return {'message': 'Call dispatcher  %s failed' % (cfg.value,), 'trace': str(e)}, HTTP_404_NOT_FOUND


def get_logs(data):
    try:
        p_sortBy = data.get('sortBy', 'pk')
        p_descending = data.get('descending', False)
        p_filter = data.get('filter', {})

        sort_column = getattr(LogEchangeRiedl, p_sortBy, LogEchangeRiedl.pk)
        if p_descending:
            sort_column = sort_column.desc()
        else:
            sort_column = sort_column.asc()

        if len(p_filter.items()) > 0:
            build_where = []
            for k, v in p_filter.items():
                if k == 'chrono':
                    # Special case : dates and date ranges
                    if 'from' in v:
                        # has a from and to property
                        build_where.append(getattr(LogEchangeRiedl, k) >= v['from'])
                        build_where.append(getattr(LogEchangeRiedl, k) <= v['to'])
                    else:
                        # one date only
                        build_where.append(getattr(LogEchangeRiedl, k) >= f'{v} 00.00.000')
                        build_where.append(getattr(LogEchangeRiedl, k) <= f'{v} 23:59:59')
                else:
                    build_where.append(getattr(LogEchangeRiedl, k).contains(v))

            where_elements = reduce(operator.and_, build_where)
        else:
            where_elements = None

        logs = LogEchangeRiedl.select().where(
            where_elements
        ).order_by(
            sort_column
        )

        return logs
    except Exception as e:
        logger.error('Fetching Riedl Logs failed')
        return {'message': 'Get Exchange Logs failed', 'trace': str(e)}, HTTP_400_BAD_REQUEST


@riedl_blueprint.route('/logs', methods=['PUT'])
@jwt_required()
def get_logs_for_table():
    try:
        jsonData = json.loads(request.data)
        logs = get_logs(jsonData)

        p_limit = jsonData.get('limit', 25)
        p_offset = jsonData.get('offset', 0)

        countBeforeLimit = logs.count()

        logsData = []
        for log in logs.limit(p_limit).offset(p_offset):
            logsData.append(model_to_dict(log, fields_from_query=logs))

        return {'logs': logsData, 'count': countBeforeLimit}, HTTP_200_OK

    except Exception as e:
        print(e)
        return {'message': 'Get Exchange Logs failed', 'trace': str(e)}, HTTP_400_BAD_REQUEST


class ApiRiedlException(Exception):
    pass
