package it.integry.integrywmsnative.gest.pv_ordine_acquisto_edit;

import androidx.lifecycle.MutableLiveData;

import com.annimon.stream.Stream;

import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import javax.inject.Inject;

import it.integry.integrywmsnative.R;
import it.integry.integrywmsnative.core.barcode_reader.BarcodeManager;
import it.integry.integrywmsnative.core.data_store.db.entity.ArticoloOrdine;
import it.integry.integrywmsnative.core.data_store.db.entity.Ordine;
import it.integry.integrywmsnative.core.data_store.db.repository.ArticoliOrdineRepository;
import it.integry.integrywmsnative.core.data_store.db.repository.ArticoloGrigliaRepository;
import it.integry.integrywmsnative.core.data_store.db.repository.GrigliaRepository;
import it.integry.integrywmsnative.core.data_store.db.repository.OrdineRepository;
import it.integry.integrywmsnative.core.data_store.db.view_model.ArticoloOrdinabileDTO;
import it.integry.integrywmsnative.core.exception.ArtNotFoundInGridException;
import it.integry.integrywmsnative.core.exception.EmptyOrderException;
import it.integry.integrywmsnative.core.exception.ExpiredProductListException;
import it.integry.integrywmsnative.core.exception.NoArtsInGridException;
import it.integry.integrywmsnative.core.exception.NoResultFromBarcodeException;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.interfaces.viewmodel_listeners.ILoadingListener;
import it.integry.integrywmsnative.core.rest.consumers.PVOrdiniAcquistoRESTConsumer;
import it.integry.integrywmsnative.core.settings.SettingsManager;
import it.integry.integrywmsnative.core.utility.UtilityBigDecimal;
import it.integry.integrywmsnative.core.utility.UtilityDate;
import it.integry.integrywmsnative.core.utility.UtilityString;

public class PVOrdineAcquistoEditViewModel {

    @Inject
    OrdineRepository mOrdineRepository;

    @Inject
    GrigliaRepository mGrigliaRepository;

    @Inject
    ArticoliOrdineRepository mArticoliOrdineRepository;

    @Inject
    ArticoloGrigliaRepository mArticoloGrigliaRepository;

    @Inject
    PVOrdiniAcquistoRESTConsumer pvOrdiniAcquistoRESTConsumer;

    private Ordine mCurrentOrdine;


    private Listener mListener;


    private List<ArticoloOrdinabileDTO> mArticoliGriglia = new ArrayList<>();
    private final MutableLiveData<List<ArticoloOrdine>> mArticoli = new MutableLiveData<>();

    private Calendar dataInizioModifica;


    public PVOrdineAcquistoEditViewModel(OrdineRepository ordineRepository,
                                         GrigliaRepository grigliaRepository,
                                         ArticoliOrdineRepository articoliOrdineRepository,
                                         ArticoloGrigliaRepository articoloGrigliaRepository,
                                         PVOrdiniAcquistoRESTConsumer pvOrdiniAcquistoRESTConsumer) {
        this.mOrdineRepository = ordineRepository;
        this.mGrigliaRepository = grigliaRepository;
        this.mArticoliOrdineRepository = articoliOrdineRepository;
        this.mArticoloGrigliaRepository = articoloGrigliaRepository;
        this.pvOrdiniAcquistoRESTConsumer = pvOrdiniAcquistoRESTConsumer;
    }

    public void init(Ordine ordine, List<ArticoloOrdinabileDTO> articoliGriglia) {
        this.mCurrentOrdine = ordine;
        mArticoliGriglia.clear();
        mArticoliGriglia.addAll(articoliGriglia);

        Calendar today = Calendar.getInstance();
        today.set(Calendar.MILLISECOND, 0);
        today.set(Calendar.SECOND, 0);
        today.set(Calendar.MINUTE, 0);
        today.set(Calendar.HOUR_OF_DAY, 0);

        this.dataInizioModifica = today;
        this.refreshListArticoli();
    }

    public void refreshListArticoli() {
        refreshListArticoli(null);
    }

    public boolean isOrderSessionInvalid() {
        Calendar today = Calendar.getInstance();
        today.set(Calendar.MILLISECOND, 0);
        today.set(Calendar.SECOND, 0);
        today.set(Calendar.MINUTE, 0);
        today.set(Calendar.HOUR_OF_DAY, 0);

        return today.after(this.dataInizioModifica);
    }

    public void refreshListArticoli(Runnable onSuccess) {
        this.sendOnLoadingStarted();
        this.loadArticoli(mCurrentOrdine, articoli -> {
            articoli = Stream.of(articoli).map(this::completeDataArticoli).sorted((a, b) -> a.getDataIns().after(b.getDataIns()) ? -1 : 1).toList();

            this.mArticoli.postValue(articoli);
            this.sendOnLoadingEnded();
            if (onSuccess != null)
                onSuccess.run();
        });
    }

    private ArticoloOrdine completeDataArticoli(ArticoloOrdine art) {
        ArticoloOrdinabileDTO artGrid = Stream.of(mArticoliGriglia).filter(x -> art.getCodMart().equalsIgnoreCase(x.getCodMart())).findFirstOrElse(null);
        if (artGrid != null) {
            art.setGgScadenza(artGrid.getGgScadenza());
            art.setGiacenza(artGrid.getGiacenza());
            art.setDescrizione(artGrid.getDescrizione());
            art.setQtaProposta(artGrid.getQtaProposta());
            art.setQtaPrevistaVendita(artGrid.getQtaPrevistaVendita());
            art.setBgTint(getBgTint(art));
            art.setMediaSett(artGrid.getMediaSett());
            art.setQtaMinOrdinabile(artGrid.getQtaMinOrdinabile());
            art.setMerceDaRic(artGrid.getMerceDaRic());
            art.setNewNoPromo(artGrid.isNewNoPromo());
            art.setFlagQtaMultipla(artGrid.getFlagQtaMultipla());
            art.setCtMaxOrd(BigDecimal.valueOf(artGrid.getCtMaxOrd()));
            art.setSystemNote(artGrid.generateSystemNote());
        } else {
            art.setInvalid(true);
        }
        return art;
    }

    private int getBgTint(ArticoloOrdine art) {
        int chk = art.checkQtaProposta();
        switch (chk) {
            case -1:
                return this.mListener.getColorFromResource(R.color.red_600);
            case 1:
                return this.mListener.getColorFromResource(R.color.orange_600);
            default:
                return this.mListener.getColorFromResource(R.color.colorPrimary);
        }
    }

    public MutableLiveData<List<ArticoloOrdine>> getArticoli() {
        return mArticoli;
    }

    public void processBarcode(String barcode) {
        Runnable onComplete = BarcodeManager::enable;
        BarcodeManager.disable();

        try {
            List<ArticoloOrdinabileDTO> listArticoli = findArticoliByBarcode(barcode);

            if (listArticoli == null || listArticoli.isEmpty())
                onNoArtsFoundByBarcode(barcode, onComplete);
            else
                addArtFromGriglia(listArticoli, onComplete);

        } catch (Exception e) {
            this.sendError(e);
            onComplete.run();
        }
    }

    private void onNoArtsFoundByBarcode(String barcode, Runnable onComplete) throws NoResultFromBarcodeException {
        if (SettingsManager.iDB().isFlagConsentiFuoriPianoLogistico() && UtilityString.isNullOrEmpty(mCurrentOrdine.getCodAlis())) {
            this.mListener.confirmCheckForUnlistedProduct(barcode, onComplete);
        } else {
            throw new NoResultFromBarcodeException(barcode);
        }
    }

    private void addArtFromGriglia(List<ArticoloOrdinabileDTO> listArticoli, Runnable onComplete) {
        if (listArticoli.size() == 1) {
            this.addArticoloToOrdine(
                    listArticoli.get(0),
                    onComplete,
                    this::sendError
            );
        } else {
            this.mListener.chooseArtFromList(listArticoli, (art) -> {
                this.addArticoloToOrdine(
                        art,
                        onComplete,
                        this::sendError);
            });
        }
    }


    private void loadArticoli(Ordine ordine, RunnableArgs<List<ArticoloOrdine>> onLoad) {
        try {
            mArticoliOrdineRepository.findArticoliByOrdine(ordine, onLoad, this::sendError);
        } catch (Exception e) {
            this.sendError(e);
        }
    }

    public void addArticoloToOrdine(ArticoloOrdinabileDTO art, Runnable onSuccess, RunnableArgs<Exception> onFail) {
        try {

            mArticoliOrdineRepository.findArticoloByScanAndOrdine(mCurrentOrdine, art.getCodMart(), foundArt -> {

                var articolo = foundArt != null ? completeDataArticoli(foundArt) : convertToArticoloOrdine(art, mCurrentOrdine);
                Runnable saveAction = () -> {

                    articolo.setQtaOrd(articolo.getQtaOrd() + articolo.getQtaCnf());
                    mArticoliOrdineRepository.saveArticoloToOrdine(articolo, () -> this.refreshListArticoli(onSuccess), onFail);
                };

                if (isAutoOrderOnScan()) {
                    if (!UtilityBigDecimal.isNullOrZero(articolo.getCtMaxOrd()) && UtilityBigDecimal.greaterThan(BigDecimal.valueOf(articolo.getQtaOrd() + articolo.getQtaCnf()), articolo.getCtMaxOrd().multiply(BigDecimal.valueOf(articolo.getQtaCnf())))) {
                        mListener.confirmExceedingQtyOrder(saveAction);
                    } else {
                        saveAction.run();
                    }
                } else {
                    articolo.setQtaOrd(articolo.getQtaOrd() > 0f ? articolo.getQtaOrd() : articolo.getQtaCnf());
                    this.sendOnItemDispatched(articolo, onSuccess);
                }


            }, onFail);


        } catch (Exception e) {
            onFail.run(e);
        }
    }

    private ArticoloOrdine convertToArticoloOrdine(ArticoloOrdinabileDTO art, Ordine ordine) {
        ArticoloOrdine articolo = new ArticoloOrdine();

        articolo.setIdOrdine(ordine.getOrdineId());

        articolo.setBarCode(art.getSelectedBarcode());
        articolo.setCodMart(art.getCodMart());
        articolo.setMediaSett(art.getMediaSett());
        articolo.setFlagQtaMultipla(art.getFlagQtaMultipla());
        articolo.setQtaMinOrdinabile(art.getQtaMinOrdinabile());
        articolo.setMerceDaRic(art.getMerceDaRic());
        articolo.setUntMis(art.getUntMis());
        articolo.setDescrizione(art.getDescrizione());
        articolo.setDataIns(UtilityDate.getDateInstance());
        articolo.setGgScadenza(art.getGgScadenza());
        articolo.setGiacenza(art.getGiacenza());
        articolo.setQtaPrevistaVendita(art.getQtaPrevistaVendita());
        articolo.setQtaProposta(art.getQtaProposta());
        articolo.setNewNoPromo(art.isNewNoPromo());
        articolo.setSystemNote(art.generateSystemNote());
        articolo.setQtaCnf(art.getQtaCnf() > 0f ? art.getQtaCnf() : 1);
        articolo.setCodAlis(art.getCodAlis());
        articolo.setCtMaxOrd(BigDecimal.valueOf(art.getCtMaxOrd()));

        return articolo;
    }

    public List<ArticoloOrdinabileDTO> findArticoliByBarcode(String barcode) throws NoArtsInGridException {
        if (mArticoliGriglia == null || mArticoliGriglia.isEmpty()) {
            throw new NoArtsInGridException();
        }

        List<ArticoloOrdinabileDTO> articoli = Stream.of(mArticoliGriglia).filter(articolo ->
                        articolo.getCodMart().equalsIgnoreCase(barcode) ||
                                articolo.getBarcode().contains(barcode) ||
                                articolo.getBarcode().contains(StringUtils.leftPad(barcode, 13, "0"))

                ).map((art) -> art.setSelectedBarcode(barcode))
//                .sortBy(x -> x.getSortByBarcodeCondition(barcode))
                .toList();

        return articoli;
    }


    private boolean isAutoOrderOnScan() {
        return SettingsManager.iDB().isFlagOrdinaArticoliOnScan();
    }

    private List<ArticoloOrdinabileDTO> getNewArticoli() {
        return Stream.of(mArticoliGriglia)
                .filter(x -> x.getQtaOrd() > 0f)
                .toList();
    }

    public void orderNewProducts() {
        List<ArticoloOrdinabileDTO> newArts = getNewArticoli();
        if (!newArts.isEmpty()) {
            List<ArticoloOrdine> articoliToSave = new ArrayList<>();

            Stream.of(newArts).forEach(articolo -> {
                if (
                        Stream.of(mArticoli.getValue())
                                .filter(x -> x.getCodMart().equalsIgnoreCase(articolo.getCodMart()))
                                .findFirst()
                                .isEmpty()

                                && Stream.of(articoliToSave)
                                .filter(x -> x.getCodMart().equalsIgnoreCase(articolo.getCodMart()))
                                .findFirst()
                                .isEmpty()
                ) {
                    ArticoloOrdine dto = convertToArticoloOrdine(articolo, mCurrentOrdine);
                    articoliToSave.add(dto);
                }
            });
            if (!articoliToSave.isEmpty()) {
                mArticoliOrdineRepository.saveArticoliToOrdine(articoliToSave, this::refreshListArticoli, this::sendError);

            } else this.sendOnLoadingEnded();
        } else this.sendOnLoadingEnded();
    }

    public void deleteArticolo(ArticoloOrdine articoloOrdine) {
        mArticoliOrdineRepository.deleteArticolo(articoloOrdine, () -> {
            this.mArticoli.getValue().remove(articoloOrdine);
            this.mArticoli.postValue(this.mArticoli.getValue());
        }, this::sendError);
    }

    public void exportOrdine(Runnable onComplete) {
        this.sendOnLoadingStarted();
        if (isOrderSessionInvalid()) {
            this.sendError(new ExpiredProductListException());
            return;
        }
        if (mArticoli.getValue() == null || mArticoli.getValue().isEmpty()) {
            this.sendError(new EmptyOrderException());
            return;
        }
        String codMdep = SettingsManager.i().getUserSession().getDepo().getCodMdep();

        Runnable saveOrdine = () -> {
            pvOrdiniAcquistoRESTConsumer.saveOrdine(mCurrentOrdine, mArticoli.getValue(), codMdep, (ordine) -> {

                mOrdineRepository.updateOrder(ordine, ord -> {
                    this.sendOnLoadingEnded();
                    onComplete.run();
                }, this::sendError);

            }, this::sendError);
        };

        if (Stream.of(mArticoli.getValue()).anyMatch(ArticoloOrdine::isInvalid)) {

            this.mListener.confirmExportInvalidProducts(saveOrdine);
            return;
        }
        saveOrdine.run();

    }

    private void sendOnItemDispatched(ArticoloOrdine articoloOrdine, Runnable onSuccess) {
        if (this.mListener != null) mListener.onItemDispatched(articoloOrdine, onSuccess);
    }

    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 void loadArticolo(String barcode, Runnable onComplete) {
        this.sendOnLoadingStarted();
        String codMdep = SettingsManager.i().getUserSession().getDepo().getCodMdep();
        pvOrdiniAcquistoRESTConsumer.retrieveArticolo(null, codMdep, barcode, (grigliaDTO) -> {

            RunnableArgs<ArticoloOrdinabileDTO> addArtToOrder = (articolo) -> {

                ArticoloOrdinabileDTO existingArt = Stream.of(mArticoliGriglia)
                        .filter(x -> x.getCodMart().equalsIgnoreCase(articolo.getCodMart()))
                        .findFirstOrElse(null);

                if (existingArt != null) {
                    List<String> barcodes = existingArt.getBarcode();
                    barcodes.addAll(articolo.getBarcode());
                    barcodes = Stream.of(barcodes).distinct().toList();
                    existingArt.setBarcode(barcodes);
                } else {
                    mArticoliGriglia.add(articolo);
                }

                this.addArtFromGriglia(Collections.singletonList(existingArt), onComplete);
            };

            if (grigliaDTO == null || grigliaDTO.getArticoli().isEmpty()) {
                this.sendError(new ArtNotFoundInGridException());
                return;
            }
            if (grigliaDTO.getArticoli().size() > 1) {
                this.mListener.chooseArtFromList(grigliaDTO.getArticoli(), addArtToOrder);
                return;
            }
            ArticoloOrdinabileDTO newArt = Stream.of(grigliaDTO.getArticoli()).findFirstOrElse(null);
            if (newArt == null) {
                this.sendError(new ArtNotFoundInGridException());
                return;
            }

            addArtToOrder.run(newArt);

        }, this::sendError);
    }

    public boolean isArtInOrder(ArticoloOrdinabileDTO articolo) {
        if (mArticoli.getValue() == null || mArticoli.getValue().isEmpty()) {
            return false;
        }
        return Stream.of(mArticoli.getValue()).noneMatch(x -> x.getCodMart().equalsIgnoreCase(articolo.getCodMart()));
    }

    public interface Listener extends ILoadingListener {
        void onItemDispatched(ArticoloOrdine articoloOrdine, Runnable onSuccess);

        void confirmExceedingQtyOrder(Runnable onSuccess);

        int getColorFromResource(int resId);

        void onError(Exception ex);

        void confirmCheckForUnlistedProduct(String barcode, Runnable onComplete);

        void confirmExportInvalidProducts(Runnable onConfirm);

        void chooseArtFromList(List<ArticoloOrdinabileDTO> listArticoli, RunnableArgs<ArticoloOrdinabileDTO> onArtChosen);
    }

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