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

from common.status import HTTP_200_OK, HTTP_500_INTERNAL_SERVER_ERROR
from flask import Blueprint, request, send_file
from flask_jwt_extended import jwt_required
from median.constant import HistoryType
from median.models import Magasin, Adresse, Stock, Product, Historique, Seuil
from peewee import JOIN, fn, Value

from ressources.astus.utils import generate_memory_excel_file

acced_consumption_blueprint = Blueprint('consumption', __name__)

logger = logging.getLogger('median')


@acced_consumption_blueprint.route('<string:type_mag>', methods=['POST'])
@jwt_required()
def get_by_mag(type_mag):
    try:
        data = json.loads(request.data)
        list_conso = get_consumption(type_mag=type_mag, data=data)

        return {'list': [get_obj_consumption(m) for m in list_conso]}, HTTP_200_OK

    except Exception as error:
        logger.error(error.args)
        return {
            'alertMessage': 'acced.consumption.error',
            'param': [type_mag]
        }, HTTP_500_INTERNAL_SERVER_ERROR


@acced_consumption_blueprint.route('<string:type_mag>/export', methods=['PATCH'])
@jwt_required()
def export(type_mag):
    data = json.loads(request.data)
    headers = data['headers']

    lines = get_consumption(type_mag, data)
    memory_file = generate_memory_excel_file(headers=headers, items=lines, transform_function=get_obj_consumption)

    return send_file(memory_file, as_attachment=True, download_name="consumption.xlsx")


def get_obj_consumption(m):
    return {
        'last': m.last_picking,
        'reference': m.stock.reference,
        'designation': m.product.designation,
        'quantity': m.quantity,
        'fraction': m.stock.fraction,
        'min': m.seuil.stock_mini if hasattr(m, 'seuil') else None,
        'max': m.seuil.stock_maxi if hasattr(m, 'seuil') else None,
        'expiration': m.expiration,
    }


def get_consumption(type_mag, data):
    criterias = data['search']
    nb_jour = data['delay']
    compare_date = (datetime.today() - timedelta(days=nb_jour))
    andexpr = (Magasin.type_mag == type_mag)
    if len(criterias) > 0:
        lst = list(map(lambda s: (
            (Adresse.adresse.contains(s.strip())) |
            (Stock.reference.contains(s.strip())) |
            (Product.designation.contains(s.strip()))
        ), criterias))
        search = reduce(operator.and_, lst)
        expr = reduce(operator.and_, [andexpr, search])
    else:
        expr = andexpr
    StockAlias = Stock.alias('StockAlias')
    MagasinAlias = Magasin.alias('MagasinAlias')

    histo_sub = (Historique.select(
        Historique.fraction.alias('fraction'),
        Historique.reference.alias('reference'),
        fn.MAX(Historique.chrono).alias('last'))
                .join(MagasinAlias, on=Historique.adresse.startswith(MagasinAlias.mag))
                .where((Historique.type_mouvement << [HistoryType.Sortie.value]) &
                       (MagasinAlias.type_mag == type_mag)
                       & (Historique.chrono > compare_date))
                .group_by(Historique.reference)).alias('jq')

    list_conso = Magasin.select(
        Magasin.type_mag,
        Stock.reference,
        Product.designation,
        fn.IFNULL(fn.SUM(Stock.quantite), 0).alias('quantity'),
        Stock.fraction,
        Seuil.stock_mini,
        Seuil.stock_maxi, (
            StockAlias.select(
                fn.MIN(StockAlias.date_peremption)
            ).where(
                (StockAlias.fraction == Stock.fraction) &
                (StockAlias.reference == Stock.reference) &
                (StockAlias.magasin == Magasin.mag))
        ).alias('expiration'),
        Value('').alias('last_picking'),
        Value(False).alias('delete')
        ).join(
            Adresse, on=Adresse.magasin == Magasin.mag
        ).switch(Magasin).join(
            Stock, on=Stock.adresse == Adresse.adresse
        ).switch(Magasin).join(
            Product, on=Product.reference == Stock.reference
        ).switch(Magasin).join(
            Seuil, JOIN.LEFT_OUTER,
            on=((Seuil.reference == Stock.reference) &
                (Seuil.zone == Magasin.type_mag) &
                (Seuil.fraction == Stock.fraction))
        ).switch(Magasin).join(
            histo_sub, JOIN.LEFT_OUTER,
            on=(histo_sub.c.reference == Stock.reference) & (histo_sub.c.fraction == Stock.fraction)
        ).where(
            expr & (histo_sub.c.last.is_null())
        ).group_by(
            Magasin.type_mag,
            Stock.reference,
            Product.designation
        ).having(fn.SUM(Stock.quantite) > 0)

    and_expr = None

    for c in list_conso:
        expr = ((Historique.fraction == c.stock.fraction) & (Historique.reference == c.stock.reference))
        if and_expr is None:
            and_expr = expr
        else:
            and_expr = and_expr | expr
    last = []
    if and_expr is not None:
        last = list(Historique.select(
            Historique.fraction.alias('fraction'),
            Historique.reference.alias('reference'),
            fn.MAX(Historique.chrono).alias('last')
        ).join(
            Magasin, on=(Historique.poste == Magasin.type_mag)
        ).where(
            (Historique.type_mouvement == HistoryType.Sortie.value) &
            (Magasin.type_mag == type_mag) &
            (and_expr)
        ).group_by(
            Historique.fraction, Historique.reference
        ))

    for c in list_conso:
        date = next(filter(lambda h: h.fraction == c.stock.fraction and h.reference == c.stock.reference, last), None)
        c.last_picking = date.last if date is not None else None
        if c.last_picking and c.last_picking > compare_date:
            c.delete = True

    return [ls for ls in list_conso if not ls.delete]
