import logging
from datetime import datetime

from flask import Blueprint, request, session
from flask_jwt_extended import jwt_required
from median.constant import TypeMouvementGpao, TypeEtatGpao, HistoryType, MYSQL_ZERO_DATE, MEDIANWEB_POSTE
from median.models import Historique, Gpao, Service, Gtin, Ucd, Seuil, Product
from median.models import Patient, Reform, Format, Stock, UnitDose
from peewee import DoesNotExist, JOIN
from playhouse.shortcuts import model_to_dict
from median.database import mysql_db
from common.util import get_counter

from common.status import (
    HTTP_200_OK,
    HTTP_400_BAD_REQUEST,
    HTTP_404_NOT_FOUND,
    HTTP_500_INTERNAL_SERVER_ERROR,
)
from common.util import mustHaveRights

external_returns_blueprint = Blueprint("external_returns", __name__)
logger = logging.getLogger("median.external")


RESSOURCE_NAME = "WEB_EXTERNAL_RETURNS"


@external_returns_blueprint.route("check", methods=["POST"])
@jwt_required()
@mustHaveRights(RESSOURCE_NAME, canView=True)
def check():
    data = request.get_json()

    try:
        serial = data.get("serial")

        # We look for both the needed "output" line and the "inventory" line, wich means it's already returned
        histos: Historique = Historique.select().where((Historique.serial == serial)).order_by(-Historique.chrono)

        # & (Historique.type_mouvement << [HistoryType.Sortie.value, HistoryType.Inventaire.value])

        last_exit: Historique = histos.where((Historique.type_mouvement == HistoryType.Sortie.value)).first()

        last_inventory: Historique = histos.where((Historique.type_mouvement == HistoryType.Inventaire.value)).first()

        if last_exit:
            # Is this returning product from the shelves?
            if last_exit.contenant[0] in ("A", "E"):
                pass
                # The contenant is a virtual shelve container, or a "posage"
            else:
                logger.warning(f"Serial {serial} is not from a shelve, but from a AP: {last_exit.contenant}")
                return {
                    "message": "This product is only returnable with an AidePick",
                    "ward": last_exit.service,
                }, HTTP_400_BAD_REQUEST

            # extract detailed info
            histo: Historique = last_exit
            patient: Patient = Patient.get_or_none(Patient.ipp == histo.ipp)
            service: Service = Service.get_or_none(Service.code == histo.service)

            # Empty variables
            compatible_formats = []

            # Is this a product managed by us or from the ARX?
            product_is_managed = False

            gtin_data = data.get("gtin")
            gtin: Gtin = Gtin.get_or_none(Gtin.cip.contains(str(int(gtin_data))))
            if gtin:
                ucd: Ucd = Ucd.get_or_none(Ucd.ucd == gtin.ucd)
                seuil: Seuil = Seuil.get_or_none(Seuil.reference == ucd.reference)
                if seuil:
                    product: Product = Product.get_or_none(Product.reference == ucd.reference)
                    product_is_managed = product.adr_pref or "No address"

                    # Does this product have assigned formats?
                    formats = (
                        Reform.select(Reform.format.alias("reform_format"), Format.typeBac)
                        .join(Format, JOIN.LEFT_OUTER, on=(Format.format == Reform.format))
                        .where(Reform.reference == product.reference)
                    )
                    compatible_formats = [
                        {"format": reform.reform_format, "code": reform.format.typeBac} for reform in formats
                    ]

            # Is there an inventory line made after the last exit?
            already_returned = last_inventory and last_inventory.chrono > histo.chrono

            return {
                "message": serial,
                "ward": histo.service,
                "is_managed": product_is_managed,
                "histo_data": {
                    "chrono": histo.chrono,
                    "contenant": histo.contenant,
                    "stay": histo.sejour,
                    "patient": {
                        "ipp": histo.ipp,
                        "name": patient.nom if patient else None,
                        "firstname": patient.prenom if patient else None,
                    },
                    "ward": {
                        "code": histo.service,
                        "name": service.libelle if service else None,
                    },
                    "compatible_formats": compatible_formats,
                    "already_returned": already_returned,
                },
            }, HTTP_200_OK
        else:
            logger.error(f"Serial {serial} not found in historic")
            return {"message": "Serial not found"}, HTTP_404_NOT_FOUND

    except Exception as e:
        logger.error(f"Error processing request: {str(e)}")
        return {"message": "Internal server error"}, HTTP_500_INTERNAL_SERVER_ERROR


@external_returns_blueprint.route("return", methods=["POST"])
@jwt_required()
@mustHaveRights(RESSOURCE_NAME, canEdit=True)
def return_product():
    data = request.get_json()

    try:
        serial = data.get("serial")
        return_ward = data.get("return_ward")
        return_box = data.get("format_code")

        logger.info("Returning product")
        logger.info(f"Returning loader box data : {return_box}")

        return_box = None if return_box == "none" else return_box

        with mysql_db.atomic() as transaction:
            try:
                gpao: Gpao = (
                    Gpao.select()
                    .where((Gpao.serial == serial) & (Gpao.type_mvt == TypeMouvementGpao.OUTPUT.value))
                    .order_by(-Gpao.pk)
                    .get()
                )
                ward: Service = Service.get_or_none(Service.code == return_ward)
                histo: Historique = (
                    Historique.select().where(Historique.serial == serial).order_by(-Historique.pk).get()
                )

                previous_stock_qty = 0
                new_stock_qty = 0

                return_destination_adr = "?"

                existing_stock: Stock = (
                    Stock.select()
                    .join(UnitDose, JOIN.LEFT_OUTER, on=(UnitDose.contenant == Stock.contenant))
                    .where((Stock.reference == gpao.ref) & (UnitDose.serial == serial) & (Stock.magasin != "SHV"))
                )

                if existing_stock.exists():
                    # This serial is already linked to a container in stock
                    pass

                # Move the product back to the inventory
                if return_box:
                    logger.info(f"Product will be returned using a loader box : {return_box}")
                    try:
                        format_type = return_box[: len(return_box) - 5]
                        format: Format = Format.get(Format.typeBac == format_type)
                        reform: Reform = Reform.get((Reform.format == format.format) & (Reform.reference == gpao.ref))

                        stock: Stock = Stock.select().where(
                            (Stock.reference == gpao.ref)
                            & (Stock.lot == gpao.lot)
                            & (Stock.ucd == gpao.ucd)
                            & (Stock.contenant == return_box)
                        )

                        if stock.exists() and stock.where(Stock.magasin == "SHV").count() == 0:
                            # This box is already used elsewhere, this cannot is not ok
                            logger.error(f"Box {return_box} already used for product {gpao.ref}")
                            return {"message": f"Loader box already in {stock.get().magasin}"}, HTTP_400_BAD_REQUEST

                        shv_stock: Stock = stock.where((Stock.magasin == "SHV"))

                        if shv_stock.exists():
                            if shv_stock.where(Stock.quantite < reform.capacity).count() > 0:
                                # This box is already used, but has enough capacity
                                shv_stock = shv_stock.get()
                                previous_stock_qty = shv_stock.quantite
                                shv_stock.quantite += 1
                                shv_stock.save()
                                new_stock_qty = shv_stock.quantite
                                return_destination_adr = shv_stock.adresse
                                logger.info(
                                    f"Product stored in existing compatible loader box at adr : {shv_stock.adresse}"
                                )
                            else:
                                # This box is used and has no more space, reject the return
                                logger.error(f"Box {return_box} is full for product {gpao.ref}")
                                return {"message": f"Loader box {return_box} is full"}, HTTP_400_BAD_REQUEST
                        else:
                            # Create a new stock entry for the return box
                            new_stock: Stock = Stock()
                            new_stock.adresse = "SHV"
                            new_stock.reference = gpao.ref
                            new_stock.quantite = 1
                            new_stock.lot = gpao.lot
                            new_stock.date_peremption = gpao.tperemp
                            new_stock.date_entree = datetime.now()
                            new_stock.contenant = return_box
                            new_stock.magasin = "SHV"
                            new_stock.ucd = gpao.ucd
                            new_stock.cip = gpao.cip
                            new_stock.serial = serial
                            new_stock.zone_admin = "SHELVES"
                            new_stock.fraction = 100
                            new_stock.save()
                            new_stock_qty = 1
                            return_destination_adr = new_stock.adresse
                            logger.info(f"Product stored in new stock line at adr : {new_stock.adresse}")

                    except DoesNotExist:
                        logger.error(f"Format {return_box} or Reform not found for product {gpao.ref}")
                        return {"message": "Loader box not found for this product"}, HTTP_404_NOT_FOUND
                    except Exception as e:
                        logger.error(f"Error processing return box: {str(e)}")
                        return {"message": "Internal server error"}, HTTP_500_INTERNAL_SERVER_ERROR
                else:
                    logger.info("Product will be returned without loader box")
                    stock: Stock = Stock.select(Stock).where(
                        (Stock.magasin == "SHV") & (Stock.reference == gpao.ref) & (Stock.contenant.startswith("E"))
                    )
                    return_container = None
                    if stock.exists():
                        # There is a virtual container (vrac) for this product, let's add it here
                        stock = stock.get()
                        previous_stock_qty = stock.quantite
                        stock.quantite += 1
                        stock.save()
                        new_stock_qty = stock.quantite
                        return_destination_adr = stock.adresse
                        return_container = stock.contenant
                        logger.info(f"Product stored in existing compatible stock adr : {stock.adresse}")
                    else:
                        counter = get_counter("EXT_SHELVEBOX")
                        newBoxCode = f"E{str(counter).rjust(9, '0')}"
                        new_stock: Stock = Stock()
                        new_stock.adresse = "SHV"
                        new_stock.reference = gpao.ref
                        new_stock.quantite = 1
                        new_stock.lot = gpao.lot
                        new_stock.date_peremption = gpao.tperemp
                        new_stock.date_entree = datetime.now()
                        new_stock.contenant = newBoxCode
                        new_stock.magasin = "SHV"
                        new_stock.ucd = gpao.ucd
                        new_stock.cip = gpao.cip
                        new_stock.serial = serial
                        new_stock.zone_admin = "SHELVES"
                        new_stock.fraction = 100
                        new_stock.save()
                        new_stock_qty = new_stock.quantite
                        return_destination_adr = new_stock.adresse
                        return_container = newBoxCode
                        logger.info(f"Product stored in new stock at adr : {new_stock.adresse}")

                # Create or update the unitdose entry
                unit_dose: UnitDose
                unit_dose, isCreated = UnitDose.get_or_create(
                    serial=serial,
                    defaults={"contenant": return_box if return_box else return_container, "chrono": datetime.now()},
                )

                if not isCreated:
                    unit_dose.contenant = return_box if return_box else return_container
                    unit_dose.chrono = datetime.now()
                    unit_dose.save()

                # Make a copy of the line, but with mouvement I (Lost or Returned) and Etat = A
                # Create a dictionary from the original gpao record
                gpao_dict = model_to_dict(gpao)

                # Remove primary key to create a new record
                gpao_dict.pop("pk")

                # Update values for the new record
                now = datetime.now()
                gpao_dict["chrono"] = now
                gpao_dict["tenvoi"] = MYSQL_ZERO_DATE
                gpao_dict["poste"] = MEDIANWEB_POSTE
                gpao_dict["tentree"] = gpao_dict.get("tentree") or MYSQL_ZERO_DATE
                gpao_dict["dest"] = ward.code if ward else gpao_dict.get("dest")
                gpao_dict["etat"] = TypeEtatGpao.DRAFT.value
                gpao_dict["type_mvt"] = TypeMouvementGpao.LOST_OR_RETURN.value
                gpao_dict["user"] = session["username"]
                gpao_dict["serial"] = serial
                gpao_dict["contenant"] = return_box if return_box else gpao_dict.get("contenant")

                # Create new gpao record
                new_gpao = Gpao.create(**gpao_dict)

                # Add to history
                new_histo = model_to_dict(histo)
                new_histo.pop("pk")
                new_histo.pop("date_service")
                new_histo["adresse"] = return_destination_adr
                new_histo["chrono"] = now
                new_histo["date_prise"] = gpao_dict.get("date_prise") or MYSQL_ZERO_DATE
                new_histo["date_debut"] = gpao_dict.get("date_debut") or MYSQL_ZERO_DATE
                new_histo["quantite_totale"] = new_stock_qty
                new_histo["service"] = ward.code if ward else gpao_dict.get("dest", "")
                new_histo["info"] = f"Retour de {serial} vers {return_ward}"
                new_histo["type_mouvement"] = HistoryType.Inventaire.value
                new_histo["utilisateur"] = session["username"]
                new_histo = Historique.create(**new_histo)

                return {
                    "message": f"Product {serial} returned successfully",
                    "gpao": new_gpao.pk,
                    "histo": new_histo.pk,
                    "previous_stock_qty": previous_stock_qty,
                    "new_stock_qty": new_stock_qty,
                }, HTTP_200_OK

            except Exception as err:
                transaction.rollback()
                print(str(err))
                logger.error(str(err))
                return {"error": str(err)}, HTTP_400_BAD_REQUEST
        logger.info(f"Product {serial} returned successfully")
        return {"message": "Product returned successfully"}, HTTP_200_OK

    except DoesNotExist:
        logger.error(f"Serial {serial} not found in Gpao")
        return {"message": "Serial not found"}, HTTP_404_NOT_FOUND
    except Exception as e:
        logger.error(f"Error processing request: {str(e)}")
        return {"message": "Internal server error"}, HTTP_500_INTERNAL_SERVER_ERROR
