__all__ = ["find_product_with_gtin", "get_product"]

import json
import logging
import datetime
import math
from random import random

from flask import Blueprint, request, session
from flask_jwt_extended import jwt_required
from median.models import Product, Ucd, Stock, Historique, Gpao, Magasin, Adresse, Seuil, FItem, FListe, Cip, Label
from median.views import RawConfig
from median.constant import PatientGlobal, MEDIANWEB_POSTE, CONFIG_WEB_CLE
from peewee import DoesNotExist, fn, JOIN
from median.database import mysql_db

from common.status import HTTP_200_OK, HTTP_400_BAD_REQUEST

from common.status import HTTP_404_NOT_FOUND, HTTP_503_SERVICE_UNAVAILABLE, HTTP_500_INTERNAL_SERVER_ERROR

from common.rest import WebResourceException, WebResource

product_blueprint = Blueprint("product", __name__)

logger = logging.getLogger("median")

INCORRECT_GTIN_MANAGEMENT = "incorrect"


def _get_gtin_management():
    gtin_management: RawConfig = RawConfig(MEDIANWEB_POSTE, CONFIG_WEB_CLE).read("k_web_gtin_management")
    if gtin_management:
        return gtin_management.value
    else:
        return False


@product_blueprint.route("unitunload", methods=["POST"])
@jwt_required()
def unload_unit_doses():
    args = request.json
    logger.info(args)
    logger.info("Sortie unitaire a l'adresse [%s]: quantite de: %s" % (args.get("emplacement"), args.get("qte", 0)))

    pk = args.get("pk", 0)
    qty_to_unload = int(args.get("qte", 0))
    service = args.get("service", False)
    magasin = args.get("magasin", False)
    stock_data = {}

    if not service:
        logger.error("unload_unit_doses service requis")
        return {"message": "ward is required!"}, HTTP_400_BAD_REQUEST

    if not magasin:
        logger.error("unload_unit_doses service requis")
        return {"message": "warehouse is required!"}, HTTP_400_BAD_REQUEST

    if qty_to_unload == 0:
        logger.error("unload_unit_doses quantite a zero")
        return {"message": "quantity cannot be zero"}, HTTP_400_BAD_REQUEST

    # recupération du mouvement de stock
    try:
        stk = Stock.get_by_id(pk)
    except DoesNotExist:
        logger.error("stock line not found: %s" % str(pk))
        return {"message": "stock line not found: %s" % str(pk)}, HTTP_400_BAD_REQUEST

    stock_data["ref"] = stk.reference
    stock_data["adresse"] = stk.adresse
    stock_data["qty_line"] = stk.quantite
    stock_data["lot"] = stk.lot
    stock_data["expiry"] = stk.date_peremption
    stock_data["ucd"] = stk.ucd
    stock_data["cip"] = stk.cip
    stock_data["serial"] = stk.serial
    stock_data["fraction"] = stk.fraction
    stock_data["mag"] = stk.magasin
    stock_data["contenant"] = stk.contenant
    stock_data["qte_blister"] = stk.qte_prod_blister

    adr = Adresse.get_or_none(adresse=stk.adresse)

    if qty_to_unload > stk.quantite:
        logger.error("unload_unit_doses quantite superieur a quantie en stock sur %s" % args.get("emplacement"))
        return {"message": "quantity cannot be greather than stock quantity"}, HTTP_400_BAD_REQUEST

    final_quantity = int(stock_data["qty_line"]) - int(qty_to_unload)
    # If quantity to unload equal to the stock line, we remove it
    if qty_to_unload == stk.quantite:
        logger.debug("Deletion of the stock line")
        Stock.delete().where(Stock.pk == pk).execute()

        nb_lignes_stocks = Stock.select(Stock.quantite).where(Stock.adresse == stk.adresse)

        if nb_lignes_stocks.count() == 0:
            logger.debug('''Update address, address become free: "%s"''' % (stk.adresse))
            Adresse.update({Adresse.etat: "L"}).where(Adresse.adresse == stk.adresse).execute()
    else:
        logger.info('change stock line quantity for pk: "%s", qty: "%s"' % (pk, qty_to_unload))
        qte_blister_bac = 0
        nb_dose_cut = 0

        if adr.format.startswith("TIROIR"):
            logger.info("format is a tiroir, we need to compute blister quantity")

            # If tiroir, we need to have a multiple of blister
            if final_quantity % int(stock_data["qte_blister"]) != 0:
                logger.error("final quantity must be a multiple of blister")
                return {"message": "final quantity must be a multiple of blister"}, HTTP_400_BAD_REQUEST

            qte_blister_bac = math.ceil(final_quantity / int(stock_data["qte_blister"]))
            nb_dose_cut = int(qty_to_unload) % int(stock_data["qte_blister"])
            if nb_dose_cut == 0:
                nb_dose_cut = stock_data["qte_blister"]

        Stock.update(
            {Stock.quantite: final_quantity, Stock.qte_blister_bac: qte_blister_bac, Stock.nb_dose_cut: nb_dose_cut}
        ).where((Stock.pk == pk)).execute()

    qte_tot = Stock.select(fn.SUM(Stock.quantite)).where(Stock.reference == stock_data["ref"])

    logger.debug("Ecriture dans la table historique")
    Historique.create(
        chrono=datetime.datetime.now(),
        reference=stock_data["ref"],
        adresse=stock_data["adresse"],
        quantite_mouvement=qty_to_unload,
        quantite_totale=qte_tot,
        service=service,
        type_mouvement="SOR",
        lot=stock_data["lot"],
        pmp=0,
        date_peremption=stock_data["expiry"],
        contenant=stock_data["contenant"],
        poste="MEDIANWEB",
        ucd=stock_data["ucd"],
        fraction=stock_data["fraction"],
        utilisateur=session["username"],
        serial=stock_data["serial"],
        commentaire="Manual",
        magasin=stock_data["mag"],
        ipp=PatientGlobal.Ipp.value,
        sejour=PatientGlobal.Sejour.value,
    )

    current_mag = Magasin.get(mag=magasin)

    logger.info("GPAO: Ajout d'un mouvement de type sortie")
    Gpao.create(
        chrono=datetime.datetime.now(),
        poste=current_mag.type_mag,
        sequence="MEDIANWEB",
        user=session["username"],
        etat="A",
        ref=stock_data["ref"],
        qte=qty_to_unload,
        lot=stock_data["lot"],
        type_mvt="S",
        dest=service,
        tperemp=stock_data["expiry"],
        fraction=stock_data["fraction"],
        id_robot=current_mag.id_robot,
        id_zone=current_mag.id_zone,
        ucd=stock_data["ucd"],
        cip=stock_data["cip"],
        contenant=stock_data["contenant"],
        magasin=current_mag.mag,
        ipp=PatientGlobal.Ipp.value,
        sejour=PatientGlobal.Sejour.value,
    )

    logger.info("Unit unload OK")

    return {"message": "OK"}, HTTP_200_OK


@product_blueprint.route("", methods=["POST"])
@jwt_required()
def create_product():
    args = json.loads(request.data)
    _ref = args["ref"]
    _designation = args["designation"]
    _com_med = args["com_med"]

    logger.info('Création de produit, ref: "%s"' % (_ref))

    if _ref:
        if not _isProductReferenceAvailable(_ref):
            logger.warning("La référence existe déjà")
            return {"status": "Error", "ref": _ref}
    else:
        logger.info("On crée une nouvelle référence")
        _ref = _generateProductReference()

    logger.info(
        'On crée le produit en base, ref: "%s", designation: "%s", com_med: "%s"', (_ref, _designation, _com_med)
    )
    Product.create(reference=_ref, designation=_designation, com_med=_com_med)

    return {"status": "Success", "ref": _ref}


@product_blueprint.route("<int:ref_pk>", methods=["GET"])
@jwt_required()
def get_product(ref_pk):
    logger.info(f"read drug: {ref_pk}")
    try:
        p: Product = Product.get(Product.pk == ref_pk)
        ref = p.reference
        stock_info = _GetProductStockInfo(ref)
        stock_by_magasin = _getStockByMagasin(ref)
        ucd_cip_list = _getProductUCDAndCIPListJSON(ref)

    except DoesNotExist:
        logger.error("""Le produit n'existe pas => Réf: "%s" """ % (ref_pk))
        return {"message": "Product does not exist"}, HTTP_404_NOT_FOUND
    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, HTTP_503_SERVICE_UNAVAILABLE

    # Get all fields that start with 'ref_' with their column names
    # 'x_adr_pref' for a specific field linked to the external storage, might need a review
    fields_dict = {}
    # Define prefixes and patterns to match field names
    field_patterns = ["alpha_", "num_", "adr_pref", "reap_mode", "com_med"]

    for field_name, field_obj in p._meta.fields.items():
        # Check if any pattern matches the field name
        if any(pattern in field_name for pattern in field_patterns):
            field_value = getattr(p, field_name)
            column_name = field_obj.column_name if hasattr(field_obj, "column_name") else field_name
            fields_dict[column_name] = field_value

    return {
        "id": p.pk,
        "reference": p.reference,
        "designation": p.designation,
        "dci": p.dci,
        "ucd": p.ucd,
        "desig_bis": p.desig_bis,
        "com_med": p.com_med if p.com_med is not None else "",
        "risk": p.risque,
        "narcotic": p.stup,
        "external": p.externe,
        "stock": stock_info["stock"],
        "stock_by_mag": stock_by_magasin,
        "ucd_cip_list": ucd_cip_list,
        "tiroir_bas": p.tiroir_bas,
        "fields": fields_dict,
        "adr_pre": p.adr_pref,
        "reap_mode": p.reap_mode,
    }


def _preprocess_gtin(gtin):
    # Préprocess gtin if needed
    if type(gtin) is str and len(gtin) == 14 and gtin[0] == "0":
        logger.info("Product : GTIN-14 starting with a 0 -> removal of the 0 char")
        gtin = gtin.removeprefix("0")

    if _get_gtin_management() == INCORRECT_GTIN_MANAGEMENT:
        try:
            # We cast to INT here, in order to be able to handle the various forms of "fake" CIP's
            # Such as :
            # 05012342345234 - This is a GTIN, we remove the first zero
            # 00000001234567 - This is a CNK filled with zeros, to be used in a GS1 datamatrix
            # 1234567 - A classic CNK, could be a UCD7, or more chars, whatever.
            newgtin = str(int(gtin))
            logger.info(
                f"Product : 13 chars in code and starting with at least 3 zeros"
                f"-> removal of all zeros : {gtin} -> {newgtin}"
            )
            gtin = newgtin
        except ValueError:
            logger.info(f"Product : GTIN is not a number : {gtin}")

    return gtin


@product_blueprint.route("find/<string:ref_gtin>", methods=["GET"])
@jwt_required()
def find_product_with_gtin(ref_gtin):
    new_gtin = _preprocess_gtin(ref_gtin)

    if _get_gtin_management() == INCORRECT_GTIN_MANAGEMENT:
        # This part is more flexible and less precise way of finding products (cf. note in gtin preprocess)
        max_dossier_subquery = Cip.select(fn.MAX(Cip.dossier)).where(Cip.cip.cast("SIGNED").contains(new_gtin))

        products = (
            Product.select(Product)
            .join(Ucd, on=(Product.reference == Ucd.reference))
            .join(Cip, on=(Cip.ucd == Ucd.ucd))
            .where(Cip.cip.cast("SIGNED").contains(new_gtin) & (Cip.dossier == max_dossier_subquery))
            .group_by(Product.reference)
        )
    else:
        products = (
            Product.select(Product)
            .join(Ucd, on=(Product.reference == Ucd.reference))
            .join(Cip, on=(Ucd.ucd == Cip.ucd))
            .where((Cip.cip == ref_gtin) & (Cip.dossier == 0))
            .distinct()
        )

    if products.count() == 1:
        product = products[0]
        return get_product(product.pk)
    elif products.count() > 1:
        errmsg = "Too many products found, database incoherent !"
        logger.error(errmsg)
        return {
            "message": errmsg,
            "error": "products.find.too_many_products",
        }, HTTP_200_OK
    elif products.count() <= 0:
        return {"message": "No product found !", "error": "external.unload.error.product_not_found"}, HTTP_200_OK

    return {}, HTTP_500_INTERNAL_SERVER_ERROR


@product_blueprint.route("find_only_ref/<string:ref_gtin>", methods=["GET"])
@jwt_required()
def find_product_with_gtin_no_CIP(ref_gtin):
    new_gtin = _preprocess_gtin(ref_gtin)

    if _get_gtin_management() == INCORRECT_GTIN_MANAGEMENT:
        # This part is more flexible and less precise way of finding products (cf. note in gtin preprocess)
        max_dossier_subquery = Cip.select(fn.MAX(Cip.dossier)).where((Cip.cip.cast("SIGNED") == new_gtin))

        products = (
            Product.select(Product, Ucd, Cip)
            .join(Ucd, on=(Product.reference == Ucd.reference))
            .join(Cip, on=(Cip.ucd == Ucd.ucd))
            .where((Cip.cip.cast("SIGNED") == new_gtin) & (Cip.dossier == max_dossier_subquery))
            .group_by(Ucd.reference)
        )

        if len(products) == 0:
            # Some products have a UCD but no CIP, let's try to find it...
            products = (
                Product.select(Product, Ucd)
                .join(Ucd, on=(Product.reference == Ucd.reference))
                .where(Ucd.ucd.cast("SIGNED") == new_gtin)
                .group_by(Ucd.reference)
            )

            if len(products) == 1:
                product: Product = products[0]
                newDate = datetime.datetime.now()
                # Found one!
                newCip: Cip = Cip()
                newCip.cip = new_gtin
                newCip.ucd = product.ucd.ucd
                newCip.qt_blister = 0
                newCip.qt_boite = 0
                newCip.dossier = 0
                newCip.qt_pass = 0
                newCip.qt_coupe = 0
                newCip.date_der_coupe = "0000-00-00 00:00:00"
                newCip.chrono = newDate
                newCip.update = newDate
                newCip.last_user = MEDIANWEB_POSTE
                newCip.save()

                # New product created, we'll tell the user that the product was not found
                # , we made a new link automatically, and then ask the user to scan once again
                return {
                    "message": "No product found, but a new CIP/UCD link was created, please try again."
                }, HTTP_404_NOT_FOUND
            elif len(products) > 1:
                # Too many possibilities, can't automatically create an association
                errmsg = (
                    "No product found, and too many possible solutions detected, "
                    "we can't automatically create a CIP/UCD link !"
                )
                logger.error(errmsg)
                return {
                    "message": errmsg,
                }, HTTP_404_NOT_FOUND
            else:
                # No product found at all
                errmsg = "No product found, and no UCD/CIP link could be created ! Please configure this product."
                logger.error(errmsg)
                return {
                    "message": errmsg,
                }, HTTP_404_NOT_FOUND

    else:
        products = (
            Product.select(Product)
            .join(Ucd, on=(Product.reference == Ucd.reference))
            .join(Cip, on=(Ucd.ucd == Cip.ucd))
            .where((Cip.cip == ref_gtin) & (Cip.dossier == 0))
            .distinct()
        )

    nb_products = len(products)

    if nb_products == 1:
        product = products.get()
        return {
            "id": product.pk,
            "reference": product.reference,
            "designation": product.designation,
            "ucd": product.ucd.ucd,
            "cip": product.ucd.gtin.cip if product.ucd.gtin is not None else "",
            "desig_bis": product.desig_bis,
            "com_med": product.com_med if product.com_med is not None else "",
            "risk": product.risque,
            "narcotic": product.stup,
        }, HTTP_200_OK
    elif nb_products > 1:
        # Get CIP values from each product found
        found_cips = [p.ucd.gtin.cip if hasattr(p.ucd, "gtin") and p.ucd.gtin is not None else "None" for p in products]
        cips_str = ", ".join(found_cips)

        errmsg = f"Too many products found, database incoherent! CIP values: {cips_str}"
        logger.error(errmsg)
        return {
            "message": errmsg,
            "error": errmsg,
        }, HTTP_200_OK
    elif nb_products <= 0:
        errmsg = "No product found ! "
        logger.error(errmsg)
        return {
            "message": errmsg,
            "error": errmsg,
        }, HTTP_200_OK

    return {}, HTTP_500_INTERNAL_SERVER_ERROR


@product_blueprint.route("<string:ref_pk>", methods=["PUT"])
@jwt_required()
def update_product(ref_pk):
    """Update drugs"""
    args = request.form

    skip_readonly_keys = []
    # Process regular form fields
    for arg_key in args.keys():
        label: Label = Label.get_or_none(Label.code == "ref_" + arg_key)
        if label and label.readonly == 1:
            skip_readonly_keys.append(arg_key)

    # Process fields from JSON
    fields_json = args.get("fields", "{}")
    fields_dict = {}
    if fields_json:
        try:
            fields_dict = json.loads(fields_json)
            for field_key in fields_dict.keys():
                label: Label = Label.get_or_none(Label.code == field_key.replace("x_", "ref_"))
                if label and label.readonly == 1:
                    skip_readonly_keys.append(field_key)
        except json.JSONDecodeError:
            logger.warning("Invalid JSON in fields parameter")

    try:
        r = WebResource()
        com_med = args["com_med"] if "com_med" in args else ""
        extern = r.boolean(args["external"] if "external" in args else "false")
        risk = r.boolean(args["risk"] if "risk" in args else "false")
        narco = r.boolean(args["narcotic"] if "narcotic" in args else "false")

        # Check if fields are readonly and get their values
        designation = (
            None
            if "designation" in skip_readonly_keys
            else r.mandatory(args.get("designation", ""), "Drug name is mandatory")
        )
        # desig_bis = None if "desig_bis" in skip_readonly_keys else r.boolean(args.get("desig_bis", "false"))
        # dci = None if "dci" in skip_readonly_keys else r.boolean(args.get("dci", "false"))

        # TODO: This will block the whole update process, as DCI is often empty!
        # We need to refactor this, and also define if we even should be able to change these sensible values
        ucd = None if "ucd" in skip_readonly_keys else args.get("ucd", "")

    except WebResourceException as e:
        logger.warning(e.message)
        return {"message": e.message}, HTTP_400_BAD_REQUEST

    logger.info('Drug editing, ref: "%s"' % ref_pk)

    if not ref_pk:
        logger.warning("Missing drug reference")
        return {"message": "Référence manquante!"}, HTTP_400_BAD_REQUEST

    if len(designation) > 100:
        logger.warning("drug description too long!")
        return {"message": "drug description too long!"}, HTTP_400_BAD_REQUEST

    try:
        p = Product.get(Product.pk == ref_pk)
        ref = p.reference

        # Build update dictionary with non-None values
        update_dict = {}
        update_dict[Product.com_med] = com_med
        update_dict[Product.risque] = risk
        update_dict[Product.stup] = narco
        update_dict[Product.externe] = extern
        if designation:
            update_dict[Product.designation] = designation
        # if desig_bis:
        #     update_dict[Product.desig_bis] = desig_bis
        # if dci is not None:
        #     update_dict[Product.dci] = dci
        if ucd:
            update_dict[Product.ucd] = ucd

        # Add free fields to update_dict
        free_fields = Label.select(Label.code, Label.readonly).where(Label.code.contains("ref_"))
        for field in free_fields:
            field_code = field.code
            x_field_code = field_code.replace("ref_", "x_")

            # Only update if the field is not readonly and exists in fields_dict
            if field.readonly == 0 and x_field_code in fields_dict:
                # Get the actual field name in Product model (e.g., 'num_1', 'alpha_2')
                field_name = field_code.replace("ref_", "")
                if hasattr(Product, field_name):
                    # Convert boolean values to 0/1 for storage
                    value = convert_to_db_value(fields_dict[x_field_code])

                    update_dict[getattr(Product, field_name)] = value
                    logger.info(f"Updating free field {field_name} with value {fields_dict[x_field_code]}")

        with mysql_db.atomic():
            n = Product.update(update_dict).where(Product.pk == ref_pk)
            n.execute()
    except DoesNotExist as error:
        logger.error(error.args)
        return {"message": error.args}, HTTP_404_NOT_FOUND
    except Exception as error:
        logger.error(error.args)
        return {"message": error.args}, HTTP_503_SERVICE_UNAVAILABLE

    logger.info("Drug modification with success. ref: %s" % ref)
    return {"result": "OK", "reference": ref, "method": "put"}, HTTP_200_OK


def _GetProductStockInfo(ref):
    st = (
        Product.select(Product, fn.SUM(Stock.quantite).alias("stock"))
        .join(Stock, on=(Product.reference == Stock.reference))
        .where(Product.reference == ref)
        .group_by(Product)
    )

    if not st:
        stock = 0
    else:
        stock = st[0].stock

    s = (
        Product.select(
            Product, fn.SUM(Seuil.stock_mini).alias("stock_mini"), fn.SUM(Seuil.stock_maxi).alias("stock_maxi")
        )
        .join(Seuil, on=(Product.reference == Seuil.reference))
        .group_by(Product)
    )

    return {"stock": stock, "stock_mini": s and s[0].stock_mini or "-", "stock_maxi": s and s[0].stock_maxi or "-"}


def _getStockByMagasin(ref):
    mags = (
        Magasin.select(Magasin.mag, Magasin.type_mag, fn.SUM(Stock.quantite).alias("stock"), Magasin.libelle)
        .join(Stock, JOIN.LEFT_OUTER, on=((ref == Stock.reference) & (Magasin.mag == Stock.magasin)))
        # .where(Magasin.eco_type == 'C')
        .group_by(Magasin.mag)
        .order_by(Magasin.eco_type)
    )

    _out = []
    for m in mags:
        if m.type_mag != "RETOUR":
            q = (
                FListe.select((fn.SUM(FItem.qte_dem - FItem.qte_serv)).alias("cmd"))
                .join(
                    FItem, on=((FListe.liste == FItem.liste) & (FListe.mode == FItem.mode) & (FItem.reference == ref))
                )
                .where(  # noqa
                    (FListe.mode == "E") & (FListe.etat == "V") & (FListe.zone_fin == m.type_mag)
                )
                .group_by(FListe.zone_fin)
            )

            cmd = q and q[0].cmd or 0
            if cmd < 0:
                cmd = 0
            _out.append(
                {
                    "mag": m.mag,
                    "type_mag": m.type_mag,
                    "libelle": m.libelle,
                    "stock": m.stock or 0,
                    "commande": cmd or 0,
                }
            )

    return _out


def _generateProductReference():
    while True:
        rand_ref = "P" + str(round(random() * 100000000))
        if _isProductReferenceAvailable(rand_ref):
            break
    return rand_ref


def _isProductReferenceAvailable(ref):
    if (Product.select(Product.reference).where(Product.reference == ref).count()) == 0:
        return True
    return False


def _getProductUCDAndCIPListJSON(ref):
    _out = []

    ucd_list = Ucd.select(Ucd.ucd).distinct().where(Ucd.reference == ref).order_by(Ucd.ucd)

    for u in ucd_list:
        ucd_cip_list = Cip.select(Cip.ucd, Cip.cip).distinct().where(Cip.ucd == u.ucd)
        logger.info("Lines : %s." % len(ucd_cip_list))
        _o = {"label": u.ucd, "header": "UCD", "children": []}
        for c in ucd_cip_list:
            _c = {"label": c.cip, "header": "CIP", "children": []}
            cip_version = Cip.select(Cip).where(Cip.ucd == u.ucd, Cip.cip == c.cip)
            for v in cip_version:
                _v = {
                    "label": v.dossier,
                    "box": v.qt_boite,
                    "pass": v.qt_pass,
                    "blister": v.qt_blister,
                    "header": "Version",
                    "icon": "file",
                    "children": [],
                }
                _c["children"].append(_v)
            _o["children"].append(_c)
        _out.append(_o)

    return _out


def convert_to_db_value(value):
    if isinstance(value, bool):
        return 1 if value else 0
    elif isinstance(value, str):
        if value.lower() in ("true", "false"):
            return 1 if value.lower() == "true" else 0
        else:
            return value or ""
    elif not value:
        return ""
    return value


@product_blueprint.route("/forme/selectbox", methods=["GET", "POST"])
@jwt_required()
def get_forms_in_products():
    # This returns an array of options for a reference field (see freefields in config_blueprint)
    # Due to its dynamic nature, it HAS to return an array, named "data", with "key" and "value" fields
    # The params HAVE to be the name of an attribute of the peewee Adresse model (ex: eco_type, not x_eco_type)

    params = None
    if request.method == "POST":
        if request.data:
            params = json.loads(request.data)

    query = Product.select(Product.forme).distinct()

    # Add where conditions based on params
    if params:
        for key, value in params.items():
            if hasattr(Product, key):
                query = query.where(getattr(Product, key) == value)
            else:
                logger.error(f"Product forme selectbox api : unknown attribute {key}")

    products = query.order_by(+Product.forme)
    return {
        "data": [
            {"value": product.forme, "label": product.forme if product.forme else "Standard"} for product in products
        ]
    }, HTTP_200_OK
