package it.integry.integrywmsnative.gest.inventario.picking;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import com.annimon.stream.Stream;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

import it.integry.barcode_base_android_library.model.BarcodeScanDTO;
import it.integry.integrywmsnative.core.data_store.db.entity.InventarioRoomDTO;
import it.integry.integrywmsnative.core.data_store.db.entity.InventarioRowRoomDTO;
import it.integry.integrywmsnative.core.data_store.db.respository_new.InventarioRepository;
import it.integry.integrywmsnative.core.data_store.db.respository_new.InventarioRowRepository;
import it.integry.integrywmsnative.core.exception.NoResultFromBarcodeException;
import it.integry.integrywmsnative.core.exception.NoResultFromCodMartException;
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.dto.PickDataDTO;
import it.integry.integrywmsnative.core.rest.consumers.BarcodeRESTConsumer;
import it.integry.integrywmsnative.core.rest.model.Ean128Model;
import it.integry.integrywmsnative.core.rest.model.Ean13PesoModel;
import it.integry.integrywmsnative.core.rest.model.inventario.InventarioArtDTO;
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.UtilityLiveData;
import it.integry.integrywmsnative.core.utility.UtilityString;
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 PickingInventarioViewModel extends ViewModel {


    private final BarcodeRESTConsumer barcodeRESTConsumer;
    private final InventarioRepository inventarioRepository;
    private final InventarioRowRepository inventarioRowRepository;
    public MutableLiveData<InventarioRoomDTO> currentInventario = new MutableLiveData<>();
    public LiveData<List<InventarioRowRoomDTO>> currentInventarioRows = new MutableLiveData<>();
    private boolean canAddUnknownItems;
    private boolean flagShouldAskToCreateOrUpdateRowInventario;
    private List<InventarioArtDTO> availableInventarioArts;
    private Listener mListener;

    public PickingInventarioViewModel(InventarioRepository inventarioRepository,
                                      InventarioRowRepository inventarioRowRepository,
                                      BarcodeRESTConsumer barcodeRESTConsumer) {
        this.inventarioRepository = inventarioRepository;
        this.inventarioRowRepository = inventarioRowRepository;
        this.barcodeRESTConsumer = barcodeRESTConsumer;
    }

    public void init(InventarioRoomDTO inventarioRoomDTO, List<InventarioArtDTO> inventarioArts, boolean canAddUnknownItems, boolean flagShouldAskToCreateOrUpdateRowInventario) {
        this.canAddUnknownItems = canAddUnknownItems;
        this.flagShouldAskToCreateOrUpdateRowInventario = flagShouldAskToCreateOrUpdateRowInventario;

        this.sendOnLoadingStarted();

        this.currentInventario.postValue(inventarioRoomDTO);
        this.currentInventarioRows = inventarioRowRepository.retrieve(inventarioRoomDTO.getId());
        this.availableInventarioArts = inventarioArts == null ? new ArrayList<>() : inventarioArts;

        UtilityLiveData.observeOnce(this.currentInventarioRows, data -> {
            this.sendOnLoadingEnded();
        });

        this.sendOnLoadingEnded();
    }

    public LiveData<InventarioRoomDTO> getCurrentInventario() {
        return currentInventario;
    }

    public LiveData<List<InventarioRowRoomDTO>> getCurrentInventarioRows() {
        return currentInventarioRows;
    }

    public List<InventarioArtDTO> getAvailableArts() {
        return availableInventarioArts;
    }

    public void processBarcodeDTO(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        this.sendOnLoadingStarted();
        if (UtilityBarcode.isEanPeso(barcodeScanDTO)) {
            //Cerco tramite etichetta ean peso
            this.executeEtichettaEanPeso(barcodeScanDTO, onComplete);

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

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

    public void loadArticoloByCodMartOrBarcode(String codMart, Runnable onComplete) {
        var matchedArt = Stream.of(this.availableInventarioArts)
                .filter(x -> x.getCodMart().equalsIgnoreCase(codMart) ||
                        x.getBarcode().contains(codMart))
                .findFirst()
                .orElse(null);


        if (matchedArt == null) {
            this.sendError(new NoResultFromCodMartException(codMart));
            return;
        }


        loadArticolo(matchedArt.getBarcode().get(0), null, onComplete);
    }

    public void loadArticoloByMtbAart(MtbAart mtbAart, Runnable onComplete) {
        loadArticolo(mtbAart.getBarCode(), null, onComplete);
    }


    private void executeEtichettaEanPeso(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        try {
            Ean13PesoModel ean13PesoModel = Ean13PesoModel.fromBarcode(barcodeScanDTO.getStringValue());
            this.loadArticolo(ean13PesoModel.getPrecode(), ean13PesoModel.toEan128(), onComplete);
        } catch (Exception ex) {
            this.sendError(ex);
        }
    }

    private void executeEtichettaEan128(BarcodeScanDTO barcodeScanDTO, Runnable onComplete) {
        barcodeRESTConsumer.decodeEan128(barcodeScanDTO, ean128Model -> {

            String barcodeProd = null;
            String codMart = null;

            if (!UtilityString.isNullOrEmpty(ean128Model.Gtin)) barcodeProd = ean128Model.Gtin;
            if (!UtilityString.isNullOrEmpty(ean128Model.Content))
                barcodeProd = ean128Model.Content;

            if (!UtilityString.isNullOrEmpty(ean128Model.Internal1))
                codMart = ean128Model.Internal1;


            if (!UtilityString.isNullOrEmpty(barcodeProd)) {
                this.loadArticolo(barcodeProd, ean128Model, onComplete);

            } else if (!UtilityString.isNullOrEmpty(codMart)) {
                this.loadArticoloByCodMartOrBarcode(codMart, onComplete);

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

    private void loadArticolo(String barcodeProd, Ean128Model ean128Model, Runnable onComplete) {
        var barcodeList = new ArrayList<String>();
        barcodeList.add(barcodeProd);

        var matchedArt = availableInventarioArts.stream().filter(x -> x.getBarcode().contains(barcodeProd))
                .findFirst()
                .orElse(null);


        if (canAddUnknownItems && matchedArt == null) {
            matchedArt = new InventarioArtDTO()
                    .setBarcode(barcodeList)
                    .setFlagQtaCnfFissa(true)
                    .setPlu(false)
                    .setUntMis("PZ")
                    .setQtaCnf(BigDecimal.ONE);
        }

        if (matchedArt != null) {
            var alreadyScannedItems = searchArtInAlreadyScannedItems(matchedArt);

            if (alreadyScannedItems == null || alreadyScannedItems.isEmpty() || !flagShouldAskToCreateOrUpdateRowInventario)
                this.dispatchRowInsert(barcodeProd, matchedArt, ean128Model);

            else {
                this.sendOnItemAlreadyFound(matchedArt, alreadyScannedItems);

            }
            if(onComplete != null) onComplete.run();
        } else this.sendError(new NoResultFromBarcodeException(barcodeProd));
    }

    private List<InventarioRowRoomDTO> searchArtInAlreadyScannedItems(InventarioArtDTO inventarioArtDTO) {
        final List<InventarioRowRoomDTO> inventarioRows = this.currentInventarioRows.getValue();

        if (inventarioRows == null || inventarioRows.isEmpty()) return null;

        return Stream.of(inventarioRows).filter(x -> x.getCodMart() != null ? x.getCodMart().equalsIgnoreCase(inventarioArtDTO.getCodMart()) : inventarioArtDTO.getBarcode().contains(x.getScanCodBarre())).toList();
    }


    public void dispatchRowInsert(String barcode, InventarioArtDTO inventarioArtDTO, Ean128Model ean128Model) {
        var mtbAart = inventarioArtDTO.toMtbAart();
        mtbAart.setFlagTracciabilita("N");

        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;

        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 (inventarioArtDTO.isPlu()) {
//                if(inventarioArtDTO.getPrzVendIva().equals(BigDecimal.ZERO))
//                    this.sendError();

                qtaColDaPrelevare = UtilityBigDecimal.multiply(qtaColDaPrelevare, BigDecimal.TEN);
                qtaColDaPrelevare = UtilityBigDecimal.divide(qtaColDaPrelevare, inventarioArtDTO.getPrzVendIva(), RoundingMode.HALF_UP);
            }

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

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

        if (pickingObjectDTO.getTempPickData() != null && inventarioArtDTO.isPlu()) {
            this.saveNewRow(pickingObjectDTO, initialNumCnf, initialQtaCnf, initialQtaTot, null, barcode);
        } else {

            this.sendOnItemDispatched(pickingObjectDTO, pickingObjectDTO.getMtbAart(), null, //initialNumCnf,
                    initialQtaCnf, null, //initialQtaTot,
                    null, null, null, null, null, null, false, true, false, (pickedQuantityDTO, shouldCloseLU) -> {
                        this.saveNewRow(pickingObjectDTO, pickedQuantityDTO.getNumCnf(), pickedQuantityDTO.getQtaCnf(), pickedQuantityDTO.getQtaTot(), pickedQuantityDTO.getPartitaMag(), barcode);
                    });
        }
    }

    public void dispatchRowEdit(MtbAart mtbAart, InventarioRowRoomDTO inventarioRowRoomDTO) {

        final PickingObjectDTO pickingObjectDTO = new PickingObjectDTO().setMtbAart(mtbAart);


        this.sendOnItemDispatched(pickingObjectDTO, pickingObjectDTO.getMtbAart(), inventarioRowRoomDTO.getNumConf(), inventarioRowRoomDTO.getQtaConf(), inventarioRowRoomDTO.getQta(), null, null, null, null, null, null, false, false, false, (pickedQuantityDTO, shouldCloseLU) -> {

            this.saveEditedRow(inventarioRowRoomDTO, pickedQuantityDTO.getNumCnf(), pickedQuantityDTO.getQtaCnf(), pickedQuantityDTO.getQtaTot(), pickedQuantityDTO.getPartitaMag());
        });

    }


    private void saveNewRow(PickingObjectDTO pickingObjectDTO, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag, String scanCodBarre) {
        final InventarioRowRoomDTO inventarioRow = new InventarioRowRoomDTO();
        inventarioRow.setCodMart(pickingObjectDTO.getMtbAart().getCodMart());
        inventarioRow.setDescrizione(pickingObjectDTO.getMtbAart().getDescrizioneEstesa());
        inventarioRow.setPartitaMag(partitaMag);
        inventarioRow.setQta(qtaTot);
        inventarioRow.setNumConf(numCnf);
        inventarioRow.setQtaConf(qtaCnf);
        inventarioRow.setUntMis(pickingObjectDTO.getMtbAart().getUntMis());
        inventarioRow.setDataOraInv(UtilityDate.getNowTime());
        inventarioRow.setZona(currentInventario.getValue().getZona());
        inventarioRow.setParentId(currentInventario.getValue().getId());
        inventarioRow.setScanCodBarre(scanCodBarre);

        inventarioRowRepository.insert(inventarioRow, () -> {
        }, this::sendError);
    }

    private void saveEditedRow(InventarioRowRoomDTO inventarioRowRoomDTO, BigDecimal numCnf, BigDecimal qtaCnf, BigDecimal qtaTot, String partitaMag) {
        inventarioRowRoomDTO.setPartitaMag(partitaMag);
        inventarioRowRoomDTO.setQta(qtaTot);
        inventarioRowRoomDTO.setNumConf(numCnf);
        inventarioRowRoomDTO.setQtaConf(qtaCnf);
        inventarioRowRoomDTO.setDataOraInv(UtilityDate.getNowTime());

        inventarioRowRepository.update(inventarioRowRoomDTO, () -> {
        }, this::sendError);
    }

    public void updateRow(InventarioRowRoomDTO itemToUpdate) {
        var barcodeList = new ArrayList<String>();
        barcodeList.add(itemToUpdate.getScanCodBarre());

        var matchedArt = availableInventarioArts.stream()
                .filter(x -> itemToUpdate.getCodMart() != null && x.getCodMart().contains(itemToUpdate.getCodMart()))
                .findFirst()
                .orElse(null);

        MtbAart articolo;

        if (canAddUnknownItems && matchedArt == null) {
            matchedArt = new InventarioArtDTO().setBarcode(barcodeList).setFlagQtaCnfFissa(true).setPlu(false).setUntMis(itemToUpdate.getUntMis()).setQtaCnf(itemToUpdate.getQtaConf());
        }
        if (matchedArt != null) {
            articolo = matchedArt.toMtbAart().setFlagTracciabilita("N");
            dispatchRowEdit(articolo, itemToUpdate);
        } else this.sendError(new NoResultFromBarcodeException(itemToUpdate.getScanCodBarre()));
    }

    public void deleteRow(InventarioRowRoomDTO itemToDelete) {
        this.inventarioRowRepository.delete(itemToDelete, () -> {
        }, this::sendError);
    }


    public void exportAll(Runnable onComplete) {
        if (this.currentInventarioRows.getValue().isEmpty()) {
            this.sendError(new Exception("Impossibile esportare un inventario senza righe"));
            return;
        }

        this.sendOnLoadingStarted();
        currentInventario.getValue().setInventarioRowList(this.currentInventarioRows.getValue());

        this.inventarioRepository.export(currentInventario.getValue(), () -> {
            this.sendOnLoadingEnded();
            onComplete.run();
        }, this::sendError);
    }


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

    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, boolean canOverflowOrderQuantity, boolean canPartitaMagBeChanged, boolean canLUBeClosed, RunnableArgss<PickedQuantityDTO, Boolean> onComplete) {
        if (this.mListener != null)
            mListener.onItemDispatched(pickingObjectDTO, mtbAart, initialNumCnf, initialQtaCnf, initialQtaTot, totalQtaAvailable, totalNumCnfAvailable, qtaCnfAvailable, totalQtaToBeTaken, totalNumCnfToBeTaken, qtaCnfToBeTaken, canOverflowOrderQuantity, canPartitaMagBeChanged, canLUBeClosed, onComplete);
    }

    private void sendOnItemAlreadyFound(InventarioArtDTO matchedArt, List<InventarioRowRoomDTO> alreadyScannedItems) {
        if (this.mListener != null)
            this.mListener.onItemAlreadyFound(matchedArt, alreadyScannedItems);
    }

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

    public interface Listener extends ILoadingListener {

        void onError(Exception ex);

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


        void onItemAlreadyFound(InventarioArtDTO matchedArt, List<InventarioRowRoomDTO> alreadyScannedItems);

    }
}