package it.integry.integrywmsnative.gest.accettazione_picking;

import androidx.annotation.NonNull;
import androidx.databinding.ObservableArrayList;
import androidx.lifecycle.MutableLiveData;

import com.annimon.stream.Optional;
import com.annimon.stream.Stream;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import javax.inject.Inject;

import it.integry.barcode_base_android_library.model.BarcodeScanDTO;
import it.integry.integrywmsnative.core.CommonConst;
import it.integry.integrywmsnative.core.exception.AlreadyUsedAnonymousLabelException;
import it.integry.integrywmsnative.core.exception.InvalidLUMultiGestioneException;
import it.integry.integrywmsnative.core.exception.InvalidOrderTypeException;
import it.integry.integrywmsnative.core.exception.LUScanNotGrantedException;
import it.integry.integrywmsnative.core.exception.NoArtsFoundException;
import it.integry.integrywmsnative.core.exception.NoPrintersFoundException;
import it.integry.integrywmsnative.core.exception.NoResultFromBarcodeException;
import it.integry.integrywmsnative.core.expansion.AtomicBigDecimal;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.expansion.RunnableArgss;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILUBaseOperationsListener;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILUPrintListener;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILoadingListener;
import it.integry.integrywmsnative.core.model.CommonModelConsts;
import it.integry.integrywmsnative.core.model.MtbAart;
import it.integry.integrywmsnative.core.model.MtbColr;
import it.integry.integrywmsnative.core.model.MtbColt;
import it.integry.integrywmsnative.core.model.dto.PickDataDTO;
import it.integry.integrywmsnative.core.model.secondary.GestioneEnum;
import it.integry.integrywmsnative.core.report.ReportManager;
import it.integry.integrywmsnative.core.rest.consumers.ArticoloRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.BarcodeRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliMagazzinoRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.GestSetupRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.PrinterRESTConsumer;
import it.integry.integrywmsnative.core.rest.model.Ean13PesoModel;
import it.integry.integrywmsnative.core.settings.SettingsManager;
import it.integry.integrywmsnative.core.utility.UtilityBarcode;
import it.integry.integrywmsnative.core.utility.UtilityBigDecimal;
import it.integry.integrywmsnative.core.utility.UtilityDate;
import it.integry.integrywmsnative.core.utility.UtilityString;
import it.integry.integrywmsnative.gest.accettazione.dto.OrdineAccettazioneInevasoDTO;
import it.integry.integrywmsnative.gest.accettazione.dto.SitArtOrdDTO;
import it.integry.integrywmsnative.gest.accettazione_picking.dto.HistoryMtbAartDTO;
import it.integry.integrywmsnative.gest.accettazione_picking.dto.PickingObjectDTO;
import it.integry.integrywmsnative.gest.accettazione_picking.rest.AccettazionePickingRESTConsumer;
import it.integry.integrywmsnative.gest.accettazione_picking.rest.RecoverMtbColt;
import it.integry.integrywmsnative.gest.spedizione.exceptions.InvalidPesoKGException;
import it.integry.integrywmsnative.gest.spedizione.exceptions.NotCurrentYearLUException;
import it.integry.integrywmsnative.gest.spedizione.model.PickedQuantityDTO;

public class AccettazionePickingViewModel {

    private final ArticoloRESTConsumer mArticoloRESTConsumer;
    private final BarcodeRESTConsumer mBarcodeRESTConsumer;
    private final ColliMagazzinoRESTConsumer mColliMagazzinoRESTConsumer;
    private final PrinterRESTConsumer mPrinterRESTConsumer;
    private final AccettazionePickingRESTConsumer mAccettazionePickingRESTConsumer;
    private final GestSetupRESTConsumer mGestSetupRESTConsumer;

    private AccettazionePickingViewModel.Listener mListener;

    private List<OrdineAccettazioneInevasoDTO> mOrders;
    private List<SitArtOrdDTO> mSitArts;
    private final MutableLiveData<List<PickingObjectDTO>> mPickingList = new MutableLiveData<>();

    private MtbColt mCurrentMtbColt = null;
    private GestioneEnum defaultGestioneOfUL = null;
    private String mDefaultCodMdep = null;

    private final List<HistoryMtbAartDTO> mHistoryUsedAarts = new ArrayList<>();

    @Inject
    public AccettazionePickingViewModel(ArticoloRESTConsumer articoloRESTConsumer,
                                        BarcodeRESTConsumer barcodeRESTConsumer,
                                        ColliMagazzinoRESTConsumer colliMagazzinoRESTConsumer,
                                        PrinterRESTConsumer printerRESTConsumer,
                                        AccettazionePickingRESTConsumer accettazionePickingRESTConsumer,
                                        GestSetupRESTConsumer gestSetupRESTConsumer) {
        this.mArticoloRESTConsumer = articoloRESTConsumer;
        this.mBarcodeRESTConsumer = barcodeRESTConsumer;
        this.mColliMagazzinoRESTConsumer = colliMagazzinoRESTConsumer;
        this.mPrinterRESTConsumer = printerRESTConsumer;
        this.mAccettazionePickingRESTConsumer = accettazionePickingRESTConsumer;
        this.mGestSetupRESTConsumer = gestSetupRESTConsumer;
    }


    public void init(List<OrdineAccettazioneInevasoDTO> orders, List<SitArtOrdDTO> sitArts, String codMdep) {
        this.mOrders = orders;
        this.mDefaultCodMdep = codMdep;

        this.mSitArts = Stream.of(sitArts)
                .filter(x -> UtilityBigDecimal.greaterThan(x.getNumCnfDaEvadere(), BigDecimal.ZERO) && UtilityBigDecimal.greaterThan(x.getQtaDaEvadere(), BigDecimal.ZERO))
                .toList();

        getEmptyPickingList(this.mSitArts, pickingObjectList -> {
            this.mPickingList.postValue(pickingObjectList);
        });

        //Definizione della gestione collo di default
        Boolean isOrdTrasf = Stream.of(mOrders)
                .map(OrdineAccettazioneInevasoDTO::isOrdTrasf)
                .withoutNulls()
                .distinctBy(x -> x)
                .findFirst()
                .get();

        //Definizione della gestione collo di default
        List<GestioneEnum> foundGestioni = Stream.of(mOrders)
                .map(OrdineAccettazioneInevasoDTO::getGestioneEnum)
                .withoutNulls()
                .distinctBy(x -> x)
                .toList();

        if (foundGestioni != null) {

            if (foundGestioni.size() == 1) {
                if (isOrdTrasf && (foundGestioni.get(0) == GestioneEnum.PRODUZIONE || foundGestioni.get(0) == GestioneEnum.LAVORAZIONE)) {
                    defaultGestioneOfUL = GestioneEnum.ACQUISTO;
                } else
                    defaultGestioneOfUL = foundGestioni.get(0) == GestioneEnum.PRODUZIONE ? GestioneEnum.LAVORAZIONE : foundGestioni.get(0);
            } else {
                this.sendError(new InvalidLUMultiGestioneException());
            }
        } else {
            this.sendError(new InvalidOrderTypeException());
        }
    }

    private void getEmptyPickingList(List<SitArtOrdDTO> sitArtOrdList, RunnableArgs<List<PickingObjectDTO>> onComplete) {

        List<String> codMarts = Stream.of(sitArtOrdList)
                .map(SitArtOrdDTO::getCodMart)
                .toList();

        this.mArticoloRESTConsumer.getByCodMarts(codMarts, listMtbAarts -> {
            List<PickingObjectDTO> pickingList = Stream.of(sitArtOrdList)
                    .map(sitArtOrdDTO -> {
                        MtbAart mtbAart = null;

                        for (MtbAart mtbAartItem : listMtbAarts) {
                            if (mtbAartItem.getCodMart().equalsIgnoreCase(sitArtOrdDTO.getCodMart())) {
                                mtbAart = mtbAartItem;
                                break;
                            }
                        }

                        return new PickingObjectDTO()
                                .setSitArtOrdDTO(sitArtOrdDTO)
                                .setMtbAart(mtbAart);
                    })
                    .toList();

            onComplete.run(pickingList);
        }, this::sendError);
    }

    public MutableLiveData<List<PickingObjectDTO>> getPickingList() {
        return mPickingList;
    }

    public void retrieveExistentLU(RunnableArgs<ArrayList<RecoverMtbColt>> onComplete) {
        this.sendOnLoadingStarted();

        this.mAccettazionePickingRESTConsumer.getBancaliGiaRegistrati(this.mOrders, mtbColtList -> {
            this.sendOnLoadingEnded();
            onComplete.run(mtbColtList);
        }, this::sendError);
    }


    public void recoverLU(MtbColt recoveredMtbColt) {
        this.mCurrentMtbColt = recoveredMtbColt;

        this.resetMatchedRows();
        this.sendLUOpened(this.mCurrentMtbColt);
    }


    public void processBarcodeDTO(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        //Se non c'è una UL aperta
        if (mCurrentMtbColt == null) {

            //Se è un'etichetta anonima
            if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
                //Se il collo non esiste allora lo creo associandolo a questa etichetta anonima
                //invece se esiste apro un collo nuovo e cerco gli articoli presenti nell'ul
                //dell'etichetta anonima
                this.executeEtichettaAnonimaNotOpenedLU(barcodeScanDTO, onComplete);
            } else {
                this.processBarcodeNotOpenedLU(barcodeScanDTO, onComplete);
            }

        } else {
            if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
                this.sendError(new LUScanNotGrantedException());
            } else {
                this.processBarcodeAlreadyOpenedLU(barcodeScanDTO, onComplete);
            }
        }
    }

    private void executeEtichettaAnonimaNotOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        mColliMagazzinoRESTConsumer.getBySSCC(barcodeScanDTO.getStringValue(), true, false, mtbColt -> {

            if (mtbColt == null) {
                if (!UtilityBarcode.isEtichettaAnonimaOfCurrentYear(barcodeScanDTO.getStringValue())) {
                    this.sendError(new NotCurrentYearLUException());
                } else {

                    this.createNewLU(
                            UtilityBarcode.getNumColloFromULAnonima(barcodeScanDTO.getStringValue()),
                            CommonConst.Config.DEFAULT_ANONYMOUS_UL_SERIE, true, onComplete);
                }
            } else {
                this.sendError(new AlreadyUsedAnonymousLabelException());
            }

        }, this::sendError);
    }

    private void processBarcodeNotOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        this.createNewLU(
                null,
                null,
                false,
                () -> processBarcodeAlreadyOpenedLU(barcodeScanDTO, onComplete));
    }

    private void processBarcodeAlreadyOpenedLU(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        if (UtilityBarcode.isEanPeso(barcodeScanDTO)) {
            //Cerco tramite etichetta ean 128 (che può indicarmi un articolo o una UL)
            this.executeEtichettaEanPeso(barcodeScanDTO, onComplete);


        } else if (UtilityBarcode.isEtichetta128(barcodeScanDTO)) {
            //Cerco tramite etichetta ean 128 (che può indicarmi un articolo o una UL)
            this.executeEtichettaEan128(barcodeScanDTO, onComplete);


        } else {
            this.loadArticolo(barcodeScanDTO.getStringValue(), null, onComplete);
        }
    }

    private void executeEtichettaEanPeso(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        try {
            Ean13PesoModel ean13PesoModel = Ean13PesoModel.fromBarcode(barcodeScanDTO.getStringValue());

            this.loadArticolo(ean13PesoModel.getPrecode(), PickDataDTO.fromEan128(ean13PesoModel.toEan128()), onComplete);
        } catch (Exception ex) {
            this.sendError(ex);
        }
    }

    private void executeEtichettaEan128(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        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.sendError(new LUScanNotGrantedException());
                } else {
                    if (barcodeProd.startsWith("0") || barcodeProd.startsWith("9")) {
                        barcodeProd = barcodeProd.substring(1);
                    }

                    PickDataDTO pickDataDTO = PickDataDTO.fromEan128(ean128Model);
                    this.loadArticolo(barcodeProd, pickDataDTO, onComplete);
                }
            } else {
                //EAN 128 non completo o comunque mancano i riferimenti al prodotto
                onComplete.run();
            }
        }, this::sendError);
    }


    private void loadArticolo(String barcodeProd, PickDataDTO pickData, Runnable onComplete) {
        if (barcodeProd.length() == 14) {
            barcodeProd = UtilityBarcode.convertITF14toEAN13(barcodeProd);
        }

        this.mArticoloRESTConsumer.getByBarcodeProd(barcodeProd, mtbAartList -> {

            if (mtbAartList != null && mtbAartList.size() > 0) {
                this.searchArtFromAnag(mtbAartList.get(0), pickData, onComplete);
            } else {
                this.sendError(new NoResultFromBarcodeException());
            }

        }, this::sendError);
    }


    private void searchArtFromAnag(MtbAart mtbAart, PickDataDTO pickData, Runnable onComplete) {
        final List<PickingObjectDTO> pickingList = mPickingList.getValue();

        List<PickingObjectDTO> matchPickingObject = Stream.of(pickingList)
                .filter(x -> UtilityString.equalsIgnoreCase(mtbAart.getCodMart(), x.getSitArtOrdDTO().getCodMart()))
                .toList();

        for (PickingObjectDTO pickingObjectDTO : matchPickingObject) {
            pickingObjectDTO.setTempPickData(pickData);
        }

        this.loadMatchedRows(matchPickingObject);
        onComplete.run();
    }

    private void loadMatchedRows(List<PickingObjectDTO> matchedRows) {
        if (matchedRows == null || matchedRows.size() == 0) {
            this.sendError(new NoArtsFoundException());
        } else if (matchedRows.size() == 1) {
            this.dispatchOrdineRow(matchedRows.get(0));
        } else {
            List<PickingObjectDTO> pickingList = mPickingList.getValue();

            for (int i = 0; i < pickingList.size(); i++) {
                if (!matchedRows.contains(pickingList.get(i))) {
                    pickingList.get(i).setHidden(true);
                }
            }

            this.sendFilterApplied(null);
            this.getPickingList().postValue(pickingList);
        }
    }

    public void createNewLU(Integer customNumCollo, String customSerCollo, boolean disablePrint, Runnable onComplete) {
        this.sendOnNoteAggiuntiveRequest(additionalNotes -> {

            this.mGestSetupRESTConsumer.getValue("PICKING", "SETUP", "DEFAULT_POSIZIONE_COLLI_ACCETTAZIONE", defaultPosAccettazioneDTO -> {

                String defaultPosAccettazione = null;
                if (defaultPosAccettazioneDTO != null && !UtilityString.isNullOrEmpty(defaultPosAccettazioneDTO.value))
                    defaultPosAccettazione = defaultPosAccettazioneDTO.value;

                MtbColt mtbColt = new MtbColt();
                mtbColt
                        .initDefaultFields()
                        .setGestione(defaultGestioneOfUL)
                        .setAnnotazioni(additionalNotes)
                        .setPosizione(defaultPosAccettazione)
                        .setOperation(CommonModelConsts.OPERATION.INSERT_OR_UPDATE);

                if (customNumCollo != null) {
                    mtbColt.setNumCollo(customNumCollo);
                }

                if (!UtilityString.isNullOrEmpty(customSerCollo)) {
                    mtbColt.setSerCollo(customSerCollo);
                }

                List<String> codAnags =
                        Stream.of(mOrders)
                                .map(OrdineAccettazioneInevasoDTO::getCodAnagOrd)
                                .distinct()
                                .toList();

                if (codAnags != null && codAnags.size() == 1) {
                    mtbColt.setCodAnag(codAnags.get(0));
                }

                List<String> rifOrds =
                        Stream.of(mOrders)
                                .map(OrdineAccettazioneInevasoDTO::getRifOrd)
                                .distinct()
                                .toList();

                if (rifOrds != null && rifOrds.size() == 1) {
                    mtbColt.setRifOrd(rifOrds.get(0));
                }

                List<String> numDataOrds =
                        Stream.of(mOrders)
                                .map(value -> value.getNumero() + value.getData())
                                .distinct()
                                .toList();

                if (numDataOrds != null && numDataOrds.size() == 1) {
                    mtbColt.setNumOrd(mOrders.get(0).getNumero());
                    mtbColt.setDataOrd(mOrders.get(0).getData());

                    try {
                        mtbColt.generaFiltroOrdine();
                    } catch (Exception ex) {
                        this.sendError(ex);
                        return;
                    }
                }

                this.mColliMagazzinoRESTConsumer.saveCollo(mtbColt, savedMtbColt -> {
                    mtbColt
                            .setNumCollo(savedMtbColt.getNumCollo())
                            .setDataCollo(savedMtbColt.getDataColloS())
                            .setMtbColr(new ObservableArrayList<>())
                            .setDisablePrint(disablePrint);

                    this.mCurrentMtbColt = mtbColt;

                    if (onComplete != null) onComplete.run();
                    this.sendLUOpened(mtbColt);
                }, this::sendError);

            }, this::sendError);

        });
    }


    public void dispatchOrdineRow(final PickingObjectDTO pickingObjectDTO) {
        if (this.mCurrentMtbColt == null) return;

        BigDecimal totalQtaOrd = pickingObjectDTO.getSitArtOrdDTO().getQtaOrd();
        BigDecimal totalNumCnfOrd = pickingObjectDTO.getSitArtOrdDTO().getNumCnfOrd();
        BigDecimal qtaCnfOrd = pickingObjectDTO.getSitArtOrdDTO().getQtaCnfOrd();

        AtomicBigDecimal numCnfWithdrawRows = new AtomicBigDecimal();
        AtomicBigDecimal qtaColWithdrawRows = new AtomicBigDecimal();

        Stream.of(pickingObjectDTO.getWithdrawMtbColrs())
                .forEach(row -> {
                    numCnfWithdrawRows.addAndGet(row.getNumCnf());
                    qtaColWithdrawRows.addAndGet(row.getQtaCol());
                });

        BigDecimal numCnfDaEvadere = pickingObjectDTO.getSitArtOrdDTO().getNumCnfDaEvadere().subtract(numCnfWithdrawRows.getBigDecimalValue());
        BigDecimal qtaDaEvadere = pickingObjectDTO.getSitArtOrdDTO().getQtaDaEvadere().subtract(qtaColWithdrawRows.getBigDecimalValue());
        BigDecimal qtaCnfDaEvadere = qtaCnfOrd;

        numCnfDaEvadere = UtilityBigDecimal.getGreaterBetween(numCnfDaEvadere, BigDecimal.ZERO);
        qtaDaEvadere = UtilityBigDecimal.getGreaterBetween(qtaDaEvadere, BigDecimal.ZERO);

        BigDecimal numCnfDaPrelevare = null;
        BigDecimal qtaColDaPrelevare = null;
        BigDecimal qtaCnfDaPrelevare = null;

        String partitaMag = null;
        Date dataScad = null;

        if (pickingObjectDTO.getTempPickData() != null && pickingObjectDTO.getTempPickData().getManualPickDTO() != null) {
            //Oppure le info del barcode scansionato
            PickDataDTO.ManualPickDTO manualPickDTO = pickingObjectDTO.getTempPickData().getManualPickDTO();
            MtbAart mtbAart = pickingObjectDTO.getMtbAart();


            qtaColDaPrelevare = manualPickDTO.getQtaTot();
            numCnfDaPrelevare = manualPickDTO.getNumCnf();

            if (mtbAart.isFlagQtaCnfFissaBoolean()) {
                qtaCnfDaPrelevare = mtbAart.getQtaCnf();

                if (manualPickDTO.isEanPeso()) {
                    if (mtbAart.getUntMisRifPeso() == MtbAart.UntMisRifPesoEnum.M) {
                        if (UtilityBigDecimal.equalsTo(mtbAart.getPesoKg(), BigDecimal.ZERO)) {
                            this.sendError(new InvalidPesoKGException());
                        } else {
                            qtaColDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, mtbAart.getPesoKg());
                        }
                    }
                }
            } else if (qtaColDaPrelevare != null && numCnfDaPrelevare != null) {
                qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);
            }


            if (qtaColDaPrelevare != null && numCnfDaPrelevare == null) {
                numCnfDaPrelevare = UtilityBigDecimal.divideAndRoundToInteger(qtaColDaPrelevare, mtbAart.getQtaCnf());

                if (!mtbAart.isFlagQtaCnfFissaBoolean()) {
                    if (UtilityBigDecimal.equalsTo(numCnfDaPrelevare, BigDecimal.ZERO))
                        numCnfDaPrelevare = BigDecimal.ONE;
                    qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);
                }
            } else if (numCnfDaPrelevare != null && qtaColDaPrelevare == null) {
                qtaCnfDaPrelevare = mtbAart.getQtaCnf();
                qtaColDaPrelevare = UtilityBigDecimal.multiply(numCnfDaPrelevare, qtaCnfDaPrelevare);
            }

            if (manualPickDTO.getMtbPartitaMag() != null) {
                partitaMag = manualPickDTO.getMtbPartitaMag().getPartitaMag();
                dataScad = manualPickDTO.getMtbPartitaMag().getDataScadD();
            }
        }

        BigDecimal initialNumCnf;
        BigDecimal initialQtaCnf;
        BigDecimal initialQtaTot;

        if (qtaColDaPrelevare != null) {
            initialNumCnf = numCnfDaPrelevare;
            initialQtaCnf = qtaCnfDaPrelevare;
            initialQtaTot = qtaColDaPrelevare;

        } else {
            initialNumCnf = totalNumCnfOrd;
            initialQtaCnf = qtaCnfOrd;
            initialQtaTot = totalQtaOrd;
        }

        if (UtilityBigDecimal.greaterThan(pickingObjectDTO.getMtbAart().getColliPedana(), BigDecimal.ZERO) && UtilityBigDecimal.greaterThan(initialNumCnf, pickingObjectDTO.getMtbAart().getColliPedana())) {
            initialNumCnf = pickingObjectDTO.getMtbAart().getColliPedana();
            initialQtaTot = initialNumCnf.multiply(initialQtaCnf);
        }

        if (dataScad == null && pickingObjectDTO.getMtbAart().getGgScadPartita() != null && pickingObjectDTO.getMtbAart().getGgScadPartita() > 0) {
            dataScad = UtilityDate.getDateInstance();
            Calendar c = new GregorianCalendar();
            c.setTime(dataScad);
            c.add(Calendar.DATE, pickingObjectDTO.getMtbAart().getGgScadPartita());
            dataScad = c.getTime();
        }

        if(partitaMag == null && dataScad == null) {
            HistoryMtbAartDTO historyMtbAartDTO = this.getHistoryItemIfExists(pickingObjectDTO.getMtbAart().getCodMart());

            if(historyMtbAartDTO != null) {
                partitaMag = historyMtbAartDTO.getPartitaMag();
                dataScad = historyMtbAartDTO.getDataScad();
            }
        }

        this.sendOnItemDispatched(
                pickingObjectDTO,
                pickingObjectDTO.getMtbAart(),
                initialNumCnf, initialQtaCnf, initialQtaTot,
                qtaDaEvadere,
                numCnfDaEvadere,
                qtaCnfDaEvadere,
                qtaColDaPrelevare,
                numCnfDaPrelevare,
                qtaCnfDaPrelevare,
                null,
                null,
                null,
                partitaMag,
                dataScad,
                true,
                (pickedQuantityDTO, shouldCloseLU) -> {
                    this.saveNewRow(pickingObjectDTO,
                            pickedQuantityDTO.getNumCnf(),
                            pickedQuantityDTO.getQtaCnf(),
                            pickedQuantityDTO.getQtaTot(),
                            pickedQuantityDTO.getPartitaMag(),
                            pickedQuantityDTO.getDataScad(),
                            shouldCloseLU);
                });

    }


    public void saveNewRow(PickingObjectDTO pickingObjectDTO, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag, Date dataScad, boolean shouldCloseLU) {
        this.sendOnLoadingStarted();

        String gestioneRif = pickingObjectDTO.getSitArtOrdDTO().getGestioneEnum() == GestioneEnum.PRODUZIONE ? "L" : pickingObjectDTO.getSitArtOrdDTO().getGestione();

        final MtbColr mtbColr = new MtbColr()
                .setCodMart(pickingObjectDTO.getMtbAart().getCodMart())
                .setPartitaMag(partitaMag)
                .setDataScadPartita(dataScad)
                .setQtaCol(qtaTot)
                .setQtaCnf(qtaCnf)
                .setNumCnf(numCnf)
                .setGestioneRif(gestioneRif)
                .setCodJcom(pickingObjectDTO.getSitArtOrdDTO().getCodJcom())
                .setDescrizione(pickingObjectDTO.getMtbAart().getDescrizioneEstesa())
                .setDatetimeRow(UtilityDate.getDateInstance())
                .setDataOrd(pickingObjectDTO.getSitArtOrdDTO().getDataOrd())
                .setNumOrd(pickingObjectDTO.getSitArtOrdDTO().getNumOrd())
                .setRigaOrd(pickingObjectDTO.getSitArtOrdDTO().getRigaOrd());


        this.addHistoryItem(mtbColr);

        mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT_OR_UPDATE);

        MtbColt cloneMtbColt = (MtbColt) mCurrentMtbColt.clone();
        cloneMtbColt.setOperation(CommonModelConsts.OPERATION.UPDATE);

        cloneMtbColt.setMtbColr(new ObservableArrayList<>());
        cloneMtbColt.getMtbColr().add((MtbColr) mtbColr.clone());

        if (UtilityBigDecimal.equalsTo(numCnf, BigDecimal.ZERO) && UtilityBigDecimal.equalsTo(qtaTot, BigDecimal.ZERO)) {
            resetMatchedRows();
            this.sendOnLoadingEnded();

            if (shouldCloseLU) closeLU();
            return;
        }

        mColliMagazzinoRESTConsumer.saveCollo(cloneMtbColt, value -> {

            mtbColr
                    .setDataCollo(value.getDataColloS())
                    .setNumCollo(value.getNumCollo())
                    .setGestione(value.getGestione())
                    .setSerCollo(value.getSerCollo())
                    .setRiga(value.getMtbColr().get(value.getMtbColr().size() - 1).getRiga())
                    .setUntMis(pickingObjectDTO.getMtbAart().getUntMis())
                    .setMtbAart(pickingObjectDTO.getMtbAart());

            pickingObjectDTO.getWithdrawMtbColrs().add(mtbColr);
            mCurrentMtbColt.getMtbColr().add(mtbColr);

            //Chiamato removeListFilter perché cosi mi cancella tutti i dati di pick temporanei
            resetMatchedRows();

            this.sendOnRowSaved();
            this.sendOnLoadingEnded();

            if (shouldCloseLU) closeLU();
        }, this::sendError);
    }


    public void dispatchRowEdit(MtbColr mtbColrToEdit) {
        final PickingObjectDTO pickingObjectDTO = new PickingObjectDTO()
                .setMtbAart(mtbColrToEdit.getMtbAart());

        this.sendOnItemDispatched(
                pickingObjectDTO,
                pickingObjectDTO.getMtbAart(),
                mtbColrToEdit.getNumCnf(),
                mtbColrToEdit.getQtaCnf(),
                mtbColrToEdit.getQtaCol(),
                null,
                null,
                null,
                null, null, null,
                null, null, null,
                mtbColrToEdit.getPartitaMag(),
                mtbColrToEdit.getDataScadPartitaD(),
                true,
                (pickedQuantityDTO, shouldCloseLU) -> {

                    this.saveEditedRow(mtbColrToEdit,
                            pickedQuantityDTO.getNumCnf(),
                            pickedQuantityDTO.getQtaCnf(),
                            pickedQuantityDTO.getQtaTot(),
                            pickedQuantityDTO.getPartitaMag(),
                            pickedQuantityDTO.getDataScad(),
                            shouldCloseLU);
                });
    }

    private void saveEditedRow(MtbColr mtbColrToUpdate, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag, Date dataScad, boolean shouldCloseLU) {
        this.sendOnLoadingStarted();

        MtbColt mtbColt = new MtbColt()
                .setNumCollo(mtbColrToUpdate.getNumCollo())
                .setDataCollo(mtbColrToUpdate.getDataColloS())
                .setSerCollo(mtbColrToUpdate.getSerCollo())
                .setGestione(mtbColrToUpdate.getGestione())
                .setMtbColr(new ObservableArrayList<>());
        mtbColt.setOperation(CommonModelConsts.OPERATION.NO_OP);

        final MtbColr mtbColr = (MtbColr) mtbColrToUpdate.clone();
        mtbColr.setOperation(CommonModelConsts.OPERATION.UPDATE);
        mtbColr
                .setNumCnf(numCnf)
                .setQtaCnf(qtaCnf)
                .setQtaCol(qtaTot)
                .setPartitaMag(partitaMag)
                .setDataScadPartita(dataScad)
                .setUtente(SettingsManager.i().getUser().getFullname())
                .setDatetimeRow(UtilityDate.getDateInstance());

        mtbColt.getMtbColr().add(mtbColr);

        this.addHistoryItem(mtbColr);

        this.mColliMagazzinoRESTConsumer.saveCollo(mtbColt, (value) -> {

            mtbColr.setNumCnf(numCnf)
                    .setQtaCnf(qtaCnf)
                    .setQtaCol(qtaTot);

            Optional<PickingObjectDTO> pickingObjectDTO = Stream.of(this.mPickingList.getValue())
                    .filter(x -> Stream.of(x.getWithdrawMtbColrs()).anyMatch(y -> y == mtbColrToUpdate))
                    .findSingle();

            if (pickingObjectDTO.isPresent()) {
                pickingObjectDTO.get().getWithdrawMtbColrs().remove(mtbColrToUpdate);
                pickingObjectDTO.get().getWithdrawMtbColrs().add(mtbColr);
            }

            this.mCurrentMtbColt.getMtbColr().remove(mtbColrToUpdate);
            this.mCurrentMtbColt.getMtbColr().add(mtbColr);

            //Chiamato removeListFilter perché cosi mi cancella tutti i dati di pick temporanei
            resetMatchedRows();

            this.sendOnRowSaved();
            this.sendOnLoadingEnded();

            if (shouldCloseLU) closeLU();
        }, this::sendError);
    }

    public void deleteRow(MtbColr mtbColrToDelete) {
        this.sendMtbColrDeleteRequest(shouldDelete -> {

            if (shouldDelete) {
                this.sendOnLoadingStarted();
                MtbColt mtbColt = new MtbColt()
                        .setNumCollo(mtbColrToDelete.getNumCollo())
                        .setDataCollo(mtbColrToDelete.getDataColloS())
                        .setSerCollo(mtbColrToDelete.getSerCollo())
                        .setGestione(mtbColrToDelete.getGestione())
                        .setMtbColr(new ObservableArrayList<>());
                mtbColt.setOperation(CommonModelConsts.OPERATION.NO_OP);

                MtbColr mtbColr = (MtbColr) mtbColrToDelete.clone();
                mtbColr.setOperation(CommonModelConsts.OPERATION.INSERT);
                mtbColr.setQtaCol(mtbColr.getQtaCol().multiply(new BigDecimal(-1)))
                        .setNumCnf(mtbColr.getNumCnf().multiply(new BigDecimal(-1)))
                        .setRiga(null);

                mtbColt.getMtbColr().add(mtbColr);

                this.mColliMagazzinoRESTConsumer.saveCollo(mtbColt, (value) -> {

                    Optional<PickingObjectDTO> pickingObjectDTO = Stream.of(this.mPickingList.getValue())
                            .filter(x -> Stream.of(x.getWithdrawMtbColrs()).anyMatch(y -> y == mtbColrToDelete))
                            .findSingle();

                    if (pickingObjectDTO.isPresent()) {
                        pickingObjectDTO.get().getWithdrawMtbColrs().remove(mtbColrToDelete);
                    }

                    this.mCurrentMtbColt.getMtbColr().remove(mtbColrToDelete);

                    this.resetMatchedRows();
                    this.sendOnRowSaved();
                    this.sendOnLoadingEnded();

                }, this::sendError);
            }
        });
    }

    public void closeLU() {
        if (mCurrentMtbColt == null) return;

        this.sendOnLoadingStarted();

        mColliMagazzinoRESTConsumer.canLUBeDeleted(mCurrentMtbColt, canBeDeleted -> {
            if (canBeDeleted) {
                deleteLU(() -> {
                    this.sendLUClosed();
                    this.sendOnLoadingEnded();
                });
            } else {
                mColliMagazzinoRESTConsumer.updateDataFine(mCurrentMtbColt, () -> {

                    if (!mCurrentMtbColt.getDisablePrint()) {
                        printCollo(this::postCloseOperations);
                    } else {
                        postCloseOperations();
                    }

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

    private void printCollo(Runnable onComplete) {
        this.mPrinterRESTConsumer.getAvailablePrinters(mDefaultCodMdep, PrinterRESTConsumer.Type.SECONDARIA, printerList -> {

            if (printerList == null || printerList.size() == 0) {
                this.sendError(new NoPrintersFoundException());
                onComplete.run();
                return;
            }

            String reportName = ReportManager.getReportNameLUFromGestione(GestioneEnum.ACQUISTO);

            this.mPrinterRESTConsumer.printCollo(
                    printerList.get(0),
                    mCurrentMtbColt,
                    1,
                    reportName, () -> {
                        this.sendLUSuccessfullyPrinted();
                        onComplete.run();
                    }, ex -> this.sendLUPrintError(ex, onComplete));
        }, this::sendError);

    }

    private void postCloseOperations() {
        this.mCurrentMtbColt = null;
        this.sendLUClosed();
        this.sendOnLoadingEnded();
    }

    private void deleteLU(Runnable onComplete) {
        mColliMagazzinoRESTConsumer.deleteCollo(mCurrentMtbColt, () -> {
            this.mCurrentMtbColt = null;

            if (onComplete != null) onComplete.run();
        }, this::sendError);
    }

    public void resetMatchedRows() {
        for (PickingObjectDTO pickingObjectDTO : this.mPickingList.getValue()) {
            pickingObjectDTO
                    .setTempPickData(null)
                    .setHidden(false);
        }

        this.mPickingList.postValue(this.mPickingList.getValue());
        this.sendFilterRemoved();
    }

    private void addHistoryItem(@NonNull MtbColr mtbColr) {
        Optional<HistoryMtbAartDTO> optional = Stream.of(this.mHistoryUsedAarts)
                .filter(x -> x.getCodMart().equalsIgnoreCase(mtbColr.getCodMart()))
                .findFirst();

        if (optional.isPresent()) {
            optional.get()
                    .setPartitaMag(mtbColr.getPartitaMag())
                    .setDataScad(mtbColr.getDataScadPartitaD());
        } else {
            this.mHistoryUsedAarts.add(new HistoryMtbAartDTO()
                    .setCodMart(mtbColr.getCodMart())
                    .setPartitaMag(mtbColr.getPartitaMag())
                    .setDataScad(mtbColr.getDataScadPartitaD()));
        }
    }

    private HistoryMtbAartDTO getHistoryItemIfExists(@NonNull String codMart) {
        Optional<HistoryMtbAartDTO> optional = Stream.of(this.mHistoryUsedAarts)
                .filter(x -> x.getCodMart().equalsIgnoreCase(codMart))
                .findFirst();

        return optional.isPresent() ? optional.get() : null;
    }

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

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

    private void sendOnNoteAggiuntiveRequest(RunnableArgs<String> onComplete) {
        if (this.mListener != null) this.mListener.onNoteAggiuntiveRequest(onComplete);
    }

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

    private void sendLUOpened(MtbColt mtbColt) {
        if (this.mListener != null) mListener.onLUOpened(mtbColt);
    }

    private void sendLUClosed() {
        if (this.mListener != null) mListener.onLUClosed();
    }

    private void sendLUSuccessfullyPrinted() {
        if (this.mListener != null) mListener.onLUSuccessullyPrinted();
    }

    private void sendLUPrintError(Exception ex, Runnable onComplete) {
        if (this.mListener != null) mListener.onLUPrintError(ex, onComplete);
    }

    private void sendOnItemDispatched(PickingObjectDTO pickingObjectDTO,
                                      MtbAart mtbAart,
                                      BigDecimal initialNumCnf,
                                      BigDecimal initialQtaCnf,
                                      BigDecimal initialQtaTot,
                                      BigDecimal totalQtaOrd,
                                      BigDecimal totalNumCnfOrd,
                                      BigDecimal qtaCnfOrd,
                                      BigDecimal totalQtaToBeTaken,
                                      BigDecimal totalNumCnfToBeTaken,
                                      BigDecimal qtaCnfToBeTaken,
                                      BigDecimal totalQtaAvailable,
                                      BigDecimal totalNumCnfAvailable,
                                      BigDecimal qtaCnfAvailable,
                                      String partitaMag,
                                      Date dataScad,
                                      boolean canPartitaMagBeChanged,
                                      RunnableArgss<PickedQuantityDTO, Boolean> onComplete) {
        if (this.mListener != null) mListener.onItemDispatched(pickingObjectDTO,
                mtbAart,
                initialNumCnf,
                initialQtaCnf,
                initialQtaTot,
                totalQtaOrd,
                totalNumCnfOrd,
                qtaCnfOrd,
                totalQtaToBeTaken,
                totalNumCnfToBeTaken,
                qtaCnfToBeTaken,
                totalQtaAvailable,
                totalNumCnfAvailable,
                qtaCnfAvailable,
                partitaMag,
                dataScad,
                canPartitaMagBeChanged,
                true,
                onComplete);
    }


    private void sendOnRowSaved() {
        if (this.mListener != null) mListener.onRowSaved();
    }

    private void sendFilterApplied(String newValue) {
        if (this.mListener != null) mListener.onFilterApplied(newValue);
    }

    private void sendFilterRemoved() {
        if (this.mListener != null) mListener.onFilterRemoved();
    }

    private void sendMtbColrDeleteRequest(RunnableArgs<Boolean> onComplete) {
        if (this.mListener != null) mListener.onMtbColrDeleteRequest(onComplete);
    }

    public AccettazionePickingViewModel setListeners(AccettazionePickingViewModel.Listener
                                                             listener) {
        this.mListener = listener;
        return this;
    }

    public interface Listener extends ILoadingListener, ILUPrintListener, ILUBaseOperationsListener {

        void onNoteAggiuntiveRequest(RunnableArgs<String> onComplete);

        void onError(Exception ex);

        void onItemDispatched(PickingObjectDTO pickingObjectDTO,
                              MtbAart mtbAart,
                              BigDecimal initialNumCnf,
                              BigDecimal initialQtaCnf,
                              BigDecimal initialQtaTot,
                              BigDecimal totalQtaOrd,
                              BigDecimal totalNumCnfOrd,
                              BigDecimal qtaCnfOrd,
                              BigDecimal totalQtaToBeTaken,
                              BigDecimal totalNumCnfToBeTaken,
                              BigDecimal qtaCnfToBeTaken,
                              BigDecimal totalQtaAvailable,
                              BigDecimal totalNumCnfAvailable,
                              BigDecimal qtaCnfAvailable,
                              String partitaMag,
                              Date dataScad,
                              boolean canPartitaMagBeChanged,
                              boolean canOverflowQuantity,
                              RunnableArgss<PickedQuantityDTO, Boolean> onComplete);

        void onFilterApplied(String newValue);

        void onFilterRemoved();

    }

}
