package it.integry.integrywmsnative.gest.prod_versamento_materiale;

import androidx.lifecycle.MutableLiveData;

import com.annimon.stream.Stream;

import org.jetbrains.annotations.NotNull;

import java.math.BigDecimal;
import java.time.LocalDate;
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.NoLUFoundException;
import it.integry.integrywmsnative.core.exception.NoResultFromBarcodeException;
import it.integry.integrywmsnative.core.exception.TooManyLUFoundInMonoLUPositionException;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.expansion.RunnableArgss;
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.dto.PickDataDTO;
import it.integry.integrywmsnative.core.rest.consumers.ArticoloRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.BarcodeRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliLavorazioneRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.ColliMagazzinoRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.MesRESTConsumer;
import it.integry.integrywmsnative.core.rest.consumers.PosizioniRESTConsumer;
import it.integry.integrywmsnative.core.rest.model.Ean128Model;
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.UtilityString;
import it.integry.integrywmsnative.gest.prod_versamento_materiale.dto.OrdineLavorazioneDTO;
import it.integry.integrywmsnative.gest.spedizione.exceptions.InvalidPesoKGException;
import it.integry.integrywmsnative.gest.spedizione.model.PickedQuantityDTO;
import it.integry.integrywmsnative.gest.spedizione.model.PickingObjectDTO;

public class ProdVersamentoMaterialeViewModel {

    private final PosizioniRESTConsumer mPosizioniRESTConsumer;
    private final ColliMagazzinoRESTConsumer mColliMagazzinoRESTConsumer;
    private final ColliLavorazioneRESTConsumer mColliLavorazioneRESTConsumer;
    private final MesRESTConsumer mMesRESTConsumer;
    private final BarcodeRESTConsumer mBarcodeRESTConsumer;
    private final ArticoloRESTConsumer mArticoloRESTConsumer;

    private boolean mFlagVersamentoDirettoProduzione;

    private final MutableLiveData<MtbColt> mtbColtMutableLiveData = new MutableLiveData<>();
    private final MutableLiveData<MtbColr> mtbColrMutableLiveData = new MutableLiveData<>();
    private final MutableLiveData<List<OrdineLavorazioneDTO>> openedOrderLavMutableLiveList = new MutableLiveData<>();
    private final MutableLiveData<MtbDepoPosizione> mtbDepoPosizioneMutableLiveData = new MutableLiveData<>();

    private Listener mListener;

    @Inject
    public ProdVersamentoMaterialeViewModel(PosizioniRESTConsumer posizioniRESTConsumer,
                                            ColliMagazzinoRESTConsumer colliMagazzinoRESTConsumer,
                                            ColliLavorazioneRESTConsumer colliLavorazioneRESTConsumer,
                                            MesRESTConsumer mesRESTConsumer,
                                            BarcodeRESTConsumer barcodeRESTConsumer,
                                            ArticoloRESTConsumer articoloRESTConsumer) {
        this.mPosizioniRESTConsumer = posizioniRESTConsumer;
        this.mColliMagazzinoRESTConsumer = colliMagazzinoRESTConsumer;
        this.mColliLavorazioneRESTConsumer = colliLavorazioneRESTConsumer;
        this.mMesRESTConsumer = mesRESTConsumer;
        this.mBarcodeRESTConsumer = barcodeRESTConsumer;
        this.mArticoloRESTConsumer = articoloRESTConsumer;
    }


    public void init(boolean flagVersamentoDirettoProduzione) {
        this.mFlagVersamentoDirettoProduzione = flagVersamentoDirettoProduzione;
    }


    public void processBarcodeDTO(BarcodeScanDTO barcodeScanDTO) {
        if (UtilityBarcode.isEtichettaPosizione(barcodeScanDTO)) {
            this.executeEtichettaPosizione(barcodeScanDTO);
        } else if (UtilityBarcode.isEtichettaAnonima(barcodeScanDTO)) {
            this.executeEtichettaLU(barcodeScanDTO.getStringValue());
        } else if (UtilityBarcode.isEtichetta128(barcodeScanDTO)) {
            this.executeEtichettaEan128(barcodeScanDTO);
        } else if (UtilityBarcode.isEan13(barcodeScanDTO)) {
//            this.executeEAN13(barcodeScanDTO);
        }
    }


    private void executeEtichettaPosizione(BarcodeScanDTO barcodeScanDTO) {

        MtbDepoPosizione foundPosizione = Stream.of(SettingsManager.iDB().getAvailablePosizioni())
                .filter(x -> x.getPosizione().equalsIgnoreCase(barcodeScanDTO.getStringValue()))
                .single();
        this.executePosizione(foundPosizione);

    }

    private void executePosizione(MtbDepoPosizione posizione) {
        this.mPosizioniRESTConsumer.getBancaliInPosizione(posizione, mtbColtList -> {

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

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

                    if (codMdepIsValid) {
                        this.onLUOpened(mtbColt);
                    } else this.sendError(new InvalidCodMdepException());

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

        }, this::sendError);
    }


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

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

            if (codMdepIsValid) {
                this.onLUOpened(mtbColt);
            } else this.sendError(new InvalidCodMdepException());

        }, this::sendError);
    }


    private void executeEtichettaEan128(BarcodeScanDTO barcodeScanDTO) {
        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);
                } else if (!UtilityString.isNullOrEmpty(barcodeProd)) {
                    this.loadArticolo(barcodeProd, ean128Model);
                } else {
                    this.sendError(new NoLUFoundException());
                }


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

    private void loadArticolo(String barcodeProd, Ean128Model ean128Model) {
        mArticoloRESTConsumer.searchByBarcode(barcodeProd, mtbAartList -> {
            if (mtbAartList != null && mtbAartList.size() > 0) {
                MtbAart articolo = mtbAartList.get(0);
                this.dispatchArt(articolo, ean128Model);

            } else {
                this.sendError(new NoResultFromBarcodeException(barcodeProd));
            }

        }, this::sendError);
    }


    private void dispatchArt(MtbAart mtbAart, Ean128Model ean128Model) {
        PickingObjectDTO pickingObjectDTO = new PickingObjectDTO()
                .setMtbAart(mtbAart)
                .setTempPickData(PickDataDTO.fromEan128(ean128Model));

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

        BigDecimal initialNumCnf;
        BigDecimal initialQtaCnf;
        BigDecimal initialQtaTot;

        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) {
                if (!mtbAart.isFlagQtaCnfFissaBoolean()) {
                    numCnfDaPrelevare = UtilityBigDecimal.divideAndRoundToInteger(qtaColDaPrelevare, mtbAart.getQtaCnf());
                    if (UtilityBigDecimal.equalsTo(numCnfDaPrelevare, BigDecimal.ZERO))
                        numCnfDaPrelevare = BigDecimal.ONE;
                    qtaCnfDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, numCnfDaPrelevare);
                } else {
                    numCnfDaPrelevare = UtilityBigDecimal.divideToInteger(qtaColDaPrelevare, mtbAart.getQtaCnf());
                }

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

            initialNumCnf = numCnfDaPrelevare;
            initialQtaCnf = qtaCnfDaPrelevare;
            initialQtaTot = qtaColDaPrelevare;
        } else {
            initialNumCnf = BigDecimal.ONE;
            initialQtaCnf = mtbAart.getQtaCnf();
            initialQtaTot = UtilityBigDecimal.multiply(initialNumCnf, initialQtaCnf);
        }

        if (numCnfDaPrelevare == null && qtaColDaPrelevare == null && initialNumCnf == null && initialQtaTot == null) {
            initialNumCnf = BigDecimal.ONE;
            initialQtaCnf = mtbAart.getQtaCnf();
            initialQtaTot = UtilityBigDecimal.multiply(initialNumCnf, initialQtaCnf);
        }




        this.sendOnItemDispatched(
                pickingObjectDTO,
                mtbAart,
                initialNumCnf,
                initialQtaCnf,
                initialQtaTot,
                null,
                null,
                null,
                null,
                null,
                null,
                partitaMag,
                dataScad,
                false,
                true,
                (pickedQuantityDTO, shouldCloseLU) -> {

                    this.sendRequestChoosePosition(mtbDepoPosizione -> {

                        mColliLavorazioneRESTConsumer.createColloScaricoDaArticolo(mtbAart,
                                pickedQuantityDTO.getPartitaMag(),
                                mtbDepoPosizione,
                                pickedQuantityDTO.getQtaTot(),
                                pickedQuantityDTO.getQtaCnf(),
                                pickedQuantityDTO.getNumCnf(),
                                null, null,
                                this::sendOnDataSaved,
                                this::sendError);
                    });

                });
    }

    private void onLUOpened(MtbColt mtbColt) {
        this.mtbColtMutableLiveData.setValue(mtbColt);
        this.sendRequestChoosePosition(this::setPosizione);
    }


    public void setPosizione(MtbDepoPosizione mtbDepoPosizione) {
        this.mtbDepoPosizioneMutableLiveData.postValue(mtbDepoPosizione);
        MtbColt mtbColt = mtbColtMutableLiveData.getValue();

        this.sendOnLoadingStarted();

        if (mtbDepoPosizione != null && mtbDepoPosizione.isFlagLineaProduzione() && mtbColt != null) {
            if (!mFlagVersamentoDirettoProduzione) {
                createColloScarico(mtbDepoPosizione);
            } else {
                if (mtbColt.getMtbColr() == null || mtbColt.getMtbColr().size() <= 0) {
                    this.sendWarning("Il collo selezionato non presenta articoli versabili sulla linea.", this::sendRequestLUOpen);
                    return;
                } else if (mtbColt.getMtbColr().size() > 1) {
                    this.sendWarning("Il collo selezionato contiene più articoli, solo il primo verrà versato in questa sessione. <br> Per versare i colli restanti ripetere l'operazione di versamento.",
                            () -> {
                                cyclicGetOrdiniLavByCollo(mtbColt.getMtbColr().iterator(), mtbDepoPosizione);
                            });
                    return;

                }

                mMesRESTConsumer.getOrdiniLavorazioneMateriale(
                        mtbDepoPosizione.getPosizione(),
                        getIdMaterialeFromCollo(mtbColt),
                        ordini -> {
                            if (ordini == null || ordini.isEmpty())
                                this.sendError(new Exception("Nessun ordine compatibile con " + getIdMaterialeFromCollo(mtbColt) + " sulla linea " + mtbDepoPosizione.getPosizione()), true);
                            else {
                                setCurrentOrders(ordini, mtbColt.getMtbColr().get(0));
                                this.sendOnLoadingEnded();
                            }

                        },
                        e -> this.sendWarning(e.getMessage(), this::sendRequestLUOpen));
            }
        } else {
            this.sendWarning("Si è verificato un errore. Riprovare", this::sendRequestLUOpen);
        }
    }

    private void createColloScarico(MtbDepoPosizione mtbDepoPosizione) {

        this.sendOnLoadingStarted();

        this.mColliMagazzinoRESTConsumer.createColloScaricoDaCarico(mtbColtMutableLiveData.getValue(), mtbDepoPosizione,
                generatedMtbColt -> {
                    this.sendOnLoadingEnded();
                    this.sendOnDataSaved();
                }, this::sendError);
    }


    private String getIdMaterialeFromCollo(MtbColt mtbColt) {
        MtbAart articolo = mtbColt.getMtbColr().get(0).getMtbAart();
        return UtilityString.isNullOrEmpty(articolo.getIdArtEqui()) ? articolo.getCodMart() : articolo.getIdArtEqui();
    }

    private void setCurrentOrders(List<OrdineLavorazioneDTO> ordini, MtbColr currentMtbColr) {
        currentMtbColr.setQtaCnf(UtilityBigDecimal.divide(currentMtbColr.getQtaCol(), currentMtbColr.getNumCnf()));
        this.mtbColrMutableLiveData.setValue(currentMtbColr);
        this.openedOrderLavMutableLiveList.setValue(ordini);
    }


    private void cyclicGetOrdiniLavByCollo(@NotNull Iterator<MtbColr> mtbColrIterator, MtbDepoPosizione mtbDepoPosizione) {
        MtbColr currentMtbColr = mtbColrIterator.next();
        MtbAart currentMtbAart = currentMtbColr.getMtbAart();

        this.mMesRESTConsumer.getOrdiniLavorazioneMateriale(
                mtbDepoPosizione.getPosizione(),
                UtilityString.isNullOrEmpty(currentMtbAart.getIdArtEqui()) ? currentMtbAart.getCodMart() : currentMtbAart.getIdArtEqui(),
                ordini -> {
                    if (ordini == null || ordini.isEmpty()) {
                        if (mtbColrIterator.hasNext()) {
                            cyclicGetOrdiniLavByCollo(mtbColrIterator, mtbDepoPosizione);
                        } else {
                            setCurrentOrders(ordini, currentMtbColr);
                        }
                    } else {
                        setCurrentOrders(ordini, currentMtbColr);
                    }
                }
                , e -> this.sendWarning(e.getMessage(), this::sendRequestLUOpen));
    }


    public void save(List<OrdineLavorazioneDTO> selectedOrders) {
        this.sendOnLoadingStarted();

        this.mColliMagazzinoRESTConsumer.createColliScaricoDaOrdineLavorazione(
                this.mtbColtMutableLiveData.getValue(),
                this.mtbColrMutableLiveData.getValue(),
                this.mtbDepoPosizioneMutableLiveData.getValue(),
                selectedOrders,
                mtbColtSaved -> {
                    this.sendOnDataSaved();
                }, this::sendError);
    }


    public MutableLiveData<MtbColt> getMtbColtMutableLiveData() {
        return mtbColtMutableLiveData;
    }

    public MutableLiveData<MtbColr> getMtbColrMutableLiveData() {
        return mtbColrMutableLiveData;
    }

    public MutableLiveData<List<OrdineLavorazioneDTO>> getOpenedOrderLavMutableLiveList() {
        return openedOrderLavMutableLiveList;
    }


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

    private void sendRequestLUOpen() {
        if (this.mListener != null) mListener.requestLUOpen();
    }

    private void sendRequestChoosePosition(RunnableArgs<MtbDepoPosizione> onComplete) {
        if (this.mListener != null) mListener.onRequestChoosePosition(onComplete);
    }

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

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

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

    private void sendWarning(String warningText, Runnable action) {
        if (this.mListener != null) mListener.onWarning(warningText, action);
    }

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

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

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

    public interface Listener extends ILoadingListener {

        void requestLUOpen();

        void onRequestChoosePosition(RunnableArgs<MtbDepoPosizione> onComplete);

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

        void onError(Exception ex, boolean requestClose);

        void onWarning(String warningText, Runnable action);

        void onDataSaved();
    }

}
