package it.integry.integrywmsnative.gest.ordini_uscita_elenco;

import androidx.lifecycle.MutableLiveData;

import com.annimon.stream.Stream;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import javax.inject.Inject;

import it.integry.barcode_base_android_library.model.BarcodeScanDTO;
import it.integry.integrywmsnative.core.exception.InvalidLUGestioneException;
import it.integry.integrywmsnative.core.exception.InvalidLUMultiCodMdepException;
import it.integry.integrywmsnative.core.exception.InvalidLUMultiGestioneException;
import it.integry.integrywmsnative.core.exception.NoLUFoundException;
import it.integry.integrywmsnative.core.exception.NoOrderFoundException;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILoadingListener;
import it.integry.integrywmsnative.core.model.DtbOrdt;
import it.integry.integrywmsnative.core.model.MtbColt;
import it.integry.integrywmsnative.core.model.MtbGrup;
import it.integry.integrywmsnative.core.model.OrdineInevasoDTO;
import it.integry.integrywmsnative.core.model.secondary.GestioneEnum;
import it.integry.integrywmsnative.core.rest.consumers.BarcodeRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliMagazzinoRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.OrdiniRESTConsumer;
import it.integry.integrywmsnative.core.rest.model.OrdineUscitaInevasoDTO;
import it.integry.integrywmsnative.core.rest.model.SitArtOrdDTO;
import it.integry.integrywmsnative.core.utility.UtilityBarcode;
import it.integry.integrywmsnative.core.utility.UtilityString;
import it.integry.integrywmsnative.gest.ordini_uscita_elenco.rest.OrdiniUscitaElencoRESTConsumer;

public class OrdiniUscitaElencoViewModel {

    private final MutableLiveData<List<OrdiniUscitaElencoDTO>> mOrderList = new MutableLiveData<>();

    private final OrdiniRESTConsumer mOrdiniRESTConsumer;
    private final ColliMagazzinoRESTConsumer mColliMagazzinoRESTConsumer;
    private final BarcodeRESTConsumer mBarcodeRESTConsumer;
    private final OrdiniUscitaElencoRESTConsumer mOrdiniUscitaElencoRESTConsumer;

    private Listener mListener;
    private String mCurrentCodMdep;
    private GestioneEnum mCurrentGestioneOrd;
    private GestioneEnum mCurrentGestioneCol;
    private int mCurrentSegnoCol;

    @Inject
    public OrdiniUscitaElencoViewModel(OrdiniRESTConsumer ordiniRESTConsumer, ColliMagazzinoRESTConsumer colliMagazzinoRESTConsumer, BarcodeRESTConsumer barcodeRESTConsumer, OrdiniUscitaElencoRESTConsumer ordiniUscitaElencoRESTConsumer) {
        this.mOrdiniRESTConsumer = ordiniRESTConsumer;
        this.mColliMagazzinoRESTConsumer = colliMagazzinoRESTConsumer;
        this.mBarcodeRESTConsumer = barcodeRESTConsumer;
        this.mOrdiniUscitaElencoRESTConsumer = ordiniUscitaElencoRESTConsumer;
    }


    public void init(String currentCodMdep, GestioneEnum gestioneOrd, GestioneEnum gestioneCol, int segnoCol) {
        this.mCurrentCodMdep = currentCodMdep;
        this.mCurrentGestioneOrd = gestioneOrd;
        this.mCurrentGestioneCol = gestioneCol;
        this.mCurrentSegnoCol = segnoCol;

        this.sendOnLoadingStarted();

        new Thread(() -> {
            Date loadingStartDate = new Date();

            this.mOrdiniRESTConsumer.getOrdiniInevasi(this.mCurrentCodMdep, mCurrentGestioneOrd,
                    ordiniLavorazione -> {
                        this.mOrderList.postValue(Stream.of(ordiniLavorazione)
                                .map(x -> {
                                    try {
                                        return OrdiniUscitaElencoDTO.fromParent(x);
                                    } catch (Exception ex) {
                                        return null;
                                    }
                                })
                                .toList());

                        long forcedDelaySecs = (new Date().getTime() - loadingStartDate.getTime()) / 1000;

                        if (2 - forcedDelaySecs > 0) {
                            try {
                                Thread.sleep((2 - forcedDelaySecs) * 1000);
                            } catch (Exception ignored) {
                            }
                        }

                        this.sendOnLoadingEnded();
                    }, this::sendError);
        }).start();


    }


    public void loadPicking(List<OrdiniUscitaElencoDTO> selectedOrders, List<MtbGrup> mtbGrupToFilter) {
        List<GestioneEnum> foundGestioni = Stream.of(selectedOrders)
                .map(OrdineUscitaInevasoDTO::getGestioneEnum)
                .withoutNulls()
                .distinct()
                .toList();

        if (foundGestioni.size() > 1) {
            this.sendError(new InvalidLUMultiGestioneException());
            return;
        }


        List<String> foundCodMdep = Stream.of(selectedOrders)
                .map(OrdineUscitaInevasoDTO::getCodMdep)
                .withoutNulls()
                .distinct()
                .toList();

        if (foundCodMdep.size() > 1) {
            this.sendError(new InvalidLUMultiCodMdepException());
            return;
        }


        this.sendOnLoadingStarted();

        List<OrdineUscitaInevasoDTO> selectedOrdersBase = Stream.of(selectedOrders)
                .map(x -> (OrdineUscitaInevasoDTO) x)
                .toList();

        this.mOrdiniRESTConsumer.retrieveListaArticoliFromOrdiniUscita(mCurrentCodMdep, selectedOrdersBase, sitArts -> {

            if (mtbGrupToFilter != null && !mtbGrupToFilter.isEmpty()) {
                sitArts = Stream.of(sitArts)
                        .filter(x -> Stream.of(mtbGrupToFilter).map(MtbGrup::getCodMgrp).anyMatch(y -> y.equalsIgnoreCase(x.getCodMgrp())))
                        .toList();
            }

            List<SitArtOrdDTO> finalSitArts = sitArts;

            this.mOrdiniRESTConsumer.getBancaliGiaRegistrati(
                    Stream.of(selectedOrdersBase).map(x -> (OrdineInevasoDTO) x).toList(),
                    mCurrentGestioneCol,
                    mCurrentSegnoCol,
                    mtbColtList -> {
                        this.sendOnOrdersDispatched(selectedOrdersBase, finalSitArts, mtbColtList);

                        this.sendOnLoadingEnded();
                    }, this::sendError);
        }, this::sendError);
    }


    public void processBarcodeDTO(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        RunnableArgs<List<OrdiniUscitaElencoDTO>> onBarcodeScanComplete = orderList -> {
            this.sendOnOrderFiltered(orderList);
            onComplete.run();
        };

        if (UtilityBarcode.isBarcodeOrdineV(barcodeScanDTO)) {
            this.executeEtichettaOrdineV(barcodeScanDTO.getStringValue(), foundOrder -> {
                this.sendOnFilterNumOrderApplied(foundOrder);
                onComplete.run();
            });
        } else if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
            this.executeEtichettaLU(barcodeScanDTO.getStringValue(), onBarcodeScanComplete);
        } else if (UtilityBarcode.isEtichetta128(barcodeScanDTO)) {
            this.executeEtichettaEan128(barcodeScanDTO, onBarcodeScanComplete);
        } else {
            onComplete.run();
        }
    }


    private void executeEtichettaOrdineV(String barcode, RunnableArgs<List<OrdiniUscitaElencoDTO>> onComplete) {
        String gestione = "";

        char c = barcode.charAt(0);
        if (c == '1')
            gestione = "A";
        else if (c == '2')
            gestione = "L";
        else if (c == '3')
            gestione = "V";

        int numOrd = Integer.parseInt(UtilityString.mid(barcode, 10));

        Date date = null;
        try {
            date = new SimpleDateFormat("ddMMyyyy")
                    .parse(UtilityString.mid(barcode, 2, 8));
        } catch (ParseException ex) {
            this.sendError(ex);
            return;
        }


        final String finalGestione = gestione;
        final Date finalDate = date;

        List<OrdiniUscitaElencoDTO> foundItem = Stream.of(mOrderList.getValue())
                .filter(x -> x.getNumOrd() == numOrd && x.getGestione().equalsIgnoreCase(finalGestione) && x.getDataOrdD().equals(finalDate))
                .toList();

        onComplete.run(foundItem);
    }


    private void executeEtichettaLU(String SSCC, RunnableArgs<List<OrdiniUscitaElencoDTO>> onComplete) {
        this.mColliMagazzinoRESTConsumer.getBySSCC(SSCC, true, false, mtbColt -> {

            if (mtbColt != null && mtbColt.getMtbColr() != null && mtbColt.getMtbColr().size() > 0) {

                if (mtbColt.getGestioneEnum() == GestioneEnum.ACQUISTO || mtbColt.getGestioneEnum() == GestioneEnum.LAVORAZIONE) {
                    //GET BY COMMESSA COLLO
                    this.mOrdiniUscitaElencoRESTConsumer.getOrdiniFromCommessaCollo(mCurrentCodMdep, mtbColt, orderList -> {

                        if (orderList != null && orderList.size() > 0) {
                            List<Integer> numOrds = Stream.of(orderList)
                                    .map(DtbOrdt::getNumOrd)
                                    .toList();

                            List<OrdiniUscitaElencoDTO> filteredOrders = Stream.of(mOrderList.getValue())
                                    .filter(x -> numOrds.contains(x.getNumOrd())).toList();

                            onComplete.run(filteredOrders);
                        } else {
                            this.sendError(new NoOrderFoundException());
                        }

                    }, this::sendError);

                } else {
                    this.sendError(new InvalidLUGestioneException(GestioneEnum.VENDITA));
                }

            } else {
                this.sendError(new NoLUFoundException());
            }

        }, this::sendError);
    }


    private void executeEtichettaEan128(BarcodeScanDTO barcodeScanDTO, RunnableArgs<List<OrdiniUscitaElencoDTO>> onComplete) {
        this.mBarcodeRESTConsumer.decodeEan128(barcodeScanDTO, ean128Model -> {
            String barcodeProd = null;

            if (!UtilityString.isNullOrEmpty(ean128Model.Sscc)) barcodeProd = ean128Model.Sscc;
            if (!UtilityString.isNullOrEmpty(ean128Model.Gtin)) barcodeProd = ean128Model.Gtin;
            if (!UtilityString.isNullOrEmpty(ean128Model.Content))
                barcodeProd = ean128Model.Content;


            if (!UtilityString.isNullOrEmpty(barcodeProd)) {

                if (!UtilityString.isNullOrEmpty(ean128Model.Sscc)) {
                    this.executeEtichettaLU(ean128Model.Sscc, onComplete);
                } else {
                    this.sendError(new NoLUFoundException());
                }
            } else {
                //EAN 128 non completo o comunque mancano i riferimenti al prodotto
                this.sendError(new NoLUFoundException());
            }
        }, this::sendError);
    }

    public MutableLiveData<List<OrdiniUscitaElencoDTO>> getOrderList() {
        return mOrderList;
    }

    public OrdiniUscitaElencoViewModel setListener(Listener listener) {
        this.mListener = listener;
        return this;
    }

    private void sendOnLoadingStarted() {
        if (this.mListener != null) mListener.onLoadingStarted();
    }

    private void sendOnLoadingEnded() {
        if (this.mListener != null) mListener.onLoadingEnded();
    }

    private void sendError(Exception ex) {
        if (this.mListener != null) mListener.onError(ex);
    }

    private void sendOnFilterNumOrderApplied(List<OrdiniUscitaElencoDTO> filteredOrder) {
        if (this.mListener != null) mListener.onFilterNumOrderApplied(filteredOrder);
    }

    private void sendOnOrderFiltered(List<OrdiniUscitaElencoDTO> filteredOrders) {
        if (this.mListener != null) mListener.onOrderFiltered(filteredOrders);
    }

    private void sendOnOrdersDispatched(List<OrdineUscitaInevasoDTO> orders, List<SitArtOrdDTO> sitArts, List<MtbColt> alreadyRegisteredMtbColts) {
        if (this.mListener != null)
            mListener.onOrdersDispatched(orders, sitArts, alreadyRegisteredMtbColts);
    }

    public interface Listener extends ILoadingListener {

        void onError(Exception ex);

        void onFilterNumOrderApplied(List<OrdiniUscitaElencoDTO> filteredOrder);

        void onOrderFiltered(List<OrdiniUscitaElencoDTO> filteredOrders);

        void onOrdersDispatched(List<OrdineUscitaInevasoDTO> orders, List<SitArtOrdDTO> sitArts, List<MtbColt> alreadyRegisteredMtbColts);
    }

}
