from ..base import BaseView, BaseViewException
from median.models import Product, Magasin, ListeModel, ListeItemModel, Historique, Gpao, Gtin, Ucd, Stock, Service
from median.models import Seuil as modSeuil
from median.views import RawConfig
from median.utils import logger
from peewee import DoesNotExist, fn
from functools import reduce
from median.database import mysql_db
from median.constant import TypeServiListe, EcoType, EtatListe, TypeListe
import datetime
import operator
import time


class SeuilView(BaseView):
    """Gestion des seuils dans Median"""

    def __init__(self, prod=None, mag=None):
        """Initialise le seuil"""
        self.produit = prod
        self.magasin = mag
        self.fraction = 100

    def _check_produit(self, produit):
        """Check if product exists

        :param produit: code produit a rechercher
        :type  produit: str
        :return:  Project model
        :rtype: median.models.Product
        """
        try:
            p = Product.get(reference=produit)
            return p
        except DoesNotExist:
            logger.error("Le produit %s n'existe pas" % produit)
            raise BaseViewException("Le produit n'existe pas dans la base")

    def _check_magasin(self, magasin):
        """Check if magasin exists

        :param magasin: code magasin a rechercher
        :type  magasin: str
        :return:  Magasin model
        :rtype: median.models.Magasin
        """
        try:
            m = Magasin.get(mag=magasin)
            return m
        except DoesNotExist:
            logger.error("Le magasin %s n'existe pas" % magasin)
            raise BaseViewException("Le magasin n'existe pas dans la base")

    def _check_type_magasin(self, type_magas):
        """Check if magasin exists

        :param type_magas: code magasin a rechercher
        :type  type_magas: str
        :return: Magasin model
        :rtype: median.models.Magasin
        """
        try:
            m = Magasin.get(type_mag=type_magas)
        except DoesNotExist:
            logger.error("Le type magasin %s n'existe pas" % type_magas)
            raise BaseViewException("Le magasin n'existe pas dans la base")
        return m

    def _transfert_service(
        self,
    ):
        """Retrieve the code of the service to transfert stock"""
        service = RawConfig().read(param="k_ua_transfert").value
        if service is None:
            raise BaseViewException("k_ua_transfert not defined")

        return service

    def liste(self, produit=None, magasin=None):
        """
        Affiche la liste des seuils d'un produit ou d'un magasin

        :param produit: code du produit a rechercher
        :type  produit: str
        :param magasin: Code du magasin
        :type  magasin: str
        :return: liste des seuils et de leur valeur
        :rtype: list
        """
        if produit is None and magasin is None:
            raise BaseViewException("Un produit ou magasin est nécessaire pour afficher la liste")

        clauses = []
        # vérification si le produit existe
        if produit is not None:
            try:
                Product.get(reference=produit)
                clauses.append((modSeuil.reference == produit))
            except DoesNotExist:
                logger.error("Le produit %s n'existe pas" % produit)
                raise BaseViewException("Le produit n'existe pas dans la base")

        # vérification si le magasin existe
        if magasin is not None:
            try:
                m = Magasin.get(mag=magasin)
                clauses.append((modSeuil.zone == m.type_mag))
            except DoesNotExist:
                logger.error("LE magasin %s n'existe pas" % magasin)
                raise BaseViewException("Le magasin n'existe pas dans la base")

        res = []
        s = modSeuil.select().where(reduce(operator.and_, clauses))

        for li in s:
            res.append(
                {
                    "pk": li.pk,
                    "reference": li.reference,
                    "magasin": li.zone,
                    "mini": li.stock_mini,
                    "maxi": li.stock_maxi,
                    "fraction": li.fraction,
                }
            )
        return res

    def reappro(self, produit=None, magasin=None, quantite=0, fraction=100, service=None, createur=""):
        """Reappro ligne de produit"""
        self.mandatory(produit, "Le produit est obligatoire")
        self.mandatory(magasin, "Le magasin est obligatoire")
        self.non_zero(quantite, "La quantité doit être supérieure à zéro")
        self._check_produit(produit)
        mag = self._check_magasin(magasin)

        liste = "%s Unitaire LE %s" % (mag.type_mag, time.strftime("%Y-%m-%d"))
        if service is None or service == '':
            service = self._transfert_service()

        # Check if service exists
        try:
            serviceRecord = Service.get(Service.code == service)
            service = serviceRecord.code
        except DoesNotExist as e:
            logger.error(f"Reappro - Unknown service: {service} - {str(e)}")
            raise BaseViewException(f"Le service {service} n'existe pas dans la base")

        zone = ""
        if mag.eco_type == EcoType.Externe.value:
            type_servi = TypeServiListe.ExterneBoite.value
        elif mag.eco_type == EcoType.Riedl.value:
            zone = "RIEDL"
            type_servi = TypeServiListe.RiedlBoite.value
        else:
            type_servi = TypeServiListe.GlobaleBoite.value

        # Check if liste is already exists
        try:
            d = ListeModel.get(liste=liste)
            d.nb_item = d.nb_item + 1
        except DoesNotExist:
            d = ListeModel(
                liste=liste,
                date_creation=datetime.datetime.now(),
                mode=TypeListe.Input.value,
                etat=EtatListe.Vierge.value,
                fusion="REASSORT",
                service=service,
                nb_item=1,
                date_modification=datetime.datetime.now(),
                username=createur,
                zone_deb=zone,
                zone_fin=mag.type_mag,
                type_servi=type_servi,
                id_servi=2,
            )
            d.save()

        # Create f_item
        itm = ListeItemModel(
            liste=liste,
            reference=produit,
            mode=TypeListe.Input.value,
            etat=EtatListe.Vierge.value,
            item="%06d" % d.nb_item,
            qte_dem=quantite,
            fraction=fraction,
            dest=service,
            user=createur,
            type_servi=type_servi,
            id_servi=2,
        )
        itm.save()

        d.date_modification = datetime.datetime.now()
        d.save()
        self.creer_gpao(mag.id_zone, produit, fraction)

        # Add trace on historique
        h = Historique(
            chrono=datetime.datetime.now(),
            reference=produit,
            type_mouvement="REF",
            liste=liste,
            info="Reappro %s, quantité %i, fraction %i" % (magasin, int(quantite), int(fraction)),
            service=service,
        )
        h.save()

        return liste

    def reappro_item(
        self,
        liste,
        item,
        ref,
        fraction,
        quantite,
        type_magasin,
        service,
        id_zone="",
        id_robot=0,
        gpao=False,
        username="",
    ):
        """Création d'une liste de produit

        :param type_magasin: code du type de magasin a réapprovisionner
        :type  type_magasin: string
        :return: la liste créée
        :rtype: ListeModel
        """
        listeEntry = ListeModel.get_or_none(ListeModel.liste == liste)
        if listeEntry is None:
            logger.error("Reappro Item: List not found")
            return False

        liste_pk = listeEntry.pk
        mag = self._check_type_magasin(type_magasin)

        if mag.eco_type == EcoType.Externe.value:
            type_servi = TypeServiListe.ExterneBoite.value
        elif mag.eco_type == EcoType.Riedl.value:
            type_servi = TypeServiListe.RiedlBoite.value
        else:
            type_servi = TypeServiListe.GlobaleBoite.value

        try:
            with mysql_db.atomic():
                # Create f_item
                itm = ListeItemModel(
                    liste=liste,
                    reference=ref,
                    mode="E",
                    etat="V",
                    item="%06d" % item,
                    qte_dem=quantite,
                    fraction=fraction,
                    dest=service,
                    type_servi=type_servi,
                    id_servi=2,
                )
                itm.save()

                # Add trace on historique
                seuilItem = modSeuil.get_or_none(
                    (modSeuil.reference == itm.reference)
                    & (modSeuil.fraction == itm.fraction)
                    & (modSeuil.zone == listeEntry.zone_fin)
                )

                stockItem = (
                    Stock.select()
                    .where((Stock.reference == itm.reference))
                    .join(Magasin, on=(Stock.magasin == Magasin.mag))
                )
                qtyStockItem = 0
                if len(stockItem) > 0:
                    for stockLine in stockItem:
                        qtyStockItem += stockLine.quantite

                h = Historique(
                    chrono=datetime.datetime.now(),
                    reference=ref,
                    type_mouvement="REF",
                    liste=liste,
                    info="Reappro %s, quantité %s, fraction %s" % (type_magasin, quantite, fraction),
                    service=service,
                    poste=mag.mag,  # f_mag.x_mag
                    utilisateur=username,  # connectedUser
                    magasin=listeEntry.zone_fin,  # f_mag.x_type_mag
                    pk_liste=liste_pk,
                    fraction=fraction,
                    pk_item=itm.pk,
                    commentaire=(
                        f"min:{round(seuilItem.stock_mini)} "
                        f"max:{round(seuilItem.stock_maxi)} "
                        f"stock:{round(qtyStockItem)}"
                    ),
                )
                h.save()

                # Create f_gpao
                if gpao is True:
                    g = Gpao(
                        chrono=datetime.datetime.now(),
                        poste="MEDIANWEB",
                        etat="A",
                        type_mvt="R",
                        ref=ref,
                        qte=quantite,
                        fraction=fraction,
                        id_zone=id_zone,
                        id_robot=id_robot,
                        liste=liste,
                        item="%06d" % item,
                    )
                    g.save()
        except Exception as e:
            logger.error(f"Reappro Item : {str(e)}")
            return False

        return True

    def reappro_calcul(self, type_magasin=None, ignore_min_threshold=False):
        """Compute all products to be refill"""
        res = []
        mag = self._check_type_magasin(type_magasin)
        self._transfert_service()
        l_seuil = modSeuil().select().where(modSeuil.zone == type_magasin)

        # cpt = 0
        for ls in l_seuil:
            try:
                pro: Product = Product.get(Product.reference == ls.reference)
            except Exception:
                continue

            quantite = ls.stock_maxi
            # produit = ls.reference
            fraction = ls.fraction
            stk_liste = pro.stock_liste(mag.type_mag, fraction)
            stk_mag = pro.stock_magasin(mag.mag, fraction)
            stk_coupe = pro.stock_coupe(mag.type_mag, fraction)

            quantite = quantite - stk_mag - stk_liste - stk_coupe
            if quantite <= 0:
                continue

            try:
                last_cut = (
                    Gtin.select(fn.MAX(Gtin.date_der_coupe).alias("last_cut_date"))
                    .join(Ucd, on=(Gtin.ucd == Ucd.ucd))
                    .where(Ucd.reference == ls.reference)
                )

                gtin = (
                    Gtin.select(Gtin.qt_boite, Gtin.qt_blister, Gtin.qt_pass)
                    .join(Ucd, on=(Gtin.ucd == Ucd.ucd))
                    .where((Ucd.reference == ls.reference) & (Gtin.date_der_coupe == last_cut))
                    .order_by(Gtin.dossier.desc())
                ).get()

                blister = gtin.qt_blister
                box = gtin.qt_boite
                pass_box = gtin.qt_pass
            except DoesNotExist:
                blister = None
                box = None
                pass_box = None

            # si on ne depasse pas le seuil mini, on passe au seuil suivant, sauf si on ignore le seuil mini
            if not ignore_min_threshold and (stk_mag + stk_liste + stk_coupe) >= ls.stock_mini:
                continue

            res.append(
                {
                    "checked": False,
                    "ref": ls.reference,
                    "name": pro.designation,
                    "stkmini": ls.stock_mini,
                    "stkmaxi": ls.stock_maxi,
                    "belowMaxThreshold": (stk_mag + stk_liste + stk_coupe) < ls.stock_maxi,
                    "fraction": ls.fraction,
                    "quantite": quantite,
                    "stock": stk_mag + stk_coupe,
                    "commande": stk_liste,
                    "blister": blister,
                    "pass": pass_box,
                    "box": box,
                    "stock_mag": stk_mag,
                    "stock_coupe": stk_coupe,
                    "reap_mode": pro.reap_mode or "0"  # Manual as default value
                }
            )
        return res

    def creer_gpao(self, id_zone, ref=None, fract=100):
        extra = ""
        if ref is not None:
            extra = "AND s.x_ref='%s' AND s.x_fraction=%i" % (ref, fract)
        query = """
            insert into f_gpao (x_chrono, x_poste, x_ref, x_fraction, x_etat, x_qte, x_type_mvt, x_id_zone, x_id_robot)
            SELECT NOW(), 'MEDIANWEB', x_ref, IFNULL(s.x_fraction,100), 'A', IFNULL((
                SELECT SUM(x_qte_dem-x_qte_serv)
                FROM f_item i
                INNER JOIN f_liste l ON l.x_liste = i.x_liste
                INNER JOIN f_mag m ON m.x_type_mag=l.x_zone_fin
                WHERE i. x_etat!='S' AND i.x_mode='E'
                  AND i.x_ref=s.x_ref AND m.x_id_zone=m1.x_id_zone
                  AND i.x_fraction = s.x_fraction
                GROUP BY i.x_ref, x_fraction), 0), 'R', m1.x_id_zone, m1.x_id_robot
            FROM f_seuil s
            INNER JOIN f_mag m1 ON s.x_zone=m1.x_type_mag
            WHERE m1.x_id_zone='{0}'
            {1}
            GROUP BY x_id_zone, x_ref, x_fraction
            """.format(id_zone, extra)
        mysql_db.execute_sql(query)

        return "ok"


# Backward compatibility
Seuil = SeuilView
