import json
import logging
import datetime
import operator
import time
from functools import reduce

from flask import Blueprint, request, session
from flask_jwt_extended import jwt_required
from median.models import (
    FItem,
    Gpao,
    Historique,
    Magasin,
    Stock,
    DispensationItem,
    Dispensation,
    Service,
    ListeModel,
    FListe,
    Patient,
    Sejour,
    Prescription,
    Product,
)
from median.constant import HistoryType, TypeServiListe, PatientGlobal, EtatListe, TypeListe
from common.status import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR
from median.views import RawConfig
from peewee import fn, DoesNotExist, JOIN
from common.util import get_counter

acced_output_blueprint = Blueprint("acced_output", __name__)

logger = logging.getLogger("median")


@acced_output_blueprint.route("create/<string:ref>", methods=["POST"])
@jwt_required()
def create_list(ref):
    data = json.loads(request.data)

    _ucd = data["ucd"]
    _lot = data["lot"]
    _ward_code = data["ward"]
    _acceds = data["acced"]

    try:
        # récupérer les produits en stock ayant ce numéro de lot
        refs_in_stock = (
            Stock.select(Magasin.type_mag, Stock.fraction, Stock.reference, fn.SUM(Stock.quantite).alias("qty"))
            .join(Magasin, on=Stock.magasin == Magasin.mag)
            .where(
                ((Stock.reference == ref) | (Stock.ucd == _ucd)) & (Stock.lot == _lot) & (Magasin.type_mag << _acceds)
            )
            .group_by(Stock.magasin, Stock.reference)
        )

        if refs_in_stock.count() < len(_acceds):
            return {"message": "batch.output.error.no_reference"}, HTTP_500_INTERNAL_SERVER_ERROR

        try:
            service = Service.select(Service.code).where(Service.code == _ward_code)
        except Exception as error:
            logger.error(error.args)
            return {"message": "batch.output.error.no_ward"}, HTTP_500_INTERNAL_SERVER_ERROR

        for acced in _acceds:
            refs = list(filter(lambda s: s.magasin.type_mag == acced, refs_in_stock))

            try:
                list_label = "{store}-{ward} LOT A SORTIR {batch}".format(ward=_ward_code, store=acced, batch=_lot)
                _existing_disp = (
                    Dispensation.select()
                    .where((Dispensation.liste.startswith(list_label)) & (Dispensation.selectionne == 0))
                    .get()
                )
                list_label = _existing_disp.liste
                _existing_disp.nb_item = _existing_disp.nb_item + len(refs)
                _existing_disp.save()

            except DoesNotExist:
                list_label = "{store}-{ward} LOT A SORTIR {batch}-{inc}".format(
                    ward=_ward_code, store=acced, batch=_lot, inc=get_counter("LIST_GLOBALE")
                )

                Dispensation.create(
                    fusion="SORTIE DE LOTS",
                    liste=list_label,
                    mode=TypeListe.Output.value,
                    service=service,
                    type_servi=TypeServiListe.GlobaleBoite.value,
                    id_servi=2,
                    nb_item=len(refs),
                    etat=EtatListe.Vierge.value,
                    date_creation=datetime.datetime.now(),
                    num_ipp=PatientGlobal.Ipp.value,
                    num_sej=PatientGlobal.Sejour.value,
                    sous_secteur=acced,
                )

            count_item = (
                DispensationItem.select(fn.Count(DispensationItem.pk).alias("count"))
                .where(DispensationItem.liste == list_label)
                .get()
            )

            for r in refs:
                nb_item = count_item.count
                DispensationItem.create(
                    liste=list_label,
                    item=str(nb_item + 1).zfill(6),
                    mode=TypeListe.Output.value,
                    reference=r.reference,
                    qte_dem=r.qty,
                    fraction=r.fraction,
                    lot=_lot,
                    dest=service,
                    type_servi=TypeServiListe.GlobaleBoite.value,
                    id_servi=2,
                    etat=EtatListe.Vierge.value,
                    sous_secteur=acced,
                    num_ipp=PatientGlobal.Ipp.value,
                    num_sej=PatientGlobal.Sejour.value,
                )
                nb_item = nb_item + 1

        logger.info("Création d'une liste de sortie de lots à retirer... REUSSI")
        return "Success"

    except Exception as error:
        logger.error(error.args)
        return {"message": "ui.generic.error"}, HTTP_500_INTERNAL_SERVER_ERROR


@acced_output_blueprint.route("deletelists", methods=["POST"])
@jwt_required()
def deleteElts():
    data = json.loads(request.data)
    logger.info("Start delete lists by %s " % session["username"])

    try:
        for d in data:
            current_list = ListeModel.get(pk=d)
            logger.info("delete list and items %s" % current_list.liste)
            if current_list.selectionne == 1:
                return {"message": "acced.list.output.delete.locked", "args": [current_list.origine_sel]}, HTTP_200_OK
            else:
                current_list.delete_instance()

        return "Success"
    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, HTTP_500_INTERNAL_SERVER_ERROR


@acced_output_blueprint.route("resetlists", methods=["POST"])
@jwt_required()
def resetElts():
    data = json.loads(request.data)
    logger.info("Start reset lists by %s " % session["username"])
    logger.info("ids: %s" % ",".join([str(d) for d in data]))
    for d in data:
        ListeModel.get_by_id(d).reset_picking()
    logger.info("Start reset lists by %s " % session["username"])
    return "Success"


@acced_output_blueprint.route("deleteitem", methods=["DELETE"])
@jwt_required()
def deleteItem():
    """Delete one item by PK"""
    logger.info(request.data)
    data = json.loads(request.data)

    try:
        logger.info("delete item <%s>[%s]: f_item.pk %i" % (data["pk"], data["item"], data["item_pk"]))
        itm = FItem.get(pk=data["item_pk"])

        mag = Magasin.select(Magasin).where(Magasin.eco_type == "C").order_by(Magasin.pk)
        qte_tot = Stock.select(fn.SUM(Stock.quantite)).where(Stock.reference == itm.reference).scalar()

        # Create a GPAO line
        logger.info("Create a GPAO line for item %i in list [%s]" % (data["item_pk"], itm.liste))
        Gpao.create(
            chrono=datetime.datetime.now(),
            poste="MEDIANWEB",
            etat="A",
            ref=itm.reference,
            qte=0,
            type_mvt="S",
            liste=itm.liste,
            dest=itm.dest,
            user=session["username"],
            item=itm.item,
            info=itm.info,
            solde=1,
            item_wms=itm.item_wms,
            fraction=itm.fraction,
            qte_dem=itm.qte_dem,
            ipp=itm.num_ipp,
            sejour=itm.num_sej,
            id_robot=mag and mag[0].id_robot or 1,
            id_zone=mag and mag[0].id_zone or 1,
            magasin=mag and mag[0].mag or "ST1",
        )

        default_null_time = datetime.time(0, 0, 0)

        # Create an history
        logger.info("Create a GPAO line for item %i" % data["item_pk"])
        Historique.create(
            chrono=datetime.datetime.now(),
            reference=itm.reference,
            adresse="",
            magasin=mag and mag[0].mag or "ST1",
            quantite_mouvement=0,
            quantite_totale=qte_tot,
            quantite_demande=itm.qte_dem,
            date_prise=itm.dtprise or default_null_time,
            heure=itm.heure,
            moment=itm.moment,
            service=itm.dest,
            liste=itm.liste,
            item=itm.item,
            type_mouvement=HistoryType.Sortie.value,
            pmp=0,
            poste="MEDIANWEB",
            ipp=itm.num_ipp,
            sejour=itm.num_sej,
            fraction=itm.fraction,
            item_wms=itm.item_wms,
            utilisateur=session["username"],
            info="MedianWeb -> Supp item",
            commentaire="Manual",
        )
        itm.delete_instance()

        nb_items = FItem.select(FItem.pk).where(FItem.liste == itm.liste).count()

        pk_list = None
        if nb_items == 0:
            lst = FListe.get(liste=itm.liste)
            pk_list = lst.pk
            lst.delete_instance()

        return {"result": nb_items, "deletedList": pk_list}, HTTP_200_OK

    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, HTTP_400_BAD_REQUEST


@acced_output_blueprint.route("", methods=["GET"])
@jwt_required()
def get_all():
    args = request.args
    _ipp = args["ipp"]
    logger.info("Récupérer les listes de sorties...")
    acced_list = _getPatient(_ipp, equipment="ACCED")
    # logger.info('Récupérer les listes de sorties... %s' % ucd_cip_list.count())
    return acced_list


@acced_output_blueprint.route("liste_sorties_patients/<string:pk_liste>", methods=["POST"])
@jwt_required()
def get_detail_patient(pk_liste):
    logger.info("Récupérer les items de listes de sorties de la liste : '%s'" % pk_liste)
    try:
        _items = (
            FListe.select(FListe, FItem, Product)
            .join(Patient, on=(FListe.num_ipp == Patient.ipp))
            .switch(FListe)
            .join(FItem, on=(FListe.liste == FItem.liste).alias("itemList"))
            .switch(FListe)
            .join(Product, on=(FItem.reference == Product.reference).alias("reference"))
            .distinct()
            .where(
                (FListe.mode == TypeListe.Output.value)
                & (Patient.pk == pk_liste)
                & (FListe.type_servi != TypeServiListe.Exotique.value)
            )
        )

        if len(_items) == 0:
            logger.error("No list for id %s" % pk_liste)
            return {"data": []}, HTTP_200_OK

        output_list = []
        for item in _items:
            if item.ddeb is None:
                dateElt = None
            else:
                dateElt = item.ddeb.isoformat()

            output_list.append(
                {
                    "list_pk": item.pk,
                    "list_name": item.liste,
                    "pk": item.itemList.pk,
                    "item": item.itemList.item,
                    "etat": item.itemList.etat,
                    "reference": item.reference.reference,
                    "fraction": item.itemList.fraction,
                    "designation": item.reference.designation,
                    "qte_demandee": item.itemList.qte_prescrite * item.itemList.fraction / 100,
                    "qte_servie": item.itemList.qte_serv * item.itemList.fraction / 100,
                    "moment": item.itemList.moment,
                    "heure": item.itemList.heure,
                    "risky": item.reference.risque,
                    "ifNeeded": item.itemList.readonly,
                    "date_deb": dateElt,
                    "reference_pk": item.reference.pk,
                }
            )

        return {"data": output_list}, HTTP_200_OK
    except Exception as error:
        logger.error(("Get reappro items Datatables raised an exception: ", error.args))
        return {"message": error.args}


@acced_output_blueprint.route("liste_sorties_items/<string:pk_liste>", methods=["POST"])
@jwt_required()
def get_detail_global(pk_liste):
    logger.info("Récupérer les items de listes de sorties de la liste : '%s'" % pk_liste)

    try:
        _items = (
            FListe.select(
                FListe.pk,
                FListe.liste,
                FListe.ddeb,
                FItem.pk,
                FItem.item,
                FItem.etat,
                FItem.fraction,
                FItem.qte_dem,
                FItem.qte_serv,
                FItem.moment,
                FItem.heure,
                Product.reference,
                Product.designation,
                Product.pk,
            )
            .join(Service, on=(FListe.service == Service.code))
            .alias("service")
            .switch(FListe)
            .join(FItem, on=(FListe.liste == FItem.liste).alias("itemList"))
            .switch(FListe)
            .join(Product, on=(FItem.reference == Product.reference).alias("reference"))
            .where(
                (FListe.mode == TypeListe.Output.value)
                & (Service.pk == pk_liste)
                & (FListe.num_ipp == PatientGlobal.Ipp.value)
                & (
                    FListe.type_servi
                    << [
                        TypeServiListe.GlobaleBoite.value,
                        TypeServiListe.Nominatif.value,
                        TypeServiListe.GlobalePilulier.value,
                    ]
                )
                & (FListe.zone_deb == "")
            )
        )

        if len(_items) == 0:
            logger.error("No list for id %s" % pk_liste)
            return {"data": []}, HTTP_200_OK

        output_list = []
        for item in _items:
            if item.ddeb is None:
                date_elt = None
            else:
                date_elt = item.ddeb.isoformat()

            output_list.append(
                {
                    "list_pk": item.pk,
                    "list_name": item.liste,
                    "reference": item.reference.reference,
                    "designation": item.reference.designation,
                    "pk": item.itemList.pk,
                    "item": item.itemList.item,
                    "etat": item.itemList.etat,
                    "fraction": item.itemList.fraction,
                    "qte_demandee": item.itemList.qte_dem,
                    "qte_servie": item.itemList.qte_serv,
                    "moment": item.itemList.moment,
                    "heure": item.itemList.heure,
                    "date_deb": date_elt if date_elt is not None else time.strftime("%Y-%m-%d"),
                    "reference_pk": item.reference.pk,
                }
            )

        return {"data": output_list}, HTTP_200_OK
    except Exception as error:
        logger.error(("Get reappro items Datatables raised an exception: ", error.args))
        return {"message": error.args}


@acced_output_blueprint.route("globale", methods=["POST"])
@jwt_required()
def create_global_box():
    try:
        args = request.json
        ref_pk = args["ref"]
        service = args["service"]
        quantite = args.get("quantite", 0)
        fraction = args.get("fraction", 100)
    except Exception as error:
        logger.error(error.args)
        return {"message": "call argument missing"}, 400

    try:
        product = Product.select(Product.reference).where(Product.pk == ref_pk).get()
    except DoesNotExist as error:
        logger.error(error.args)
        return {"message": "drugs reference not found"}, 400

    liste = "%s GLOBALE %s" % (datetime.date.today().strftime("%Y-%m-%d"), service)

    # Check if global patient exists and episode
    _check_patient_global()

    # Check if liste is already exists
    try:
        d = FListe.get(liste=liste)

    except DoesNotExist:
        d = FListe(
            liste=liste,
            date_creation=datetime.datetime.now(),
            mode=TypeListe.Output.value,
            etat=EtatListe.Vierge.value,
            fusion="SORTIE GLOBALE BOITE PASS",
            service=service,
            nb_item=1,
            date_modification=datetime.datetime.now(),
            type_servi=TypeServiListe.GlobaleBoite.value,
            id_servi=2,
            num_ipp=PatientGlobal.Ipp.value,
            num_sej=PatientGlobal.Sejour.value,
        )
        d.save()

    except Exception as error:
        logger.error(error.args)
        return {"message": "list creation failed!"}, 400

    try:
        # Create f_item
        paged_item = FItem.select(FItem.item).where(FItem.liste == liste).order_by(FItem.item.desc())

        if len(paged_item) == 0:
            item = 1
        else:
            item = int(paged_item[0].item) + 1

        itm = FItem(
            liste=liste,
            reference=product.reference,
            mode=TypeListe.Output.value,
            etat=EtatListe.Vierge.value,
            item=str(item).zfill(6),
            qte_dem=quantite,
            fraction=fraction,
            dest=service,
            type_servi=TypeServiListe.GlobaleBoite.value,
            id_servi=2,
            num_ipp=PatientGlobal.Ipp.value,
            num_sej=PatientGlobal.Sejour.value,
        )
        itm.save()
    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, 400

    d.date_modification = datetime.datetime.now()
    d.save()

    return {"result": "ok", "id": d.pk, "item_id": itm.pk}


@acced_output_blueprint.route("globale_pilulier", methods=["POST"])
@jwt_required()
def create_global_pillbox():
    try:
        args = json.loads(request.data)
        service = args["service"]
        ref_pk = args["ref"]
        quantite = args["quantite"]
        fraction = args["fraction"]
        mode = args["mode"]
    except Exception as error:
        logger.error(error.args)
        return {"message": "call argument missing"}, 400

    try:
        product = Product.select(Product.reference).where(Product.pk == ref_pk).get()
        ref = product.reference
    except DoesNotExist as error:
        logger.error(error.args)
        return {"message": "drugs reference not found"}, 400

    paged_prescription = (
        Prescription.select(Prescription.pk)
        .where(Prescription.ipp == PatientGlobal.Ipp.value)
        .order_by(Prescription.pk)
    )

    if len(paged_prescription) > 0:
        id_presc = paged_prescription[0].pk
    else:
        p = Prescription(ipp=PatientGlobal.Ipp.value, sejour=PatientGlobal.Sejour.value)
        p.save()
        paged_prescription = (
            Prescription.select(Prescription.pk)
            .where(Prescription.ipp == PatientGlobal.Ipp.value)
            .order_by(Prescription.pk)
        )
        id_presc = paged_prescription[0].pk

    paged_liste = (
        FListe.select(FListe.ddeb)
        .where((FListe.type_servi == TypeServiListe.GlobalePilulier.value) & (FListe.service == service))
        .order_by(FListe.ddeb.desc())
    )

    if len(paged_liste) > 0:
        ddeb = paged_liste[0].ddeb + datetime.timedelta(days=1)
        liste_date = ddeb
        ddeb = str(ddeb)
        ddeb = ddeb[0:10]
        liste = "%s GLOBALE PILULIER %s" % (ddeb, service)
    else:
        liste = "%s GLOBALE PILULIER %s" % (datetime.date.today().strftime("%Y-%m-%d"), service)
        liste_date = datetime.date.today()

    # Check if liste is already exists
    try:
        d = FListe.get(liste=liste)

    except DoesNotExist:
        d = FListe(
            liste=liste,
            date_creation=datetime.datetime.now(),
            mode=TypeListe.Output.value,
            etat=EtatListe.Vierge.value,
            fusion="SORTIE GLOBALE PILULIER",
            service=service,
            nb_item=1,
            date_modification=datetime.datetime.now(),
            type_servi=TypeServiListe.GlobalePilulier.value,
            id_servi=0,
            ipp=PatientGlobal.Ipp.value,
            num_ipp=PatientGlobal.Ipp.value,
            num_sej=PatientGlobal.Sejour.value,
            ddeb=liste_date,
            id_prescription=id_presc,
        )
        d.save()

    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, 400

    try:
        # Create f_item
        itm_restant = quantite
        alveole_theo = 1
        if mode == "1":
            qte_max = 10
        else:
            qte_max = 5

        while int(itm_restant) > 0:
            if int(itm_restant) > qte_max:
                quantite_new = qte_max
            else:
                quantite_new = itm_restant

            if alveole_theo > 4:
                alveole_theo = 1
                liste_date = liste_date + datetime.timedelta(days=1)
                liste = "%s GLOBALE PILULIER %s" % (liste_date.strftime("%Y-%m-%d"), service)
                try:
                    d = FListe.get(liste=liste)

                except DoesNotExist:
                    d = FListe(
                        liste=liste,
                        date_creation=datetime.datetime.now(),
                        mode=TypeListe.Output.value,
                        etat=EtatListe.Vierge.value,
                        fusion="SORTIE GLOBALE PILULIER",
                        service=service,
                        nb_item=1,
                        date_modification=datetime.datetime.now(),
                        type_servi=TypeServiListe.GlobalePilulier.value,
                        id_servi=0,
                        ipp=PatientGlobal.Ipp.value,
                        num_ipp=PatientGlobal.Ipp.value,
                        num_sej=PatientGlobal.Sejour.value,
                        ddeb=liste_date,
                        id_prescription=id_presc,
                    )
                    d.save()

            paged_item = FItem.select(FItem.item).where(FItem.liste == liste).order_by(FItem.item.desc())

            if len(paged_item) == 0:
                item = "000001"
            else:
                item = int(paged_item[0].item) + 1

            if alveole_theo == 1:
                moment = "matin"

            if alveole_theo == 2:
                moment = "midi"

            if alveole_theo == 3:
                moment = "soir"

            if alveole_theo == 4:
                moment = "coucher"

            itm = FItem(
                liste=liste,
                reference=ref,
                mode=TypeListe.Output.value,
                etat=EtatListe.Vierge.value,
                item=str(item).zfill(6),
                qte_dem=quantite_new,
                fraction=fraction,
                dest=service,
                type_servi=TypeServiListe.GlobalePilulier.value,
                id_servi=0,
                num_ipp=PatientGlobal.Ipp.value,
                num_sej=PatientGlobal.Sejour.value,
                alveole_theo=alveole_theo,
                dtprise=liste_date,
                moment=moment,
            )
            itm.save()
            item = int(item) + 1
            itm_restant = int(itm_restant) - qte_max
            alveole_theo = alveole_theo + 1
    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, 400

    d.date_modification = datetime.datetime.now()
    d.save()

    logger.info("mode: %s" % mode)

    return {"result": "ok", "id": d.pk}


@acced_output_blueprint.route("perimes", methods=["GET"])
@jwt_required()
def get_outdated():
    logger.info("Calcul d'une liste de sortie de périmés")

    args = request.args
    mag = args["mag"]
    date_peremption = args["date_peremption"]
    res = []

    try:
        _m = (
            Stock.select(
                Stock.reference,
                Stock.fraction,
                Stock.date_peremption,
                Stock.quantite,
                Stock.adresse,
                Product.designation,
            )
            .join(Product, on=(Product.reference == Stock.reference))
            .where((Stock.magasin == mag) & (Stock.date_peremption <= date_peremption))
        )

        for ls in _m:
            res.append(
                {
                    "ref": ls.reference,
                    "designation": ls.product.designation,
                    "fraction": ls.fraction,
                    "date_peremption": str(ls.date_peremption)[0:10],
                    "quantite": ls.quantite,
                    "adresse": ls.adresse,
                }
            )

    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, 400

    logger.info("Génération automatique d'une liste de périmés... REUSSI")

    return res


@acced_output_blueprint.route("perimes", methods=["POST"])
@jwt_required()
def post():
    res = request.get_json(force=True)
    type_mag = res["type_mag"]
    date_peremption = res["date_peremption"]

    try:
        service = RawConfig("TOUS").read(param="k_ua_perime").value
        logger.info("UF des périmés " + service)
    except Exception as error:
        logger.error(error.args)
        return {"message": "k_ua_pui n'est pas définit."}, 503

    liste = "%s PERIME AU %s" % (type_mag, date_peremption)

    # Check if liste is already exists
    try:
        d = FListe.get(liste=liste)

    except DoesNotExist:
        d = FListe(
            liste=liste,
            date_creation=datetime.datetime.now(),
            mode=TypeListe.Output.value,
            etat=EtatListe.Vierge.value,
            fusion="SORTIE DE PERIMES",
            service=service,
            nb_item=1,
            date_modification=datetime.datetime.now(),
            zone_fin=type_mag,
            sous_secteur=type_mag,
            type_servi=TypeServiListe.GlobaleBoite.value,
            id_servi=2,
            num_ipp=PatientGlobal.Ipp.value,
            num_sej=PatientGlobal.Sejour.value,
        )
        d.save()

    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, 400

    try:
        # Create f_item
        for r in res["lines"]:
            d.nb_item = d.nb_item + 1
            itm = FItem(
                liste=liste,
                reference=r["ref"],
                mode=TypeListe.Output.value,
                etat=EtatListe.Vierge.value,
                item=str(d.nb_item).zfill(6),
                qte_dem=r["qte"],
                fraction=r["frac"],
                tperemp=str(r["date"]),
                dest=service,
                type_servi=TypeServiListe.GlobaleBoite.value,
                id_servi=2,
            )
            itm.save()
    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, 400

    d.date_modification = datetime.datetime.now()
    d.save()

    return {"result": "ok", "id": d.pk}


def _getPatient(_ipp, equipment, criterias=[]):
    _out = []

    expr = FListe.mode == TypeListe.Output.value
    if equipment == "ACCED":
        expr = (
            expr
            & (
                FListe.type_servi
                << [
                    TypeServiListe.GlobaleBoite.value,
                    TypeServiListe.Nominatif.value,
                    TypeServiListe.GlobalePilulier.value,
                ]
            )
            & (FListe.zone_deb == "")
        )
    elif equipment == "ASTUS":
        expr = expr & (FListe.type_servi == TypeServiListe.Exotique.value) & (FListe.zone_deb << ["", "ASTUS"])

    if len(criterias) > 0:
        lst = list(
            map(
                lambda s: (
                    (Patient.nom.contains(s.strip()))
                    | (Patient.prenom.contains(s.strip()))
                    | (Patient.ipp.contains(s.strip()))
                    | (Service.code.contains(s.strip()))
                    | (Service.libelle.contains(s.strip()))
                ),
                criterias,
            )
        )
        expr = expr & reduce(operator.and_, lst)

    MagasinAlias = Magasin.alias("MagasinAlias")

    list_ids = (
        Patient.select(
            Patient.pk,
            Patient.ipp,
            Patient.nom,
            Patient.prenom,
            Patient.nom_jeune_fille,
            Patient.sexe,
            Patient.date_naissance,
            Sejour.sejour,
            Sejour.chambre,
            Sejour.lit,
            Magasin.libelle,
            Service.pk.alias("ward_pk"),
            Service.code,
            Service.libelle.alias("ward_label"),
            Prescription.ordre,
            FListe.selectionne,
            MagasinAlias.libelle.alias("working_equipment"),
            FListe.origine_sel,
            FListe.ddeb,
            FListe.zone_fin,
            FListe.sous_secteur,
            FListe.pk.alias("liste_pk"),
        )
        .distinct()
        .join(FListe, on=(Patient.ipp == FListe.num_ipp).alias("list_sortie"))
        .join(Service, on=(FListe.service == Service.code).alias("service"))
        .switch(Patient)
        .join(MagasinAlias, JOIN.LEFT_OUTER, on=MagasinAlias.type_mag == FListe.origine_sel)
        .switch(Patient)
        .join(Magasin, JOIN.LEFT_OUTER, on=Magasin.type_mag == FListe.zone_fin)
        .switch(Patient)
        .join(Sejour, JOIN.LEFT_OUTER, on=((Patient.ipp == Sejour.ipp) & (Sejour.sejour == FListe.num_sej)))
        .switch(Patient)
        .join(
            Prescription,
            JOIN.LEFT_OUTER,
            on=(Patient.ipp == Prescription.ipp)
            & (Sejour.sejour == Prescription.sejour)
            & (FListe.id_prescription == Prescription.pk),
        )
        .where(expr)
        .order_by(Service.libelle, Patient.prenom, Patient.nom)
        .objects()
    )

    for item in list_ids:
        if item.ipp == PatientGlobal.Ipp.value:
            output_item = next(filter(lambda f: f["type"] == "global" and f["pk"] == item.ward_pk, _out), None)
            if output_item is None:
                output_item = {
                    "type": "global",
                    "title": "global.output.label",
                    "working": item.selectionne,
                    "working_equipment": item.working_equipment,
                    "equipment": item.libelle,
                    "pk": item.ward_pk,
                    "service_label": item.ward_label,
                    "service_code": item.code,
                    "service_sous_secteur": item.sous_secteur,
                    "ipp": "",
                    "dates": [],
                }
                _out.append(output_item)
            if next(filter(lambda d: d["pk"] == item.liste_pk, output_item["dates"]), None) is None:
                output_item["dates"].append(
                    {
                        "date": item.ddeb and item.ddeb.strftime("%Y-%m-%d") or time.strftime("%Y-%m-%d"),
                        "pk": item.liste_pk,
                    }
                )

        else:
            list_item = list(filter(lambda f: f["ipp"] == item.ipp, _out))

            if not any(list_item):
                sous_secteur_text = None
                if item.sous_secteur:
                    service_data: Service = Service.get_or_none(
                        (Service.adr1 == item.code) & (Service.adr2 == item.sous_secteur)
                    )
                    if service_data:
                        sous_secteur_text = service_data.libelle
                    else:
                        sous_secteur_text = item.sous_secteur

                obj = {
                    "type": "patient",
                    "working": item.selectionne,
                    "working_equipment": item.working_equipment,
                    "service_pk": item.ward_pk,
                    "service_label": item.ward_label,
                    "service_code": item.code,
                    "service_sous_secteur_label": sous_secteur_text if sous_secteur_text else "",
                    "service_sous_secteur_code": item.sous_secteur,
                    "stay_num": item.sejour,
                    "stay_bed": item.lit,
                    "stay_room": item.chambre,
                    "title": item.ward_label,
                    "ipp": item.ipp,
                    "nom": item.nom,
                    "dates": [],
                    "prenom": item.prenom,
                    "patient_gender": item.sexe,
                    "maiden_name": item.nom_jeune_fille,
                    "birth_date": item.date_naissance.strftime("%Y-%m-%d"),
                    "prescription_order": item.ordre,
                    "pk": item.pk,
                }
                _out.append(obj)
            else:
                obj = list_item[0]

            obj["dates"].append({"date": item.ddeb.strftime("%Y-%m-%d"), "pk": item.liste_pk})

    return _out


def _check_patient_global():
    try:
        p = Patient.get(ipp=PatientGlobal.Ipp.value)
    except DoesNotExist:
        p = Patient()
        p.ipp = PatientGlobal.Ipp.value
        p.prenom = "GL"
        p.nom = "GL"
        p.sexe = "H"
        p.save()

    try:
        s = Sejour.get(ipp=PatientGlobal.Ipp.value, sejour=PatientGlobal.Sejour.value)
    except DoesNotExist:
        s = Sejour()
        s.ipp = p.ipp
        s.sejour = PatientGlobal.Sejour.value
        s.save()

    return (p, s)
