import json
import logging
import operator
from datetime import datetime
from functools import reduce

from flask import request
from flask_jwt_extended import get_jwt_identity
from median.constant import EcoType
from median.models import Stock, Historique, User, Gpao, Seuil, Magasin, Product, FItem, FListe
from peewee import fn, DoesNotExist, JOIN

logger = logging.getLogger('median')


def get_product_stock(data, mag, ucd):
    return Stock.select().where((Stock.reference == data['reference']) &
                                (Stock.adresse == data['location']) &
                                (Stock.contenant == data['selectedCIP']) &
                                (Stock.cip == data['selectedCIP']) &
                                (Stock.magasin == mag.mag) &
                                (Stock.lot == data['selectedBatch']) &
                                (Stock.ucd == ucd.ucd))


def input_product(data, expiration, ucd, mag, mvt_qty):
    identity = get_jwt_identity()
    usr = User.get(User.pk == identity)

    Stock.create(
        reference=data['reference'],
        quantite=mvt_qty,
        lot=data['selectedBatch'],
        date_peremption=expiration,
        date_entree=datetime.now(),
        contenant=data['selectedCIP'],
        magasin=mag.mag,
        ucd=ucd.ucd,
        cip=data['selectedCIP'],
        adresse=data['location'],
        serial=data['selectedSerial'],
        fraction=data.get('fraction', 100),
        # zone_admin='TMP',
        capa=ucd.qt_boite)

    reqTotal = Stock.select(fn.IFNULL(fn.SUM(Stock.quantite), 0).alias('total')).where(
        (Stock.reference == data['reference']) & (Stock.fraction == data.get('fraction', 100))).get()

    Historique.create(
        chrono=datetime.now(),
        reference=data['reference'],
        adresse=mag.mag,
        magasin=mag.mag,
        quantite_mouvement=mvt_qty,
        quantite_totale=reqTotal.total,
        type_mouvement='ENT',
        lot=data['selectedBatch'],
        pmp=0,
        commentaire='manual stock entry',
        date_peremption=expiration,
        contenant=data['selectedCIP'],
        poste='MEDIANWEB',
        ucd=ucd.ucd,
        serial=data['selectedSerial'],
        info='manual stock entry',
        fraction=data.get('fraction', 100),
        utilisateur=usr.username
    )

    Gpao.create(
        chrono=datetime.now(),
        etat='A',
        ref=data['reference'],
        qte=mvt_qty,
        lot=data['selectedBatch'],
        type_mvt='E',
        ucd=ucd.ucd,
        # dest=k_ua_entree.value,
        tperemp=expiration,
        id_robot=mag.id_robot,
        id_zone=mag.id_zone,
        magasin=mag.mag,
    )


def output_product(data, expiration, ucd, mag, mvt_qty):
    identity = get_jwt_identity()
    usr = User.get(User.pk == identity)
    try:
        stock = get_product_stock(data=data, mag=mag, ucd=ucd).get()
    except DoesNotExist:
        return "external.scan.med.product.unknown"

    total = stock.quantite - mvt_qty
    if total > 0:
        stock.quantite = total
        stock.save()
    else:
        stock.delete_instance()

    reqTotal = Stock.select(fn.IFNULL(fn.SUM(Stock.quantite), 0).alias('total')).where(
            Stock.reference == data['reference']).get()

    Historique.create(
        chrono=datetime.now(),
        reference=data['reference'],
        adresse=data['location'],
        quantite_mouvement=mvt_qty,
        quantite_totale=reqTotal.total,
        # service=service,
        type_mouvement='SOR',
        lot=data['selectedBatch'],
        pmp=0,
        date_peremption=expiration,
        contenant=data['selectedBatch'],
        poste='MEDIANWEB',
        ucd=ucd.ucd,
        fraction=data['fraction'],
        utilisateur=usr.username,
        serial=data['selectedSerial'],
        commentaire='Manual',
        magasin=mag.mag,

    )

    _calc_id_robot = mag.id_robot
    _id_robot = _calc_id_robot or 1
    _id_zone = _calc_id_robot or 1

    logger.info('GPAO: Ajout d\'un mouvement de type sortie')
    Gpao.create(
        chrono=datetime.now(),
        poste=mag.type_mag,
        sequence='MEDIANWEB',
        user=usr.username,
        etat='A',
        reference=data['reference'],
        qte=mvt_qty,
        lot=data['selectedBatch'],
        type_mvt='S',
        # dest=service,
        tperemp=expiration,
        fraction=data['fraction'],
        id_robot=_id_robot,
        id_zone=_id_zone,
        ucd=ucd.ucd,
        cip=data['selectedCIP'],
        contenant=data['selectedCIP'],
        magasin=mag.mag,

    )

    return None


def _stock_request(all_stock=False):
    data = json.loads(request.data)
    equipments = data.get('equipments', [])
    criterias = data.get('criterias', [])
    expr = (Magasin.eco_type == EcoType.Externe.value)

    if not all_stock:
        expr = expr & (Seuil.pk.is_null(False))

    if len(equipments) > 0:
        expr = expr & Magasin.pk << equipments

    if len(criterias) > 0:
        lst = list(map(lambda s: (
            (Product.designation.contains(s.strip())) |
            (Product.reference.contains(s.strip()) |
             (Magasin.libelle.contains(s.strip())))
        ), criterias))
        search = reduce(operator.and_, lst)

        expr = reduce(operator.and_, [expr, search])

    stocks = (Magasin.select(
        Magasin.pk.alias('equipment_pk'),
        Magasin.avatar.alias('equipment_avatar'),
        Magasin.libelle.alias('equipment_label'),
        Magasin.eco_type.alias('equipment_type'),
        Magasin.mag.alias('equipment_mag'),
        Product.pk.alias('stock_pk'),
        Product.reference.alias('stock_reference'),
        Product.designation.alias('stock_designation'),
        Product.risque.alias('stock_risky'),
        Product.stup.alias('stock_narcotic'),
        Seuil.stock_maxi.alias('stock_maxi'),
        Seuil.stock_mini.alias('stock_mini'),
        Stock.fraction.alias('stock_fraction'),
        fn.SUM(Stock.quantite).alias('stock_quantity'),
        fn.IF(
            (
                FItem.select(fn.COUNT(FItem.pk))
                .join(FListe, on=(FItem.liste == FListe.liste) &
                                 (FItem.mode == FListe.mode))
                .where((FItem.reference == Product.reference) & (FListe.zone_fin == Magasin.type_mag))
            ) <= 0, True, False).alias('toOrder'))
              .join(Stock, on=Stock.magasin == Magasin.mag)
              .join(Product, on=Stock.reference == Product.reference)
              .join(Seuil, JOIN.LEFT_OUTER, on=(Seuil.reference == Stock.reference) &
                                               (Seuil.fraction == Stock.fraction) &
                                               (Seuil.zone == Magasin.type_mag))
              .where(expr)
              .group_by(Magasin.pk, Product.reference, Stock.fraction))
    # logger.debug(stocks)
    if not all_stock:
        stocks = stocks.having((1.1 * Seuil.stock_mini) > fn.SUM(Stock.quantite))

    thresholds = (Magasin.select(
        Magasin.pk.alias('equipment_pk'),
        Magasin.avatar.alias('equipment_avatar'),
        Magasin.libelle.alias('equipment_label'),
        Magasin.eco_type.alias('equipment_type'),
        Magasin.mag.alias('equipment_mag'),
        Product.pk.alias('stock_pk'),
        Product.reference.alias('stock_reference'),
        Product.designation.alias('stock_designation'),
        Product.risque.alias('stock_risky'),
        Product.stup.alias('stock_narcotic'),
        Seuil.stock_maxi.alias('stock_maxi'),
        Seuil.stock_mini.alias('stock_mini'),
        Seuil.fraction.alias('stock_fraction'),
        fn.IFNULL(fn.SUM(Stock.quantite), 0).alias('stock_quantity'),
        fn.IF(
            (
                FItem.select(fn.COUNT(FItem.pk))
                .join(FListe, on=(FItem.liste == FListe.liste) &
                                 (FItem.mode == FListe.mode))
                .where((FItem.reference == Product.reference) & (FListe.zone_fin == Magasin.type_mag))
            ) <= 0, True, False).alias('toOrder'))
                  .join(Seuil, on=(Seuil.zone == Magasin.type_mag))
                  .join(Product, on=(Seuil.reference == Product.reference))
                  .join(Stock, JOIN.LEFT_OUTER, on=(Stock.magasin == Magasin.mag) &
                                                   (Stock.reference == Product.reference) &
                                                   (Stock.fraction == Seuil.fraction))
                  .where(expr & (Stock.pk.is_null()))
                  .group_by(Magasin.pk, Product.reference, Seuil.fraction)
                  )

    return stocks.union_all(thresholds)


def _stock_obj(item):
    return {
        "equipment": {
            "pk": item.equipment_pk,
            "eco_type": item.equipment_type,
            "mag": item.equipment_mag,
            "avatar": item.equipment_avatar,
            "label": item.equipment_label,

        },
        "stock": {
            "pk": item.stock_pk,
            "fraction": item.stock_fraction,
            "reference": item.stock_reference,
            "label": item.stock_designation,
            "quantity": item.stock_quantity,
            "toOrder": item.toOrder,
            'risky': item.stock_risky,
            'narcotic': item.stock_narcotic,
        },
        "threshold": {
            "warn": 0.10,
            "maxi": item.stock_maxi,
            "mini": item.stock_mini,
        }
    }
