package it.integry.integrywmsnative.gest.versamento_merce;

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

import com.annimon.stream.Stream;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.inject.Inject;

import it.integry.barcode_base_android_library.model.BarcodeScanDTO;
import it.integry.integrywmsnative.core.exception.InvalidCodMdepException;
import it.integry.integrywmsnative.core.exception.InvalidLUGestioneException;
import it.integry.integrywmsnative.core.exception.NoArtsInLUException;
import it.integry.integrywmsnative.core.exception.NoLUFoundException;
import it.integry.integrywmsnative.core.exception.ScannedPositionNotExistException;
import it.integry.integrywmsnative.core.exception.TooManyLUFoundInMonoLUPositionException;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
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.MtbDepoPosizione;
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.MagazzinoAutomaticoRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.PosizioniRESTConsumer;
import it.integry.integrywmsnative.core.rest.model.magazzino_automatico.MagazzinoAutomaticoPutItemsRequestDTO;
import it.integry.integrywmsnative.core.settings.SettingsManager;
import it.integry.integrywmsnative.core.utility.UtilityBarcode;
import it.integry.integrywmsnative.core.utility.UtilityDate;
import it.integry.integrywmsnative.core.utility.UtilityPosizione;
import it.integry.integrywmsnative.core.utility.UtilityString;
import it.integry.integrywmsnative.gest.spedizione.model.PickedQuantityDTO;

public class VersamentoMerceViewModel {

    private final MutableLiveData<MtbColt> mCurrentMtbColt = new MutableLiveData<>();

    private Listener mListener;

    private final ColliMagazzinoRESTConsumer mColliMagazzinoRESTConsumer;
    private final BarcodeRESTConsumer mBarcodeRESTConsumer;
    private final PosizioniRESTConsumer mPosizioniRESTConsumer;
    private final MagazzinoAutomaticoRESTConsumer mMagazzinoAutomaticoRESTConsumer;

    @Inject
    public VersamentoMerceViewModel(ColliMagazzinoRESTConsumer colliMagazzinoRESTConsumer, BarcodeRESTConsumer barcodeRESTConsumer, PosizioniRESTConsumer posizioniRESTConsumer, MagazzinoAutomaticoRESTConsumer mMagazzinoAutomaticoRESTConsumer) {
        this.mColliMagazzinoRESTConsumer = colliMagazzinoRESTConsumer;
        this.mBarcodeRESTConsumer = barcodeRESTConsumer;
        this.mPosizioniRESTConsumer = posizioniRESTConsumer;
        this.mMagazzinoAutomaticoRESTConsumer = mMagazzinoAutomaticoRESTConsumer;
    }


    public void processBarcodeDTO(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        if (UtilityBarcode.isEtichettaPosizione(barcodeScanDTO, false)) {
            this.executeEtichettaPosizione(barcodeScanDTO, onComplete);
        } else if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
            this.executeEtichettaLU(barcodeScanDTO.getStringValue(), true, onComplete);
        } else if (UtilityBarcode.isEtichetta128(barcodeScanDTO)) {
            this.executeEtichettaEan128(barcodeScanDTO, onComplete);
        } else {
            onComplete.run();
        }
    }


    private void executeEtichettaPosizione(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        MtbDepoPosizione foundPosizione = Stream.of(SettingsManager.iDB().getAvailablePosizioni())
                .filter(x -> x.getPosizione().equalsIgnoreCase(barcodeScanDTO.getStringValue()))
                .single();


        if (foundPosizione == null) {
            //Nessuna posizione trovata con questo barcode
            this.sendError(new ScannedPositionNotExistException());
            return;
        }

        if (foundPosizione.isMagazzinoAutomatico()) {
            //La posizione è di un magazzino automatico
            this.executePosizioneMagazzinoAutomatico(foundPosizione, onComplete);

        } else if (foundPosizione.isFlagMonoCollo()) {
            this.executePosizioneMonocollo(onComplete, foundPosizione);
        } else {

            if (!UtilityString.equalsIgnoreCase(mCurrentMtbColt.getValue().getCodMdep(), foundPosizione.getCodMdep())) {
                this.sendOnSpostamentoTraDepConfirmRequired(mCurrentMtbColt.getValue().getCodMdep(), foundPosizione.getCodMdep(), canContinue -> {
                    onComplete.run();
                    if (canContinue) {
                        if (UtilityPosizione.isPosizioneWithLivello(foundPosizione)) {
                            askLivelloPosizione(foundPosizione);
                        } else {
                            updatePosizione(foundPosizione);
                        }
                    }
                });
            } else {
                onComplete.run();
                if (UtilityPosizione.isPosizioneWithLivello(foundPosizione)) {
                    askLivelloPosizione(foundPosizione);
                } else {
                    updatePosizione(foundPosizione);
                }
            }

        }
    }


    private void executePosizioneMagazzinoAutomatico(MtbDepoPosizione mtbDepoPosizione, Runnable onComplete) {
        var magazzinoAutomaticoPickRequest = new MagazzinoAutomaticoPutItemsRequestDTO()
                .setInputMtbColt(this.mCurrentMtbColt.getValue());

        mMagazzinoAutomaticoRESTConsumer.putItems(mtbDepoPosizione,
                magazzinoAutomaticoPickRequest, () -> {
                    onComplete.run();
                    this.sendOnDataSaved();
                }, this::sendError);
    }

    private void executePosizioneMonocollo(Runnable onComplete, MtbDepoPosizione foundPosizione) {
        this.mPosizioniRESTConsumer.getBancaliInPosizione(foundPosizione, mtbColtList -> {

            if (mtbColtList == null || mtbColtList.size() == 0) {
                this.sendError(new NoLUFoundException());
            } else if (mtbColtList.size() == 1) {
                mColliMagazzinoRESTConsumer.getByTestata(mtbColtList.get(0), true, false, mtbColt -> {

                    //TAKE HERE
                    boolean codMdepIsValid = Stream.of(SettingsManager.iDB().getAvailableCodMdep())
                            .anyMatch(x -> x.getCodMdep().equalsIgnoreCase(mtbColt.getCodMdep()));

                    if (codMdepIsValid) {
                        pickMerceULtoUL(mtbColt, onComplete);
                    } else this.sendError(new InvalidCodMdepException());

                }, this::sendError);
            } else {
                this.sendError(new TooManyLUFoundInMonoLUPositionException());
            }
        }, this::sendError);
    }


    private void executeEtichettaEan128(BarcodeScanDTO barcodeScanDTO, Runnable onBarcodeScanComplete) {
        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, false, onBarcodeScanComplete);
                } else {
                    this.sendError(new NoLUFoundException());
                }


            } else {
                //EAN 128 non completo o comunque mancano i riferimenti al prodotto
                onBarcodeScanComplete.run();
            }
        }, this::sendError);
    }


    private void executeEtichettaLU(String sscc, boolean isAnonima, Runnable onComplete) {
        this.mColliMagazzinoRESTConsumer.getBySSCC(sscc, true, false, mtbColt -> {

            if (mtbColt == null && !isAnonima) {
                this.sendError(new NoLUFoundException());
            } else if (mtbColt != null && (/*mtbColt.getGestioneEnum() == GestioneEnum.ACQUISTO ||*/ mtbColt.getGestioneEnum() == GestioneEnum.VENDITA)) {
                this.sendError(new InvalidLUGestioneException(GestioneEnum.VENDITA));
            } else {
                if (mtbColt == null) {
                    this.mColliMagazzinoRESTConsumer.createColloFromEtichettaAnonima(sscc, GestioneEnum.LAVORAZIONE, mtbColtAnonimo -> {
                        onComplete.run();

                        boolean codMdepIsValid = Stream.of(SettingsManager.iDB().getAvailableCodMdep())
                                .anyMatch(x -> x.getCodMdep().equalsIgnoreCase(mtbColt.getCodMdep()));

                        if (codMdepIsValid) {
                            pickMerceULtoUL(mtbColtAnonimo, onComplete);
                        } else this.sendError(new InvalidCodMdepException());

                    }, this::sendError);

                } else {
                    boolean codMdepIsValid = Stream.of(SettingsManager.iDB().getAvailableCodMdep())
                            .anyMatch(x -> x.getCodMdep().equalsIgnoreCase(mtbColt.getCodMdep()));

                    if (codMdepIsValid) {
                        pickMerceULtoUL(mtbColt, onComplete);
                    } else this.sendError(new InvalidCodMdepException());
                }
            }


        }, this::sendError);
    }


    private void pickMerceULtoUL(MtbColt destMtbColt, Runnable onComplete) {
        this.sendOnLoadingStarted();
        MtbColt sourceMtbColt = mCurrentMtbColt.getValue();

        if (!UtilityString.equalsIgnoreCase(sourceMtbColt.getCodMdep(), destMtbColt.getCodMdep())) {
            this.sendError(new Exception("Impossibile spostare la merce tra UL di due depositi differenti"));
            return;
        }

        List<MtbColr> mtbColrsToPick = Stream.of(sourceMtbColt.getMtbColr())
                .filter(x -> x.getQtaCol().floatValue() > 0)
                .toList();

        if (mtbColrsToPick.size() == 0) {
            this.sendError(new NoArtsInLUException());
            return;
        }

        this.sendOnLoadingEnded();
        this.sendOnArtsChooseRequired(mtbColrsToPick, pickedAarts -> {
            List<MtbColr> destNewMtbColr = new ArrayList<>();

            askQuantities(pickedAarts.iterator(), destNewMtbColr, () -> {
                this.sendOnLoadingStarted();

                MtbColt clonedSourceTestata = (MtbColt) sourceMtbColt.clone();
                clonedSourceTestata.setMtbColr(new ObservableArrayList<>());
                clonedSourceTestata.getMtbColr().addAll(destNewMtbColr);

                this.mColliMagazzinoRESTConsumer.spostaArtsTraUL(
                        clonedSourceTestata,
                        destMtbColt,
                        () -> {
                            this.sendOnDataSaved();
                            onComplete.run();
                        },
                        this::sendError
                );

            }, this::sendOnLoadingEnded);

        }, this::sendOnLoadingEnded);
    }


    private void askQuantities(Iterator<MtbColr> sourceMtbColrs, List<MtbColr> destMtbColr, Runnable onComplete, Runnable onAbort) {

        if (sourceMtbColrs.hasNext()) {
            askSingleQuantity(sourceMtbColrs.next(), mtbColr -> {
                destMtbColr.add(mtbColr);
                askQuantities(sourceMtbColrs, destMtbColr, onComplete, onAbort);
            }, onAbort);
        } else {
            onComplete.run();
        }
    }

    private void askSingleQuantity(MtbColr mtbColr, RunnableArgs<MtbColr> onComplete, Runnable onAbort) {
        this.sendOnItemDispatched(mtbColr.getMtbAart(),
                mtbColr.getNumCnf(),
                mtbColr.getQtaCnf(),
                mtbColr.getQtaCol(),
                mtbColr.getQtaCol(),
                mtbColr.getNumCnf(),
                mtbColr.getQtaCnf(),
                mtbColr.getPartitaMag(),
                mtbColr.getDataScadPartita(),
                false,
                false,
                pickedQuantity -> {
                    mtbColr
                            .setQtaCol(pickedQuantity.getQtaTot())
                            .setQtaCnf(pickedQuantity.getQtaCnf())
                            .setNumCnf(pickedQuantity.getNumCnf())
                            .setDatetimeRow(UtilityDate.getDateInstance());

                    onComplete.run(mtbColr);

                });
    }


    public void askLivelloPosizione(MtbDepoPosizione mtbDepoPosizione) {
        this.sendOnLivelloPosizioneRequired(mtbDepoPosizione, newPosizione -> {
            if (newPosizione == null) {
                askLivelloPosizione(mtbDepoPosizione);
            } else {
                updatePosizione(newPosizione);
            }

        });
    }

    public void updatePosizione(MtbDepoPosizione mtbDepoPosizione) {
        this.sendOnLoadingStarted();
        mColliMagazzinoRESTConsumer.changePosizione(mCurrentMtbColt.getValue(), mtbDepoPosizione, () -> {
            this.sendOnLoadingEnded();
            this.sendOnDataSaved();
        }, this::sendError);
    }


    public MutableLiveData<MtbColt> getCurrentMtbColt() {
        return mCurrentMtbColt;
    }

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

    private void sendOnDataSaved() {
        if (this.mListener != null) mListener.onDataSaved();
    }

    private void sendOnLivelloPosizioneRequired(MtbDepoPosizione posizione, RunnableArgs<MtbDepoPosizione> onComplete) {
        if (this.mListener != null)
            mListener.onLivelloPosizioneRequired(posizione, onComplete);
    }

    private void sendOnArtsChooseRequired(List<MtbColr> mtbColrList, RunnableArgs<List<MtbColr>> onComplete, Runnable onAbort) {
        if (this.mListener != null)
            mListener.onArtsChooseRequired(mtbColrList, onComplete, onAbort);
    }

    private void sendOnSpostamentoTraDepConfirmRequired(String sourceCodMdep, String destinationCodMdep, RunnableArgs<Boolean> onComplete) {
        if (this.mListener != null)
            mListener.onSpostamentoTraDepConfirmRequired(sourceCodMdep, destinationCodMdep, onComplete);
    }

    private void sendOnItemDispatched(MtbAart mtbAart,
                                      BigDecimal initialNumCnf,
                                      BigDecimal initialQtaCnf,
                                      BigDecimal initialQtaTot,
                                      BigDecimal totalQtaAvailable,
                                      BigDecimal totalNumCnfAvailable,
                                      BigDecimal qtaCnfAvailable,
                                      String partitaMag,
                                      LocalDate dataScad,
                                      boolean canOverflowOrderQuantity,
                                      boolean canBatchLotBeChanged,
                                      RunnableArgs<PickedQuantityDTO> onComplete) {
        if (this.mListener != null) mListener.onItemDispatched(
                mtbAart,
                initialNumCnf,
                initialQtaCnf,
                initialQtaTot,
                totalQtaAvailable,
                totalNumCnfAvailable,
                qtaCnfAvailable,
                partitaMag,
                dataScad,
                canOverflowOrderQuantity,
                canBatchLotBeChanged,
                onComplete);
    }

    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);
    }

    public interface Listener extends ILoadingListener {

        void onLivelloPosizioneRequired(MtbDepoPosizione posizione, RunnableArgs<MtbDepoPosizione> onComplete);

        void onSpostamentoTraDepConfirmRequired(String sourceCodMdep, String destinationCodMdep, RunnableArgs<Boolean> onComplete);

        void onArtsChooseRequired(List<MtbColr> mtbColrList, RunnableArgs<List<MtbColr>> onComplete, Runnable onAbort);

        void onItemDispatched(MtbAart mtbAart,
                              BigDecimal initialNumCnf,
                              BigDecimal initialQtaCnf,
                              BigDecimal initialQtaTot,
                              BigDecimal totalQtaAvailable,
                              BigDecimal totalNumCnfAvailable,
                              BigDecimal qtaCnfAvailable,
                              String partitaMag,
                              LocalDate dataScad,
                              boolean canOverflowOrderQuantity,
                              boolean canBatchLotBeChanged,
                              RunnableArgs<PickedQuantityDTO> onComplete);

        void onDataSaved();

        void onError(Exception ex);

    }
}
