from ..base import BaseView, BaseViewException
from median.constant import EtatListe, PatientGlobal, TypeListe, TypeServiListe, HistoryType, EcoType, ReapModes
from median.constant import MEDIANWEB_POSTE
from median.models import Magasin, Product, ListeModel, ListeItemModel, Gpao, Historique, Espace, Config, Stock
from peewee import DoesNotExist, fn
from datetime import datetime
import logging

logger = logging.getLogger('median.view')

REAP_MODE = {
    "T": "CUT",
    "K": "TRACK",
    "0": "MANUEL"
}


class ReplenishmentView(BaseView):
    """Manage replenishment per equipment"""

    def __init__(self, magasin: str = None, creator: str = ""):
        """Initialise a replenishment view"""
        logger.info("New replnishment for %s by %s" % (magasin, creator))
        if magasin is None:
            logger.info("please provide warehouse code")
            raise BaseViewException("please provide warehouse code")
        try:
            self.magasin: Magasin = Magasin.get(mag=magasin)
        except DoesNotExist:
            logger.info("Warehouse code %s not exists" % magasin)
            raise BaseViewException("Warehouse code %s not exists" % magasin)

        self.creator: str = creator
        self.liste: ListeModel = None
        self.liste_out: dict = {}
        self.liste_ext: ListeModel = None
        self.items: list[ListeItemModel] = []
        self.items_out: list[ListeItemModel] = []
        self.items_ext: list[ListeItemModel] = []
        self.current_item: ListeItemModel = None
        self.external_whs = self._is_relate_to_external() or []
        self.counter = self._get_replenishment_counter()

    def add_reference(self, reference: str, quantity: int, fraction: int = 100) -> None:
        if quantity <= 0:
            logger.info(
                "Skipped reference for negative or null quantiy: %s -> Qty/Part: %i/%i"
                % (reference, quantity, fraction)
            )
            return

        logger.info("add reference: %s -> Qty/Part: %i/%i" % (reference, quantity, fraction))
        try:
            product = Product.get(reference=reference)
            try:
                suffix = REAP_MODE[product.reap_mode]
            except KeyError:
                suffix = ReapModes.Manual.value
        except DoesNotExist:
            raise BaseViewException("Product %s not exists" % reference)

        # Check if the warehouse is an external warehouse
        if self.magasin.eco_type == EcoType.Externe.value:
            self.add_reference_external(reference, quantity, fraction)
        else:
            # Check if we have enougth stock in the external
            ext_quantity = 0
            if self.external_whs:
                logger.info("Warehouse: %s related to %s" % (
                    self.magasin.mag, ', '.join([m.mag for m in self.external_whs])))
                ext_quantity = self._external_reference_quantity(reference, quantity, fraction) or 0
                logger.info("external quantity: %d" % ext_quantity)

            if ext_quantity > 0:
                if not self.liste_out:

                    self.liste_out[self.external_whs[0].mag] = self._generate_output_liste(
                        self._liste_name(self.external_whs[0]), TypeListe.Output.value,
                        self.external_whs[0], self.magasin,
                    )
                self._generate_output_item(
                    self.liste_out[self.external_whs[0].mag], reference, quantity, fraction,
                    self.items_out
                )
                # self.liste_out[self.external_whs[0].mag].nb_item
                self._update_liste_item_number(self.liste_out[self.external_whs[0].mag])
            else:
                # If liste not defined, we create an header
                if self.liste is None:
                    self._generate_liste(self._liste_name(suffix=suffix))

                self._generate_item(reference, quantity, fraction)
                self._generate_gpao(quantity, fraction)
                self._generate_history(quantity, fraction)

                self._update_liste_item_number(self.liste)

    def add_reference_external(self, reference: str, quantity: int, fraction: int = 100) -> None:
        drug: Product = Product.get_or_none(reference=reference)
        # If we have cuttung file, we creat an AIDE CUT list
        if drug and drug.reap_mode == ReapModes.Tampon.value:
            logger.info("Cutting files detect, we create an AIDE CUT list")
            if self.liste is None:
                self._generate_liste(self._liste_name(suffix="CUT"))

            self._generate_item(reference, quantity, fraction)
            self._generate_gpao(quantity, fraction)
            self._generate_history(quantity, fraction)

            self._update_liste_item_number(self.liste)
        elif drug and drug.reap_mode == ReapModes.Track.value:
            logger.info("AIDE TRACK detected, we create an AIDE TRACK list")
            self.liste_ext = self._generate_liste_external(self._liste_name(self.magasin, "TRACK"))
            ext_item = self._generate_item_external(reference, quantity, fraction)
            self._generate_gpao(quantity, fraction, self.liste_ext, ext_item)
            self._generate_history(quantity, fraction, self.liste_ext, ext_item)
            self._update_liste_item_number(self.liste_ext)
        else:
            # Manual
            logger.info("No reap mode or not managed, we create an MANUAL list")
            self.liste_ext = self._generate_liste_external(self._liste_name(self.magasin, "MANUAL"))
            ext_item = self._generate_item_external(reference, quantity, fraction)
            self._generate_gpao(quantity, fraction, self.liste_ext, ext_item)
            self._generate_history(quantity, fraction, self.liste_ext, ext_item)
            self._update_liste_item_number(self.liste_ext)

    def relate_espace(self) -> list:
        """Retrieve equipement relate to this equipement"""
        return Espace.select(Espace).where(Espace.mag == self.magasin.mag)

    def _get_replenishment_counter(self):
        """Rewrite this in f_compteur"""
        try:
            n = Config.get(poste="TOUS", cle="CPT_LISTE_S")
        except DoesNotExist:
            n = Config()
            n.poste = "TOUS"
            n.cle = "CPT_LISTE_S"
            n.type_value = "N"
            n.value = 1
            n.save()

        c = str(int(n.value) + 1)
        Config.update({Config.value: c}).where((Config.poste == "TOUS") & (Config.cle == "CPT_LISTE_S")).execute()
        return c.zfill(5)

    def _liste_name(self, poste: Magasin = None, suffix: str = "") -> str:
        """Create a liste name"""
        formated_suffix = f"-{suffix}" if suffix else ""
        if poste is None:
            return "%s %s %s%s" % (self.magasin.type_mag, self.creator, self.counter, formated_suffix)
        return "%s %s %s%s" % (poste.type_mag, self.creator, self.counter, formated_suffix)

    def _is_relate_to_external(self):
        """Return True is the equipement is relate to one external equipement"""
        res = []
        for equipement in self.relate_espace():
            m = Magasin.get_or_none(type_mag=equipement.poste)
            if m and m.eco_type == "E":
                res.append(m)
        return res

    def _external_reference_quantity(self, reference: str, quantity: int, fraction: int = 100) -> int:
        res = Stock.select(
            fn.SUM(Stock.quantite)
        ).where(
            Stock.reference == reference,
            Stock.magasin << [m.mag for m in self.external_whs],
            Stock.bloque == 0
        ).scalar()
        return res

    def _compute_item_number(self, items: list[ListeItemModel] = []) -> int:
        return "%05d" % (len(items) + 1)

    def _update_liste_item_number(self, current_liste: ListeModel):
        """Update the number of items on the list"""
        items = ListeItemModel.select(ListeItemModel.pk).where(
            ListeItemModel.mode == current_liste.mode,
            ListeItemModel.liste == current_liste.liste
            )
        current_liste.nb_item = len(items)
        current_liste.save()

    def _generate_output_liste(
            self, name: str, mode: str = TypeListe.Output.value,
            zone_deb: Magasin = None, zone_fin: Magasin = None,
            service: str = None
    ) -> ListeModel:
        if service is None:
            service = self.magasin.dest or "TRAN"
        lst, _ = ListeModel.get_or_create(
            mode=mode, liste=name,
            defaults={
                "etat": EtatListe.Vierge.value,
                "nb_item": 0,
                "fusion": "REAPPRO",
                "service": service,
                "ipp": PatientGlobal.Ipp.value,
                "num_ipp": PatientGlobal.Ipp.value or "",
                "num_sej": PatientGlobal.Sejour.value or "",
                "zone_deb": zone_deb.type_mag,
                "zone_fin": zone_fin.type_mag,
                "username": self.creator,
                "type_servi": TypeServiListe.ExterneBoite.value,
            }
        )
        self.liste_out[zone_deb.mag] = lst
        return lst

    def _generate_output_item(
            self, liste_out: ListeModel,  reference: str, quantity: int, fraction: int,
            items_out: list[ListeItemModel] = []
    ) -> ListeItemModel:
        """Generate an item for this product on an output list"""
        logger.info(liste_out)
        itm, _ = ListeItemModel.get_or_create(
            mode=liste_out.mode,
            liste=liste_out.liste,
            reference=reference,
            fraction=fraction,
            defaults={
                "item": self._compute_item_number(items_out),
                "qte_dem": quantity,
                "dest": liste_out.service,
                "user": self.creator,
                "etat": EtatListe.Vierge.value,
                "num_ipp": PatientGlobal.Ipp.value or "",
                "num_sej": PatientGlobal.Sejour.value or "",
                "type_servi": TypeServiListe.ExterneBoite.value,
            }
        )
        self.items_out.append(itm)
        return itm

    def _generate_liste(self, name: str, mode: str = TypeListe.Input.value) -> ListeModel:
        """Generate a f_liste entry"""
        lst, _ = ListeModel.get_or_create(
            mode=mode, liste=name,
            defaults={
                "etat": EtatListe.Vierge.value,
                "nb_item": 0,
                "fusion": "REASSORT",
                "service": self.magasin.dest or "TRAN",
                "ipp": PatientGlobal.Ipp.value,
                "num_ipp": PatientGlobal.Ipp.value or "",
                "num_sej": PatientGlobal.Sejour.value or "",
                "zone_deb": "",
                "zone_fin": self.magasin.type_mag,
                "username": self.creator,
                "type_servi": TypeServiListe.GlobaleBoite.value,
            }
        )
        self.liste = lst
        return lst

    def _generate_liste_external(self, name: str, mode: str = TypeListe.Input.value) -> ListeModel:
        """Generate a f_liste entry"""
        lst, _ = ListeModel.get_or_create(
            mode=mode, liste=name,
            defaults={
                "etat": EtatListe.Vierge.value,
                "nb_item": 0,
                "fusion": "REASSORT",
                "service": self.magasin.dest or "TRAN",
                "ipp": PatientGlobal.Ipp.value,
                "num_ipp": PatientGlobal.Ipp.value or "",
                "num_sej": PatientGlobal.Sejour.value or "",
                "zone_deb": "",
                "zone_fin": self.magasin.type_mag,
                "username": self.creator,
                "type_servi": TypeServiListe.ExterneBoite.value,
            }
        )
        return lst

    def _generate_item_external(
            self, reference, quantity, fraction
    ) -> ListeItemModel:
        """Generate an item for this product"""
        itm, _ = ListeItemModel.get_or_create(
            mode=self.liste_ext.mode,
            liste=self.liste_ext.liste,
            reference=reference,
            fraction=fraction,
            defaults={
                "item": self._compute_item_number(self.items_ext),
                "qte_dem": quantity,
                "dest": self.liste_ext.service,
                "user": self.creator,
                "etat": EtatListe.Vierge.value,
                "num_ipp": PatientGlobal.Ipp.value or "",
                "num_sej": PatientGlobal.Sejour.value or "",
                "type_servi": TypeServiListe.ExterneBoite.value,
            }
        )
        self.items_ext.append(itm)
        return itm

    def _generate_item(self, reference, quantity, fraction) -> ListeItemModel:
        """Generate an item for this product"""
        itm, _ = ListeItemModel.get_or_create(
            mode=self.liste.mode,
            liste=self.liste.liste,
            reference=reference,
            fraction=fraction,
            defaults={
                "item": self._compute_item_number(self.items),
                "qte_dem": quantity,
                "dest": self.liste.service,
                "user": self.creator,
                "etat": EtatListe.Vierge.value,
                "num_ipp": PatientGlobal.Ipp.value or "",
                "num_sej": PatientGlobal.Sejour.value or "",
                "type_servi": TypeServiListe.GlobaleBoite.value,
            }
        )
        self.current_item = itm
        self.items.append(itm)
        return itm

    def _generate_gpao(
            self, quantity: int, fraction: int,
            liste: ListeModel = None, item: ListeItemModel = None
    ) -> Gpao:
        """Generate a GPAO replenishment line"""
        if liste is None:
            liste = self.liste
        if item is None:
            item = self.current_item
        logger.info("Add GPAO line for %s qty/part %i/%i" % (item.reference, quantity, fraction))
        gp = Gpao()
        gp.chrono = datetime.now()
        gp.poste = self.magasin.type_mag
        gp.etat = "A"
        gp.ref = item.reference
        gp.qte = quantity
        gp.type_mvt = "R"
        gp.liste = liste.liste
        gp.dest = liste.service
        gp.user = self.creator
        gp.item = item.item
        gp.fraction = fraction
        gp.id_zone = self.magasin.id_zone
        gp.id_robot = self.magasin.id_robot
        gp.save()
        return gp

    def _generate_history(
            self, quantity: int, fraction: int,
            liste: ListeModel = None, item: ListeItemModel = None
    ) -> Historique:
        """Generate an history line"""
        if liste is None:
            liste = self.liste
        if item is None:
            item = self.current_item
        logger.info("Add History line for %s qty/part %i/%i" % (item.reference, quantity, fraction))
        his = Historique()
        his.chrono = datetime.now()
        his.reference = item.reference
        his.quantite_mouvement = quantity
        his.liste = liste.liste
        his.service = liste.service
        his.type_mouvement = HistoryType.Reference.value
        his.utilisateur = self.creator
        his.poste = MEDIANWEB_POSTE
        his.fraction = fraction
        his.ipp = liste.num_ipp
        his.sejour = liste.num_sej
        his.save()
        return his
