import json
import operator
from functools import reduce

from flask import Blueprint, request
from flask_jwt_extended import jwt_required
from peewee import fn

from median.models import Format, Reform, Product, Stock
from peewee import JOIN, DoesNotExist
from common.status import HTTP_204_NO_CONTENT, HTTP_200_OK

from ressources.equipments.externe import generate_external_box_code
import logging

format_blueprint = Blueprint("format", __name__)

logger = logging.getLogger("median.format")


@format_blueprint.route("<string:ref_pk>", methods=["PUT"])
@jwt_required()
def update(ref_pk):
    data = json.loads(request.data)

    capacity = float(data["capacity"]) if data.get("capacity") else 0

    try:
        reform = (
            Reform.select(Reform)
            .join(Format, on=Reform.format == Format.format)
            .join(Product, on=Reform.reference == Product.reference)
            .where((Format.pk == data["pk"]) & (Product.pk == ref_pk))
        ).get()

        # Delete the reform if capacity is null or 0
        if capacity <= 0:
            reform.delete_instance()
            return "deleted", HTTP_204_NO_CONTENT

    except DoesNotExist:
        # Don't create a new reform if capacity is null or 0
        if capacity <= 0:
            return "not created", HTTP_204_NO_CONTENT

        product = Product.get(Product.pk == ref_pk)
        reform = Reform()
        reform.format = data["format"]
        reform.reference = product.reference

    reform.capacity = capacity
    reform.save()

    return "success", HTTP_204_NO_CONTENT


@format_blueprint.route("", methods=["GET"])
@jwt_required()
def get_all_formats():
    req = Format.select(Format.pk, Format.format, Format.typeBac, Format.nb_div).order_by(Format.format)

    return {
        "list": [
            {"pk": item.pk, "format": item.format, "typeBac": item.typeBac, "capacity": item.nb_div} for item in req
        ]
    }, 200


@format_blueprint.route("get_used/<string:box_type>", methods=["GET"])
@jwt_required()
def get_used_box_serials(box_type):
    try:
        # Search for all boxes in stock, with the given type, and additional counter characters
        number_of_counter_chars = 3
        if box_type[:2] == "AP":
            number_of_quantity_chars = 2
        else:
            number_of_quantity_chars = 0

        # Query to find containers with the specified box_type prefix and exact length
        stocked_containers = (
            Stock.select(Stock.contenant, Stock.magasin, Stock.reference, fn.SUM(Stock.quantite).alias("quantite"))
            .where(
                (Stock.contenant.startswith(box_type))
                & (fn.LENGTH(Stock.contenant) == len(box_type) + number_of_quantity_chars + number_of_counter_chars)
            )
            .group_by(Stock.reference, Stock.contenant)
        )

        # Log the containers found with their quantities
        for container in stocked_containers:
            logger.info(
                f"Found container: {container.contenant} "
                f"with reference: {container.reference} "
                f"and quantity: {container.quantite}"
            )

        # Extract the container values and convert to a list
        containers_list = [
            {
                "container": container.contenant,
                "reference": container.reference,
                "mag": container.magasin,
                "quantity": container.quantite,
            }
            for container in stocked_containers
        ]

        return {"containers": containers_list}, 200

    except Exception as e:
        return {"error": str(e)}, 500


@format_blueprint.route("<string:ref_pk>", methods=["POST"])
@jwt_required()
def get_all(ref_pk):
    data = json.loads(request.data)
    search_list = data.get("criterias", [])
    andexpr = True

    if len(search_list) > 0:
        lst = list(map(lambda s: (Format.format.contains(s.strip())), search_list))
        search = reduce(operator.and_, lst)
        expr = reduce(operator.and_, [andexpr, search])
    else:
        expr = andexpr

    req = (
        Format.select(Format.pk, Format.format, Format.typeBac, Reform.capacity, Reform.ordre, Reform.nb_div)
        .join(Product, JOIN.LEFT_OUTER, on=(Product.pk == ref_pk))
        .switch(Format)
        .join(Reform, JOIN.INNER, on=(Reform.format == Format.format) & (Reform.reference == Product.reference))
        .where(expr)
        .order_by(Format.format)
    )

    return {
        "list": [
            {
                "pk": item.pk,
                "format": item.format,
                "division": item.reform.nb_div if hasattr(item, "reform") and item.reform is not None else None,
                "capacity": item.reform.capacity if hasattr(item, "reform") else None,
                "order": item.reform.ordre if hasattr(item, "reform") and item.reform is not None else None,
                "typeBac": item.typeBac,
            }
            for item in req
        ]
    }, 200


@format_blueprint.route("generate_external_format", methods=["GET"])
@jwt_required()
def generate_external_format():
    # This will make a virtual format, in order to let the user store products directly in the external store
    # In the same fashion as the passbox, with an incremental number.
    newBoxCode = generate_external_box_code()

    return {"boxCode": newBoxCode}, HTTP_200_OK
