package it.integry.integrywmsnative.gest.accettazione_bolla_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.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

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.LUScanNotGrantedException;
import it.integry.integrywmsnative.core.exception.NoArtsFoundException;
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.MtbAart;
import it.integry.integrywmsnative.core.model.MtbColr;
import it.integry.integrywmsnative.core.model.MtbColt;
import it.integry.integrywmsnative.core.model.MtbTCol;
import it.integry.integrywmsnative.core.model.dto.AlreadyRegisteredUDCDTO;
import it.integry.integrywmsnative.core.model.dto.PickDataDTO;
import it.integry.integrywmsnative.core.model.secondary.GestioneEnum;
import it.integry.integrywmsnative.core.rest.consumers.ArticoloRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.BarcodeRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliAccettazioneRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliLavorazioneRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliMagazzinoRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.PrinterRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.interfaces.ColliCaricoRESTConsumerInterface;
import it.integry.integrywmsnative.core.rest.model.Ean13PesoModel;
import it.integry.integrywmsnative.core.rest.model.VersamentoAutomaticoULResponseDTO;
import it.integry.integrywmsnative.core.rest.model.udc.CloseUDCRequestDTO;
import it.integry.integrywmsnative.core.rest.model.udc.CreateUDCRequestDTO;
import it.integry.integrywmsnative.core.rest.model.udc.DeleteUDCRowRequestDTO;
import it.integry.integrywmsnative.core.rest.model.udc.EditUDCRowRequestDTO;
import it.integry.integrywmsnative.core.rest.model.uds.DeleteULRequestDTO;
import it.integry.integrywmsnative.core.rest.model.uds.InsertUDCRowRequestDTO;
import it.integry.integrywmsnative.core.rest.model.uds.PrintULRequestDTO;
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_bolla_elenco.rest.dto.SitBollaAccettazioneDTO;
import it.integry.integrywmsnative.gest.accettazione_bolla_elenco.rest.dto.TestataBollaAccettazioneDTO;
import it.integry.integrywmsnative.gest.accettazione_bolla_picking.dto.PickingObjectDTO;
import it.integry.integrywmsnative.gest.accettazione_bolla_picking.rest.AccettazioneBollaPickingRESTConsumer;
import it.integry.integrywmsnative.gest.accettazione_ordini_picking.dto.HistoryMtbAartDTO;
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 AccettazioneBollaPickingViewModel {


    private final ArticoloRESTConsumer mArticoloRESTConsumer;
    private final BarcodeRESTConsumer mBarcodeRESTConsumer;
    private final ColliMagazzinoRESTConsumer mColliMagazzinoRESTConsumer;
    private final PrinterRESTConsumer mPrinterRESTConsumer;
    private final AccettazioneBollaPickingRESTConsumer mAccettazioneBollaPickingRESTConsumer;
    private final ColliAccettazioneRESTConsumer mColliAccettazioneRESTConsumer;
    private final ColliLavorazioneRESTConsumer mColliLavorazioneRESTConsumer;


    private ColliCaricoRESTConsumerInterface mColliCaricoRESTConsumer;


    private Listener mListener;

    private List<TestataBollaAccettazioneDTO> mBolle;
    private boolean mUseQtaBolla;
    private final MutableLiveData<List<PickingObjectDTO>> mPickingList = new MutableLiveData<>();

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

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

    @Inject
    public AccettazioneBollaPickingViewModel(ArticoloRESTConsumer articoloRESTConsumer,
                                             BarcodeRESTConsumer barcodeRESTConsumer,
                                             ColliMagazzinoRESTConsumer colliMagazzinoRESTConsumer,
                                             PrinterRESTConsumer printerRESTConsumer,
                                             AccettazioneBollaPickingRESTConsumer accettazioneBollaPickingRESTConsumer,
                                             ColliAccettazioneRESTConsumer colliAccettazioneRESTConsumer,
                                             ColliLavorazioneRESTConsumer colliLavorazioneRESTConsumer) {
        this.mArticoloRESTConsumer = articoloRESTConsumer;
        this.mBarcodeRESTConsumer = barcodeRESTConsumer;
        this.mColliMagazzinoRESTConsumer = colliMagazzinoRESTConsumer;
        this.mPrinterRESTConsumer = printerRESTConsumer;
        this.mAccettazioneBollaPickingRESTConsumer = accettazioneBollaPickingRESTConsumer;
        this.mColliAccettazioneRESTConsumer = colliAccettazioneRESTConsumer;
        this.mColliLavorazioneRESTConsumer = colliLavorazioneRESTConsumer;
    }

    public void init(List<TestataBollaAccettazioneDTO> bolle, List<SitBollaAccettazioneDTO> sitArts, boolean useQtaOrd) {
        this.mBolle = bolle;
        this.mUseQtaBolla = useQtaOrd;

        List<SitBollaAccettazioneDTO> mSitArts = Stream.of(sitArts)
                .filter(x ->
                        UtilityBigDecimal.greaterThan(x.getQtaDaAccettare(), BigDecimal.ZERO))
                .toList();

        getEmptyPickingList(mSitArts, this.mPickingList::postValue);


        defaultGestioneOfUL = GestioneEnum.ACQUISTO;

        var availableCodAnags = bolle.stream()
                .map(TestataBollaAccettazioneDTO::getCodAnag)
                .distinct()
                .collect(Collectors.toList());
        defaultCodAnag = availableCodAnags.size() == 1 ? availableCodAnags.get(0) : null;

        switch (defaultGestioneOfUL) {
            case ACQUISTO -> this.mColliCaricoRESTConsumer = mColliAccettazioneRESTConsumer;
            case LAVORAZIONE -> this.mColliCaricoRESTConsumer = mColliLavorazioneRESTConsumer;
        }
    }

    private void getEmptyPickingList(List<SitBollaAccettazioneDTO> sitBolleList, RunnableArgs<List<PickingObjectDTO>> onComplete) {

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

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

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

                        return new PickingObjectDTO()
                                .setSitBollaAccettazione(sitBolla)
                                .setMtbAart(mtbAart);
                    })
                    .toList();

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

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


    public void retrieveExistentLU(RunnableArgs<List<AlreadyRegisteredUDCDTO>> onComplete) {
        this.sendOnLoadingStarted();

        this.mAccettazioneBollaPickingRESTConsumer.retrieveAlreadyRegisteredUDC(this.mBolle, 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 {
                    int numCollo = -1;

                    try {
                        numCollo = UtilityBarcode.getNumColloFromULAnonima(barcodeScanDTO.getStringValue());
                        this.createNewLU(
                                numCollo,
                                CommonConst.Config.DEFAULT_ANONYMOUS_UL_SERIE, true, onComplete);
                    } catch (Exception ex) {
                        this.sendError(ex);
                    }
                }
            } 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.isEtichettaPosizione(barcodeScanDTO)) {
            //Applico filtro per posizione
            this.executeEtichettaPosizione(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 {

                    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 executeEtichettaPosizione(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {

        var optionalPos = Stream.of(SettingsManager.iDB().getAvailablePosizioni())
                .filter(x -> x.getPosizione().equalsIgnoreCase(barcodeScanDTO.getStringValue()))
                .findFirst();

        this.sendFilterPosizioneApplied(optionalPos.get().getPosizione());
        onComplete.run();
    }

    private void loadArticolo(String barcodeProd, PickDataDTO pickData, Runnable onComplete) {
        this.mArticoloRESTConsumer.searchByBarcode(barcodeProd, mtbAartList -> {

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

        }, 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.getSitBollaAccettazione().getCodMart()))
                .toList();

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

        this.loadMatchedRows(matchPickingObject, mtbAart, pickData);
        onComplete.run();
    }

    private void loadMatchedRows(List<PickingObjectDTO> matchedRows, MtbAart mtbAart, PickDataDTO pickDataDTO) {
        if (matchedRows == null || matchedRows.size() == 0) {
            manageNoArtFound(mtbAart, pickDataDTO);
        } else if (matchedRows.size() == 1) {
            this.manageDispatchBollaRow(matchedRows.get(0));
        } else {
            //So che il codMart sarà solo 1
            var firstCodMart = Stream.of(matchedRows)
                    .map(x -> x.getSitBollaAccettazione().getCodMart())
                    .distinct()
                    .findFirst()
                    .get();

            this.sendFilterCodMartApplied(firstCodMart);
        }
    }


    private void manageUnknownBarcode(String barcode, Runnable onComplete) {
        if (SettingsManager.iDB().isFlagCanAddUnknownBarcodesAccettazioneBolla()) {
            //TODO: Implementare popup che richiede delle note aggiuntive sul barcode scansionato
            this.sendOnUnknownBarcodeScanned(barcode, notes -> {
                this.manageDispatchBollaRow(new PickingObjectDTO()
                        .setMtbAart(new MtbAart()
                                .setDescrizione(notes)
                                .setDescrizioneEstesa(notes)
                                .setFlagTracciabilita("N")
                                .setFlagQtaCnfFissa("S")
                                .setQtaCnf(BigDecimal.ONE)
                                .setBarCode(barcode)));
                onComplete.run();
            }, onComplete);
        } else {
            this.sendError(new NoResultFromBarcodeException(barcode));
        }
    }


    private void manageNoArtFound(MtbAart mtbAart, PickDataDTO pickDataDTO) {
        if (SettingsManager.iDB().isFlagCanAddUnknownItemsAccettazioneBolla()) {

            this.manageDispatchBollaRow(new PickingObjectDTO()
                    .setMtbAart(mtbAart)
                    .setTempPickData(pickDataDTO));
        } else
            this.sendError(new NoArtsFoundException());
    }

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

            final CreateUDCRequestDTO createUDCRequestDTO = new CreateUDCRequestDTO()
                    .setCodMdep(SettingsManager.i().getUserSession().getDepo().getCodMdep())
                    .setAnnotazioni(additionalNotes)
                    .setCodTcol(tCol != null ? tCol.getCodTcol() : null)
                    .setNumCollo(customNumCollo)
                    .setSerCollo(customSerCollo)
                    .setCodAnag(defaultCodAnag);

            this.mColliCaricoRESTConsumer.createUDC(createUDCRequestDTO, createdUDS -> {
                this.mCurrentMtbColt = createdUDS;

                this.mCurrentMtbColt.setMtbColr(new ObservableArrayList<>())
                        .setDisablePrint(disablePrint);

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

        });
    }

    private void manageInfoAggiuntiveRequest(RunnableArgss<String, MtbTCol> onComplete) {
        if (!SettingsManager.iDB().isFlagAskTipoColloAccettazioneBolla()) {
            onComplete.run(null, null);
            return;
        }

        this.sendOnInfoAggiuntiveRequest(onComplete);
    }

    public void manageDispatchBollaRow(final PickingObjectDTO pickingObjectDTO) {
        String partitaMag = null;
        if(pickingObjectDTO.getTempPickData() != null){
            if(pickingObjectDTO.getTempPickData().getManualPickDTO() != null){
                if(pickingObjectDTO.getTempPickData().getManualPickDTO().getMtbPartitaMag() != null){
                    partitaMag = pickingObjectDTO.getTempPickData().getManualPickDTO().getMtbPartitaMag().getPartitaMag();
                }
            }
        }

        if (SettingsManager.iDB().isFlagEnableFastPickAccettazioneBolla()) {
            this.saveNewRow(pickingObjectDTO,
                    BigDecimal.ONE,
                    pickingObjectDTO.getMtbAart().getQtaCnf(),
                    pickingObjectDTO.getMtbAart().getQtaCnf(),
                    partitaMag,
                    null,
                    false);

            return;
        }

        dispatchBollaRow(pickingObjectDTO);
    }

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

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

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

        BigDecimal totalQtaDoc = null;
        BigDecimal totalNumCnfDoc = null;
        BigDecimal qtaCnfDoc = null;

        BigDecimal numCnfDaEvadere = null;
        BigDecimal qtaDaEvadere = null;
        BigDecimal qtaCnfDaEvadere = null;

        final MtbAart mtbAart = pickingObjectDTO.getMtbAart();
        final SitBollaAccettazioneDTO sitBollaAccettazione = pickingObjectDTO.getSitBollaAccettazione();

        if (sitBollaAccettazione != null) {

            totalQtaDoc = sitBollaAccettazione.getQtaDaAccettare();
            totalNumCnfDoc = sitBollaAccettazione.getNumCnfDaAccettare();
            qtaCnfDoc = sitBollaAccettazione.getQtaCnfDoc();

            numCnfDaEvadere = sitBollaAccettazione.getNumCnfDaAccettare().subtract(numCnfWithdrawRows.getBigDecimalValue());
            qtaDaEvadere = sitBollaAccettazione.getQtaDaAccettare().subtract(qtaColWithdrawRows.getBigDecimalValue());
            qtaCnfDaEvadere = qtaCnfDoc;

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

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

        String partitaMag = null;
        LocalDate dataScad = null;

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


            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().getDataScad();
            }
        }

        BigDecimal initialNumCnf;
        BigDecimal initialQtaCnf;
        BigDecimal initialQtaTot;

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

        } else if (totalQtaDoc != null) {
            initialNumCnf = totalNumCnfDoc;
            initialQtaCnf = qtaCnfDoc;
            initialQtaTot = totalQtaDoc;
        } else {
            initialNumCnf = BigDecimal.ONE;
            initialQtaCnf = mtbAart.getQtaCnf();
            initialQtaTot = mtbAart.getQtaCnf();
        }

        if (!this.mUseQtaBolla &&
                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.getNow();
            dataScad.plusDays(pickingObjectDTO.getMtbAart().getGgScadPartita());
        }

        if (partitaMag == null && dataScad == null) {
            HistoryMtbAartDTO historyMtbAartDTO =
                    pickingObjectDTO.getMtbAart().getCodMart() != null ?
                            this.getHistoryItemByCodMartIfExists(pickingObjectDTO.getMtbAart().getCodMart()) :
                            this.getHistoryItemByBarcodeIfExists(pickingObjectDTO.getMtbAart().getBarCode());

            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, LocalDate dataScad, boolean shouldCloseLU) {
        if (UtilityBigDecimal.equalsTo(numCnf, BigDecimal.ZERO) && UtilityBigDecimal.equalsTo(qtaTot, BigDecimal.ZERO)) {
            resetMatchedRows();

            if (shouldCloseLU) closeLU();
            return;
        }

        this.sendOnLoadingStarted();


        var clonedTargetMtbColt = (MtbColt) mCurrentMtbColt.clone();
        clonedTargetMtbColt.setMtbColr(null);

        var sitBolla = pickingObjectDTO.getSitBollaAccettazione();
        var firstAvailableBolla = mBolle.get(0);

        var insertUDCRowRequestDto = new InsertUDCRowRequestDTO()
                .setCodMart(pickingObjectDTO.getMtbAart().getCodMart())
                .setDescrizione(pickingObjectDTO.getMtbAart().getCodMart() == null ? pickingObjectDTO.getMtbAart().getDescrizione() : null)
                .setBarcode(pickingObjectDTO.getMtbAart().getBarCode())
                .setQtaTot(qtaTot)
                .setQtaCnf(qtaCnf)
                .setNumCnf(numCnf)
                .setPartitaMag(partitaMag)
//                .setCodJcom(pickingObjectDTO.getSitBollaAccettazione().getCodJcom())
                .setDataDoc(sitBolla != null ? sitBolla.getDataDoc() : firstAvailableBolla.getDataDoc())
                .setNumDoc(sitBolla != null ? sitBolla.getNumDoc() : firstAvailableBolla.getNumDoc())
                .setCodDtip(sitBolla != null ? sitBolla.getCodDtip() : firstAvailableBolla.getCodDtip())
                .setSerDoc(sitBolla != null ? sitBolla.getSerDoc() : firstAvailableBolla.getSerDoc())
                .setTargetMtbColt(clonedTargetMtbColt);

        if (dataScad != null)
            insertUDCRowRequestDto
                    .setDataScad(dataScad);

        mColliCaricoRESTConsumer.insertUDCRow(insertUDCRowRequestDto, insertedMtbColr -> {
            this.addHistoryItem(insertedMtbColr);

            insertedMtbColr
                    .setUntMis(pickingObjectDTO.getMtbAart().getUntMis())
                    .setMtbAart(pickingObjectDTO.getMtbAart());

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

            //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.getDataScadPartita(),
                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, LocalDate dataScad, boolean shouldCloseLU) {
        this.sendOnLoadingStarted();

        MtbColr mtbColrClone = (MtbColr) mtbColrToUpdate.clone();
        mtbColrClone.setMtbAart(null)
                .setMtbPartitaMag(null);

        var editUDCRowRequest = new EditUDCRowRequestDTO()
                .setSourceMtbColr(mtbColrClone)
                .setNewNumCnf(numCnf)
                .setNewQtaCnf(qtaCnf)
                .setNewQtaTot(qtaTot)
                .setNewPartitaMag(partitaMag);

        if (dataScad != null)
            editUDCRowRequest
                    .setNewDataScad(dataScad);


        this.mColliCaricoRESTConsumer.editUDCRow(editUDCRowRequest, (editedMtbColr) -> {

            this.addHistoryItem(editedMtbColr);

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

            editedMtbColr
                    .setUntMis(mtbColrToUpdate.getUntMis())
                    .setMtbAart(mtbColrToUpdate.getMtbAart());

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

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

            //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)
                return;

            this.sendOnLoadingStarted();

            var deleteUDCRowRequest = new DeleteUDCRowRequestDTO()
                    .setMtbColrToDelete(mtbColrToDelete);

            this.mColliCaricoRESTConsumer.deleteUDCRow(deleteUDCRowRequest,
                    () -> {
                        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.canULBeDeleted(mCurrentMtbColt, canBeDeleted -> {
            if (canBeDeleted) {
                deleteLU(() -> {
                    this.sendLUClosed();
                    this.sendOnLoadingEnded();
                });
            } else {
                CloseUDCRequestDTO closeUDCRequestDTO = new CloseUDCRequestDTO()
                        .setMtbColt(mCurrentMtbColt);
                mColliCaricoRESTConsumer.closeUDC(closeUDCRequestDTO, closeUDCResponse -> {

                    Runnable onVersamentoCompleted = () -> {
                        managePrintCollo(this::postCloseOperations);

                        this.sendLUClosed();
                        this.sendOnLoadingEnded();
                    };

                    manageVersamentoAutomatico(onVersamentoCompleted);
                }, this::sendError);
            }
        }, this::sendError);
    }

    private void manageVersamentoAutomatico(Runnable onComplete) {

        if (!SettingsManager.iDB().isFlagAskVersamentoAutomaticoAccettazioneBolla()) {
            onComplete.run();
            return;
        }

        sendVersamentoAutomaticoULRequest(response -> {
            if (response) {
                versaAutomaticamenteUL(onComplete);
            } else {
                onComplete.run();
            }
        });
    }

    private void versaAutomaticamenteUL(Runnable onComplete) {
        if (mCurrentMtbColt == null) return;

        mColliMagazzinoRESTConsumer.versamentoAutomaticoUL(mCurrentMtbColt, versamentoResult -> {
            this.notifyVersamentoAutomaticoResult(versamentoResult, onComplete);
        }, this::sendError);
    }

    private void managePrintCollo(Runnable onComplete) {
        if (!mCurrentMtbColt.getDisablePrint() || !SettingsManager.iDB().isFlagAskPrintUlAccettazioneBolla()) {
            onComplete.run();
            return;
        }

        printCollo(onComplete);
    }

    private void printCollo(Runnable onComplete) {

        MtbColt cloneMtbColt = (MtbColt) mCurrentMtbColt.clone();
        cloneMtbColt.setGestione(cloneMtbColt.getGestioneEnum() == GestioneEnum.LAVORAZIONE ? GestioneEnum.PRODUZIONE : cloneMtbColt.getGestioneEnum());

        var printUDSRequestDto = new PrintULRequestDTO()
                .setMtbColt(cloneMtbColt);

        this.mColliMagazzinoRESTConsumer.printUL(printUDSRequestDto, onComplete,
                ex -> this.sendLUPrintError(ex, onComplete));
    }

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

    private void deleteLU(Runnable onComplete) {
        DeleteULRequestDTO deleteULRequestDTO = new DeleteULRequestDTO()
                .setMtbColt(this.mCurrentMtbColt);

        mColliMagazzinoRESTConsumer.deleteUL(deleteULRequestDTO, () -> {
            this.mCurrentMtbColt = null;

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

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

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

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

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

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

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

    private HistoryMtbAartDTO getHistoryItemByBarcodeIfExists(@NonNull String barcode) {
        Optional<HistoryMtbAartDTO> optional = Stream.of(this.mHistoryUsedAarts)
                .filter(x -> x.getBarcode().equalsIgnoreCase(barcode))
                .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 sendVersamentoAutomaticoULRequest(RunnableArgs<Boolean> onComplete) {
        if (this.mListener != null) this.mListener.onVersamentoAutomaticoULRequest(onComplete);
    }

    private void sendOnInfoAggiuntiveRequest(RunnableArgss<String, MtbTCol> onComplete) {
        if (this.mListener != null) this.mListener.onInfoAggiuntiveRequest(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,
                                      LocalDate 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 sendFilterCodMartApplied(String codMart) {
        if (this.mListener != null) mListener.onFilterCodMartApplied(codMart);
    }

    private void sendFilterPosizioneApplied(String posizione) {
        if (this.mListener != null) mListener.onFilterPosizioneApplied(posizione);
    }

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

    private void notifyVersamentoAutomaticoResult(VersamentoAutomaticoULResponseDTO versamentoAutomaticoULResponseDTO, Runnable onComplete) {
        if (this.mListener != null)
            mListener.onULVersata(versamentoAutomaticoULResponseDTO, onComplete);
    }

    private void sendOnUnknownBarcodeScanned(String barocde, RunnableArgs<String> onComplete, Runnable onAbort) {
        if (this.mListener != null)
            mListener.onUnknownBarcodeScanned(barocde, onComplete, onAbort);
    }

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


    public interface Listener extends ILoadingListener, ILUPrintListener, ILUBaseOperationsListener {

        void onVersamentoAutomaticoULRequest(RunnableArgs<Boolean> onComplete);

        void onInfoAggiuntiveRequest(RunnableArgss<String, MtbTCol> 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,
                              LocalDate dataScad,
                              boolean canPartitaMagBeChanged,
                              boolean canOverflowQuantity,
                              RunnableArgss<PickedQuantityDTO, Boolean> onComplete);

        void onFilterCodMartApplied(String codMartToFilter);

        void onFilterPosizioneApplied(String posizioneToFilter);

        void onULVersata(VersamentoAutomaticoULResponseDTO versamentoAutomaticoULResponseDTO, Runnable onComplete);

        void onUnknownBarcodeScanned(String barcode, RunnableArgs<String> done, Runnable abort);

    }

}