import datetime
import logging

from flask import Blueprint, request, session, jsonify
from flask_jwt_extended import jwt_required
from median.constant import TypeListe, EtatListe, RiedlTakingMode, RIEDL_PRIORITY, TypeServiListe
from median.models import FListe, Service, Endpoint, Magasin, FListeError, FItem, Stock
from common.status import HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND
from peewee import DoesNotExist, JOIN
from playhouse.shortcuts import model_to_dict

from common.log import log_riedl

riedl_output_list_blueprint = Blueprint('riedl_output_list', __name__)

logger = logging.getLogger('median')


@riedl_output_list_blueprint.route('', methods=['GET'])
@jwt_required()
def get_all():
    return {
        'listes': _getRiedlEntryListJSON(),
    }


@riedl_output_list_blueprint.route('<string:liste>', methods=['GET'])
@jwt_required()
def get(liste):
    logger.info("Récupérer les listes de sorties")
    return _get_liste(liste)


@riedl_output_list_blueprint.route('', methods=['POST'])
@jwt_required()
def post():
    """Generate a taking mode list"""
    args = request.json
    list_name = args['liste']
    try:
        lst = FListe.get(FListe.mode == TypeListe.Output.value, FListe.liste == list_name)

        # We can split a list in Draft only
        if lst.etat != EtatListe.Vierge.value:
            return {
                'message': 'Unload list is not in draft: %s (%s)' % (lst.service, list_name),
                'i18n_code': 'riedl.taking_mode.ward.not_in_draft'
            }, HTTP_400_BAD_REQUEST

        # Check if service is authorized to split the list
        try:
            serv = Service.get(Service.code == lst.service, Service.type_dest == "SERVICE")
            if serv.riedl_tk_mode == RiedlTakingMode.NoTakingMode.value:
                logger.error('Ward not authorise to split list: %s (%s)' % (serv.code, list_name))
                return {
                    'message': 'Ward not authorise to split list: %s' % (serv.code,),
                    'i18n_code': 'riedl.taking_mode.split.unauth'
                }, HTTP_400_BAD_REQUEST
        except DoesNotExist:
            logger.error('Ward not exists: %s (%s)' % (lst.service, list_name))
            return {
                'message': 'Ward not exists: %s (%s)' % (lst.service, list_name),
                'i18n_code': 'riedl.taking_mode.ward.not_exists'
            }, HTTP_400_BAD_REQUEST

        logger.info("TK Mode on ward %s: %s" % (serv.code, serv.riedl_tk_mode))
        result = _split_liste(lst, serv.riedl_tk_mode)
        if result is None:
            logger.error('Impossible to split taking mode list: %s' % (list_name,))
            return {
                'message': 'Impossible to split taking mode list: %s' % (list_name,),
                'i18n_code': 'riedl.taking_mode.split.failed'
            }, HTTP_400_BAD_REQUEST

        # After generate the split, we block the original list to not generate a new one
        lst.valide_sel = 1
        lst.save()

    except DoesNotExist:
        logger.error('Liste %s does not exists' % (list_name,))
        return {'message': 'Liste %s does not exists' % (list_name,)}, HTTP_404_NOT_FOUND

    return {'liste': list_name}


@riedl_output_list_blueprint.route('', methods=['DELETE'])
@jwt_required()
def delete():
    """Delete list by name"""
    args = request.args
    list_name = args['liste']
    # Check if list is relaed to a Riedl
    try:
        lst = FListe.get(FListe.mode == TypeListe.Output.value, FListe.liste == list_name)
        log_riedl(
            session['username'], 'out_del_liste',
            message='suppression liste %s (%s)' % (lst.liste, lst.pk)
        )
        # Delete a liste, delete the items
        lst.delete_instance()

    except DoesNotExist:
        logger.error('Liste %s does not exists' % (list_name,))
        return {'message': 'Liste %s does not exists' % (list_name,)}, HTTP_404_NOT_FOUND

    return {'liste': args['liste']}


@riedl_output_list_blueprint.route('', methods=['PATCH'])
@jwt_required()
def patch():
    """Update value on f_liste"""
    args = request.json

    tmp_type = args.get('type', 'str')
    tmp_val = args['value']
    if tmp_type == 'bool':
        if tmp_val in ('0', 'false'):
            tmp_val = False
        else:
            tmp_val = True

    list_name = args['liste']
    try:
        lst = FListe.get(FListe.mode == TypeListe.Output.value, FListe.liste == list_name)
        getattr(lst, args['field'])
        setattr(lst, args['field'], tmp_val)
        lst.save()

    except DoesNotExist:
        logger.error('Liste %s does not exists' % (list_name,))
        return {'message': 'Liste %s does not exists' % (list_name,)}, HTTP_404_NOT_FOUND
    except AttributeError:
        logger.error('Field  %s not exists on f_liste' % (args['field'],))
        return {'message': 'Field  %s not exists on f_liste' % (args['field'],)}, HTTP_400_BAD_REQUEST
    except ValueError as error:
        return {'message': error.args[0]}, HTTP_400_BAD_REQUEST

    return {'result': 'OK', 'type': tmp_type, 'val': tmp_val}


def _get_liste(list_name):
    """Retrieve the list information"""
    res = {}
    try:

        lst = FListe.select(FListe, Service.riedl_tk_mode, Service.libelle) \
            .join(Service, JOIN.LEFT_OUTER,
                  on=((Service.code == FListe.service) & (Service.type_dest == "SERVICE"))) \
            .where((FListe.mode == TypeListe.Output.value) & (FListe.liste == list_name)).get()

        endpoints = Endpoint.select(Endpoint) \
            .where(Endpoint.type_dest == 'RIEDL', Endpoint.secteur == lst.zone_fin).order_by(Endpoint.type_peigne)
        riedls = Magasin.select(Magasin).where(Magasin.eco_type == 'L')

        res['data'] = model_to_dict(lst, exclude=[FListe.service])
        res['data']['riedl_tk_mode'] = lst.service.riedl_tk_mode
        res['data']['ward_name'] = lst.service.libelle
        res['selection'] = {}
        res['selection']['endpoint'] = [model_to_dict(enp) for enp in endpoints]
        res['selection']['riedl'] = [model_to_dict(rdl) for rdl in riedls]
        res['selection']['priority'] = [{'value': i[0], 'name': i[1]} for i in RIEDL_PRIORITY]

        return jsonify(res)
    except DoesNotExist:
        res['error'] = {'message': 'List %s does not exists' % list_name}
        return res, HTTP_404_NOT_FOUND


def _getRiedlEntryListJSON():
    res = []
    # First we search all entry list not affected to a riedl
    res.append({
        'text': 'Globale',
        'id': 'outglobal',
        'children': _getRiedlGlobaleList(),
    })
    res.append({
        'text': 'Nominative',
        'id': 'outnominative',
        'children': _getRiedlNominativeList(),
    })
    res.append({
        'text': 'Retrait de Lot',
        'id': 'outlot',
        'children': _getRiedlRetraitLotList(),
    })
    res.append({
        'text': 'Périmé',
        'id': 'outperime',
        'children': _getRiedlPerimeList(),
    })
    return res


def _getRiedlGlobaleList():
    res = []
    mags = Magasin.select(Magasin).where(Magasin.eco_type == 'L')
    for m in mags:
        ul = {
            'text': '%s' % m.libelle,
            'id': 'global-%i' % (m.pk,),
            'children': []
        }
        # Search all lists affect to this riedl
        rdl_list = FListe.select(FListe, FListeError.message.alias('error_message')) \
            .join(FListeError, JOIN.LEFT_OUTER,
                  on=(FListe.liste == FListeError.liste) & (FListe.mode == FListeError.mode)) \
            .where(
            FListe.mode == TypeListe.Output.value,
            FListe.type_servi << [TypeServiListe.GlobaleBoite.value, TypeServiListe.RiedlBoite.value],
            FListe.zone_deb == 'RIEDL', FListe.zone_fin == m.type_mag).order_by(FListe.service)
        # print(rdl_list)
        for lst in rdl_list:
            li = {
                'text': '%s' % (lst.liste),
                'isSend': lst.selectionne,
                'ward': lst.service,
                'mag': lst.zone_fin,
                'id': 'model-%s' % (lst.liste,),
                'user': lst.username or '',
                'icon': 'jstree-file',
                'etat': lst.etat,
                'items_num': lst.nb_item,
                'message': lst.listeerrormodel.error_message if hasattr(lst, 'listeerrormodel') else '',
                'li_attr': {'class': 'color:black, background-color: blue'},
                'date_create': lst.date_creation and lst.date_creation.strftime("%Y-%m-%d %H:%M") or '',
                'order_num': lst.interface,
                'date_update': lst.ddeb and lst.ddeb.strftime("%Y-%m-%d %H:%M") or '',
            }
            ul['children'].append(li)
        res.append(ul)
    return res


def _getRiedlNominativeList():
    res = []
    mags = Magasin.select(Magasin).where(Magasin.eco_type == 'L')
    for m in mags:
        ul = {
            'text': '%s' % m.libelle,
            'id': 'nominat-%i' % (m.pk,),
            'children': []
        }
        # Search all lists affect to this riedl
        rdl_list = FListe.select(FListe, FListeError.message.alias('error_message')) \
            .join(FListeError, JOIN.LEFT_OUTER,
                  on=(FListe.liste == FListeError.liste) & (FListe.mode == FListeError.mode)) \
            .where(
            FListe.mode == TypeListe.Output.value,
            FListe.type_servi == TypeServiListe.Nominatif.value,
            FListe.zone_deb == 'RIEDL', FListe.zone_fin == m.type_mag).order_by(FListe.service)
        # print(rdl_list)
        for lst in rdl_list:
            li = {
                'text': '%s' % (lst.liste,),
                'id': 'model-%s' % (lst.liste,),
                'isSend': lst.selectionne,
                'ward': lst.service,
                'mag': lst.zone_fin,
                'user': lst.username or '',
                'icon': 'jstree-file',
                'etat': lst.etat,
                'message': lst.listeerrormodel.error_message if hasattr(lst, 'listeerrormodel') else '',
                'date_create': str(lst.date_creation) if lst.date_creation is not None else '',
                'order_num': lst.interface,
                'date_update': str(lst.ddeb) if lst.ddeb is not None else '',
            }

            ul['children'].append(li)
        res.append(ul)
    return res


def _getRiedlRetraitLotList():
    res = []
    mags = Magasin.select(Magasin).where(Magasin.eco_type == 'L')
    for m in mags:
        ul = {
            'text': '%s' % m.libelle,
            'id': 'retlot-%i' % (m.pk,),
            'children': []
        }
        # Search all lists affect to this riedl
        rdl_list = FListe.select(FListe, FListeError.message.alias('error_message')) \
            .join(FListeError, JOIN.LEFT_OUTER,
                  on=(FListe.liste == FListeError.liste) & (FListe.mode == FListeError.mode)) \
            .where(
            FListe.mode == TypeListe.Output.value,
            FListe.type_servi == TypeServiListe.RetraitLot.value,
            FListe.zone_deb == 'RIEDL', FListe.zone_fin == m.type_mag).order_by(FListe.service)
        # print(rdl_list)
        for lst in rdl_list:
            li = {
                'text': '%s' % (lst.liste,),
                'isSend': lst.selectionne,
                'id': 'model-%s' % (lst.liste,),
                'user': lst.username or '',
                'ward': lst.service,
                'icon': 'jstree-file',
                'mag': lst.zone_fin,
                'etat': lst.etat,
                'message': lst.listeerrormodel.error_message if hasattr(lst, 'listeerrormodel') else '',
                'date_create': str(lst.date_creation) if lst.date_creation is not None else '',
                'order_num': lst.interface,
                'date_update': str(lst.ddeb) if lst.ddeb is not None else '',
            }

            ul['children'].append(li)
        res.append(ul)
    return res


def _getRiedlPerimeList():
    res = []
    mags = Magasin.select(Magasin).where(Magasin.eco_type == 'L')
    for m in mags:
        ul = {
            'text': '%s' % m.libelle,
            'id': 'perime-%i' % (m.pk,),
            'children': []
        }
        # Search all lists affect to this riedl
        rdl_list = FListe.select(FListe, FListeError.message.alias('error_message')) \
            .join(FListeError, JOIN.LEFT_OUTER,
                  on=(FListe.liste == FListeError.liste) & (FListe.mode == FListeError.mode)) \
            .where(
            FListe.mode == TypeListe.Output.value,
            FListe.type_servi == TypeServiListe.Perimee.value,
            FListe.zone_deb == 'RIEDL', FListe.zone_fin == m.type_mag).order_by(FListe.service)
        # print(rdl_list)
        for lst in rdl_list:
            li = {
                'text': '%s' % (lst.liste,),
                'id': 'model-%s' % (lst.liste,),
                'icon': 'jstree-file',
                'isSend': lst.selectionne,
                'ward': lst.service,
                'mag': lst.zone_fin,
                'user': lst.username or '',
                'etat': lst.etat,
                'message': lst.listeerrormodel.error_message if hasattr(lst, 'listeerrormodel') else '',
                'date_create': str(lst.date_creation) if lst.date_creation is not None else '',
                'order_num': lst.interface,
                'date_update': str(lst.ddeb) if lst.ddeb is not None else '',
            }
            ul['children'].append(li)
        res.append(ul)
    return res


def _split_liste(list_obj, split_mode=RiedlTakingMode.OneBoxGlobalTray.value):
    """
    Split the list in 2
    By default, split mode keep one box on the global list, instead of move to the taking list
    """
    logger.info("Creation liste de prelevement")
    item_list = []

    # Retrieve the taking mode endpoint for this endpoint
    try:
        endp = Endpoint.get(type_dest='RIEDL', secteur=list_obj.zone_fin, type_peigne=list_obj.no_pilulier)
        logger.info("Bouche de sortie standard: %s" % endp.code)
        if not endp.adr1:
            # If endpoint taking mode not defined, we cannot create the list
            # We can creat an f_liste_error, to said not taking mode endpoint defined !
            logger.warning("Pas de bouche de sortie de prélèvement, pour %s" % endp.code)
            logger.warning("Utilisation de la meme sortie")
            endp_prelev = endp
        else:
            endp_prelev = Endpoint.get(type_dest='RIEDL', code=endp.adr1)
            logger.info("Bouche de sortie pour le prélèvement: %s" % endp_prelev.code)
    except DoesNotExist:
        # TODO: if endpoint not exists, we quit or raise and error
        return None

    lst2 = FListe()
    lst2.mode = list_obj.mode
    lst2.liste = list_obj.liste + "-P"
    lst2.etat = EtatListe.Vierge.value
    lst2.type_servi = list_obj.type_servi
    lst2.fusion = "PRELEVEMENT"
    lst2.service = list_obj.service
    lst2.num_sej = list_obj.num_sej
    lst2.ipp = list_obj.ipp
    lst2.num_ipp = list_obj.num_ipp
    lst2.zone_deb = list_obj.zone_deb
    lst2.zone_fin = list_obj.zone_fin
    lst2.pos_pilulier = 1
    lst2.no_pilulier = endp_prelev.type_peigne
    lst2.nb_item = 1
    lst2.selectionne = 0
    lst2.valide_sel = 1
    lst2.liste_origine = ""

    # Retrieve the warehouse for this riedl
    try:
        mag = Magasin.get(Magasin.type_mag == list_obj.zone_fin, Magasin.eco_type == 'L')
    except DoesNotExist:
        # TODO: THis case is not possible
        logger.error("No warehouse found for %s" % list_obj.zone_fin)

    # For each line, compute line with stock and check if necessary to split the list
    itms = FItem.select(FItem).where(FItem.liste == list_obj.liste, FItem.mode == list_obj.mode)
    logger.debug("Found %i items" % len(itms))
    line = 0
    for i in itms:
        logger.info("%s: ref %s" % (i.item, i.reference))
        if i.num_face == 1:
            # Quantity is a multiple of the box, we don't split
            # We got to the next item
            continue

        boxes_before_quantity = 0
        cpt_qte = 0
        init_qte = i.qte_dem
        item_qte = 0
        box_qte = 0
        boxes_qte = 0
        boxes_count = 0
        no_cut = False  # If quantity is equal no need to split the line
        # old_qte = 0
        # check quantity to unload (only unblock stock line)
        stk = Stock.select(Stock).where(Stock.magasin == mag.mag, Stock.reference == i.reference,
                                        Stock.bloque == 0).order_by(Stock.date_peremption)
        logger.debug("Found %i stock lines" % len(stk))
        for s in stk:
            cpt_qte += s.quantite
            box_qte = s.quantite
            boxes_qte += box_qte
            boxes_count += 1

            if init_qte == cpt_qte:  # multiple quantity boxes equal to request quantity, do nothing
                item_qte = 0
                no_cut = True
                logger.debug(
                    "Quantity request %d is a multiple of box(es) quantity(ies) %d (box(es): %d)" %
                    (init_qte, cpt_qte, boxes_count))
                break
            elif init_qte < cpt_qte and boxes_count > 1:
                # We have enougth boxes, we can compute the quantity for taking mode
                # item_qte = init_qte - (cpt_qte - box_qte)
                boxes_before_quantity = cpt_qte - box_qte
                item_qte = init_qte - boxes_before_quantity
                break
            elif init_qte < cpt_qte and boxes_count == 1:
                boxes_before_quantity = init_qte
                item_qte = 0
                break

        if init_qte > cpt_qte:  # If we have not enougth stock we don't split it
            no_cut = True

        # If we found stock we can split the item
        if (no_cut):
            logger.warning("No cutting needed for %s item: %s" % (i.reference, i.item))
            continue
        elif (item_qte <= 0 and split_mode == RiedlTakingMode.OneBoxGlobalTray.value):
            logger.warning("Global, not enougth quantity found for %s item: %s" % (i.reference, i.item))
            continue
        elif (item_qte < 0 and split_mode == RiedlTakingMode.OneBoxTakingTray.value
              and boxes_count > 1):
            logger.warning("TK mode, ot enougth quantity found for %s item: %s" % (i.reference, i.item))
            continue
        else:
            line += 1
            new_item = FItem()
            new_item.liste = lst2.liste
            new_item.mode = lst2.mode
            new_item.etat = EtatListe.Vierge.value
            new_item.dest = lst2.service
            new_item.fraction = 100
            new_item.type_servi = lst2.type_servi
            new_item.reference = i.reference
            new_item.item = "%06d" % line
            new_item.item_wms = i.item_wms
            new_item.qte_dem = item_qte  # Quantité a retournée.
            new_item.qte_serv = 0
            new_item.dtprise = datetime.datetime.now()
            new_item.num_ipp = lst2.num_ipp
            new_item.num_sej = lst2.num_sej

            # We replace the original quantity
            if (split_mode == RiedlTakingMode.OneBoxTakingTray.value and boxes_count == 1):
                new_item.qte_dem = i.qte_dem
                i.qte_dem = 0
                i.etat = EtatListe.Solde.value
            else:
                i.qte_dem = boxes_before_quantity

            # Indicate we use full boxes only
            i.num_face = 1
            i.save()

            item_list.append(new_item)

    # If items on the list, wee
    if item_list:
        for itm in item_list:
            itm.save()

        lst2.nb_item = line
        lst2.save()

        # We indicate we have already split this list
        # By saving the new list name
        list_obj.liste_origine = lst2.liste
        list_obj.save()

        return lst2
    # no new list create, we return None
    return None
