import json
import logging
from datetime import datetime

from flask import Blueprint, request, session
from flask_jwt_extended import jwt_required, get_jwt_identity
from median.constant import EtatListe, TypeListe, TypeServiListe, PatientGlobal, HistoryType, EcoType
from median.models import UnitDose, Stock, Magasin, Product, FListe, FItem, User, Adresse, Historique, Gpao
from median.views import RawConfig, Seuil
from peewee import fn, DoesNotExist

from common.status import HTTP_200_OK, HTTP_500_INTERNAL_SERVER_ERROR
from ressources.blueprint.external.external_service import _stock_request, _stock_obj

from ressources.acced.acced_replenish_blueprint import get_counter_replenishment

from common.util import get_config_global_or_local
from ressources.blueprint.move_container_blueprint import _padAddressField

external_stock_blueprint = Blueprint('external_stock', __name__)
logger = logging.getLogger('median')


@external_stock_blueprint.route('/count', methods=['POST'])
@jwt_required()
def count_all_stock_external():
    res = _stock_request(all_stock=True).count()
    return {
        "data": res
    }, HTTP_200_OK


@external_stock_blueprint.route('/all', methods=['POST'])
@jwt_required()
def get_all_stock_external():
    data = json.loads(request.data)
    page = data['pageNumber']
    page_size = data['rowsPerPage']
    stocks = _stock_request(all_stock=True)
    res = (stocks
           .order_by(stocks.c.stock_designation.asc())
           .limit(page_size).offset(page * page_size))

    return {
        "list": [
            _stock_obj(item)
            for item in res.objects()
        ]
    }, HTTP_200_OK


@external_stock_blueprint.route('/<string:equipment_pk>/<string:ref_pk>', methods=['GET'])
@jwt_required()
def get_detail_stock_external(equipment_pk, ref_pk):
    stocks = (Magasin.select(
        Product.reference.alias('stock_reference'),
        Product.designation.alias('stock_designation'),
        Stock.fraction.alias('stock_fraction'),
        Stock.quantite.alias('stock_quantity'),
        Stock.lot.alias('stock_batchNumber'),
        Stock.adresse.alias('stock_address'),
        Stock.ucd.alias('stock_ucd'),
        Stock.pk.alias('stock_pk'),
        Stock.contenant.alias('container_BP'),
        Stock.serial.alias('serial'),
        UnitDose.select(fn.COUNT(UnitDose.pk)).where(UnitDose.contenant == Stock.contenant).alias('nbElt')
    )
              .join(Stock, on=Stock.magasin == Magasin.mag)
              .join(Product, on=Stock.reference == Product.reference)
              .where((Magasin.pk == equipment_pk) & (Product.pk == ref_pk))

              )

    res = []

    for item in stocks.objects():
        adr = next(filter(lambda r: r['adress'] == item.stock_address, res), None)

        if adr is None:
            adr = {
                'adress': item.stock_address,
                'references': []
            }
            res.append(adr)

        ref = {
            'reference': item.stock_reference,
            'fraction': item.stock_fraction,
            'batchNumber': item.stock_batchNumber,
            'quantity': item.stock_quantity,
            'container': item.container_BP,
            'ucd': item.stock_ucd,
            'nbElt': item.nbElt,
            'serial': item.serial,
            'pk': item.stock_pk
        }
        adr['references'].append(ref)

    return {"data": res}, HTTP_200_OK


@external_stock_blueprint.route('/container/<string:container_code>', methods=['GET'])
@jwt_required()
def get_stock_container(container_code):
    container = (UnitDose.select(UnitDose.pk, UnitDose.serial, UnitDose.chrono)
                 .where(UnitDose.contenant == container_code)
                 .order_by(UnitDose.chrono.desc))

    return {
        "list": [
            {
                "pk": item.pk,
                "serial": item.serial,
                "chrono": datetime.utcfromtimestamp(item.chrono.timestamp())
            } for item in container
        ]
    }, HTTP_200_OK


@external_stock_blueprint.route('/outstock', methods=['POST'])
@jwt_required()
def get_stock_external():
    try:
        data = json.loads(request.data)
        page = data['pageNumber']
        page_size = data['rowsPerPage']
    except Exception as ex:
        return {'message': 'Missing key when call API %s' % str(ex)}, HTTP_500_INTERNAL_SERVER_ERROR

    stocks = _stock_request()
    res = (stocks
           .order_by(stocks.c.stock_quantity.asc(), stocks.c.stock_designation.asc())
           .limit(page_size).offset(page * page_size))

    return {
        "list": [
            _stock_obj(item)
            for item in res.objects()
        ]
    }, HTTP_200_OK


@external_stock_blueprint.route('/order', methods=['POST'])
@jwt_required()
def order_external():
    data = json.loads(request.data)
    reference = data['reference']
    fraction = data['fraction']
    quantity = data['quantity']
    equipment_pk = data['equipment']
    service = RawConfig().read(param='k_ua_transfert').value
    identity = get_jwt_identity()
    usr = User.get(User.pk == identity)

    equipment = (Magasin.select(Magasin.type_mag, Magasin.id_robot, Magasin.id_zone, Magasin.type_mag)
                 .where(Magasin.pk == equipment_pk)).get()

    input_lists = (FListe.select(FListe.pk, FListe.liste, FListe.nb_item)
                   .where((FListe.etat == EtatListe.Vierge.value) &
                          (FListe.mode == TypeListe.Input.value) &
                          (FItem.select(fn.COUNT(FItem.pk)).where(FItem.etat != EtatListe.Vierge.value) == 0) &
                          (FListe.zone_fin == equipment.type_mag))
                   .group_by(FListe.pk, FListe.liste)
                   .order_by(FListe.date_creation.desc())).limit(1)

    if len(input_lists) == 1:
        input_list = input_lists[0]
        input_list.nb_item = input_list.nb_item + 1
        input_list.save()

        nb_item = FItem.select(FItem.pk).where(FItem.liste == input_list.liste).count()
        nb_item = nb_item + 1
    else:
        today = datetime.now().strftime("%Y-%m-%d")
        compteur = get_counter_replenishment()

        liste = equipment.type_mag + " PAR " + usr.username + " LE " + today + " No " + compteur

        input_list = FListe.create(
            liste=liste,
            date_creation=datetime.now(),
            etat=EtatListe.Vierge.value,
            mode=TypeListe.Input.value,
            fusion='REASSORT',
            service=service,
            date_modification=datetime.now(),
            zone_fin=equipment.type_mag,
            type_servi=TypeServiListe.GlobaleBoite.value,
            id_servi=2,
            nb_item=1
        )
        nb_item = 1

    Seuil().reappro_item(input_list.liste, nb_item, reference, fraction, quantity,
                         equipment.type_mag, service, equipment.id_zone, equipment.id_robot, 0)
    Seuil().creer_gpao(equipment.id_zone)

    return {}, HTTP_200_OK


@external_stock_blueprint.route('/<string:stock_pk>/move', methods=['PUT'])
@jwt_required()
def move_container(stock_pk):
    """Move the stock from one adress to another"""
    try:
        args = json.loads(request.data)
    except json.decoder.JSONDecodeError:
        return {'message': 'request.json.data.decode.error'}, HTTP_500_INTERNAL_SERVER_ERROR

    ref = args['reference']
    try:
        prod = Product.get(reference=ref)
        logger.info("Move container for product %s" % prod.reference)
    except DoesNotExist:
        return {'message': 'reference.stock.container.reference.not.exists'}, HTTP_500_INTERNAL_SERVER_ERROR

    origin_adresse = _padAddressField(args.get('origin_adresse', ''))
    dest_addresse = _padAddressField(args.get('adresse', ''))
    mag = args.get('magasin', '')  # destination magasin

    lot = args.get('lot', '')
    peremp = args.get('date_peremption', '')
    ucd = args.get('ucd', '')
    fraction = args.get('fraction', '100')

    logger.info('Move container from "%s" to "%s"' % (origin_adresse, dest_addresse))

    # First we need to retrieve the old and the new address
    # If adresses not found, we raise and error
    try:
        address_from = Adresse.get(Adresse.adresse == origin_adresse)
        mag_from = Magasin.get(mag=address_from.magasin, eco_type=EcoType.Externe.value)
        adr_qte_total = (Stock.select(fn.SUM(Stock.quantite)).where(Stock.pk == stock_pk)).scalar()
        logger.info("Quantity at address %s is %d" % (origin_adresse, adr_qte_total))
    except DoesNotExist:
        return {'message': 'reference.stock.container.address.from.notexists'}, HTTP_500_INTERNAL_SERVER_ERROR

    try:
        address_to = Adresse.get(Adresse.adresse == dest_addresse)
        mag_dest = Magasin.get(mag=address_to.magasin)
    except DoesNotExist:
        return {'message': 'reference.stock.container.address.to.notexists'}, HTTP_500_INTERNAL_SERVER_ERROR

    qte_tot = (Stock.select(fn.SUM(Stock.quantite)).where(Stock.reference == ref)).scalar()
    logger.info('Compute the total quantity of this product: "%s"' % (qte_tot))

    # we retrieve the container from the from address
    logger.info('Retrieve the old container code from the adresse: "%s"' % (address_from.contenant))

    # mise à jour dans la table f_stock
    logger.debug('Move stock adresse from the old address to the new address...')
    qqq = Stock.update({Stock.adresse: dest_addresse, Stock.magasin: mag}).where((Stock.pk == stock_pk))
    qqq.execute()

    # mise à jour dans la table f_adr
    logger.info('We update the origin address...')
    logger.info('Original address become free if empty...')

    if Stock.select(Stock.pk).where(Stock.adresse == origin_adresse).count() == 0:
        (Adresse
         .update({Adresse.etat: 'L', Adresse.contenant: ""})
         .where(Adresse.adresse == origin_adresse)
         .execute())

    # l'adresse de destination devient occupée si la destination n'est pas un externe
    if mag_dest.eco_type != EcoType.Externe.value:
        logger.info('New address become occupied...')
        address_to.etat = "O"
        address_to.contenant = address_from.contenant
        address_to.save()

    logger.info('Save the movement to the history table')
    Historique.create(
        chrono=datetime.now(),
        reference=ref,
        adresse=dest_addresse,
        adresse_from=origin_adresse,
        quantite_mouvement=0,
        quantite_totale=qte_tot,
        # service=service,
        type_mouvement=HistoryType.Transfert.value,
        lot=lot,
        pmp=0,
        date_peremption=peremp,
        contenant=address_from.contenant,
        poste='MEDIANWEB',
        ucd=ucd,
        magasin=mag_dest.mag,
        fraction=fraction,
        commentaire="Déplacement contenant",
        utilisateur=session['username']
    )
    # Check if the robot between the 2 equipements are the same, if not we create some GPAO movement
    logger.warning("Check if robot code %s == %s" % (mag_from.id_robot, mag_dest.id_robot))
    if mag_from.id_robot != mag_dest.id_robot:
        logger.warning("The robot code are differents, we generate some GPAO movement")
        # Retrieve the k_ua_tranfert parameters
        cfg_poste_from = get_config_global_or_local(mag_from.type_mag, "k_ua_transfert")
        cfg_poste_dest = get_config_global_or_local(mag_dest.type_mag, "k_ua_transfert")

        logger.info("Send GPAO movement to decrease stock from %s" % mag_from.mag)
        Gpao.create(
            poste='MEDIANWEB',
            etat="A",
            ref=ref,
            qte=(-1 * adr_qte_total),
            lot=lot,
            type_mvt="I",
            dest=cfg_poste_from,
            user=session['username'],
            magasin=mag_from.mag,
            tperemp=peremp,
            contenant=address_from.contenant,
            ucd=ucd,
            fraction=fraction,
            sequence=origin_adresse,
            ipp=PatientGlobal.Ipp.value,
            sejour=PatientGlobal.Sejour.value,
            id_zone=mag_from.id_zone,
            id_robot=mag_from.id_robot
        )

        logger.info("Send GPAO movement to increase stock to %s" % mag_dest.mag)
        Gpao.create(
            poste='MEDIANWEB',
            etat="A",
            ref=ref,
            qte=adr_qte_total,
            lot=lot,
            type_mvt="I",
            dest=cfg_poste_dest,
            user=session['username'],
            magasin=mag_dest.mag,
            tperemp=peremp,
            contenant=address_from.contenant,
            ucd=ucd,
            fraction=fraction,
            sequence=dest_addresse,
            ipp=PatientGlobal.Ipp.value,
            sejour=PatientGlobal.Sejour.value,
            id_zone=mag_dest.id_zone,
            id_robot=mag_dest.id_robot
        )

        logger.info("Send GPAO movement to increase stock to %s" % mag_dest.mag)

    logger.info('End to move the container')

    return "ok"
