package it.integry.integrywmsnative.core.data_store.db.repository.datasource;

import android.os.Handler;

import com.annimon.stream.Stream;

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import it.integry.integrywmsnative.core.data_store.db.dao.ArticoloGrigliaDao;
import it.integry.integrywmsnative.core.data_store.db.entity.ArticoloGriglia;
import it.integry.integrywmsnative.core.data_store.db.entity.Griglia;
import it.integry.integrywmsnative.core.data_store.db.repository.ArticoloGrigliaRepository;
import it.integry.integrywmsnative.core.expansion.RunnableArgs;
import it.integry.integrywmsnative.core.utility.UtilityHashMap;

public class ArticoliGrigliaDataSource extends BaseDataSource implements ArticoloGrigliaRepository {

    private final ArticoloGrigliaDao mArticoloGrigliaDao;

    @Inject
    public ArticoliGrigliaDataSource(ExecutorService executorService,
                                     Handler handler,
                                     ArticoloGrigliaDao articoloGrigliaDao) {
        super(executorService, handler);
        this.mArticoloGrigliaDao = articoloGrigliaDao;
    }

    public void saveArticoliToGriglia(List<ArticoloGriglia> updatedArts, Griglia griglia, Runnable onSuccess, RunnableArgs<Exception> onError) {

        try {
            var tmp = calcItemsToInsertAndUpdate(updatedArts);
            var toInsert = UtilityHashMap.<List<ArticoloGriglia>>getValue(tmp, "toInsert");
            var toUpdate = UtilityHashMap.<List<ArticoloGriglia>>getValue(tmp, "toUpdate");
            var codMarts = UtilityHashMap.<List<String>>getValue(tmp, "codMarts");

            executorService.execute(() -> {
                mArticoloGrigliaDao.insertAll(toInsert);
                mArticoloGrigliaDao.updateAll(toUpdate);

                List<ArticoloGriglia> articoliGriglia = mArticoloGrigliaDao.getArticoliFromGriglia(griglia.getGrigliaId());
                List<ArticoloGriglia> toDelete = Stream.of(articoliGriglia).filter(articolo -> !codMarts.contains(articolo.getCodMart())).toList();
                mArticoloGrigliaDao.deleteList(toDelete);

                handler.post(onSuccess);
            });

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

    private HashMap<String, Object> calcItemsToInsertAndUpdate(List<ArticoloGriglia> updatedArts) throws InterruptedException, ExecutionException {
        var toInsert = new ArrayList<ArticoloGriglia>();
        var toUpdate = new ArrayList<ArticoloGriglia>();
        var codMarts = new ArrayList<String>();

        List<Callable<ArticoloGriglia>> calls = new ArrayList<>();

        for (int i = 0; i < updatedArts.size(); i++) {
            int finalI = i;

            Callable<ArticoloGriglia> callableTask = () -> {
                var updatedArt = updatedArts.get(finalI);
                Integer id = mArticoloGrigliaDao.getIdArticoloByGrigliaAndBarcodeAndCodMart(updatedArt.getIdGriglia(), updatedArt.getBarCode(), updatedArt.getCodMart());

                if (id != null) {
                    updatedArt.setArticoloGrigliaId(id);
                }

                return updatedArt;
            };

            calls.add(callableTask);
        }

        List<Future<ArticoloGriglia>> futures = executorService.invokeAll(calls, Long.MAX_VALUE, TimeUnit.MILLISECONDS);

        for (Future<ArticoloGriglia> future : futures) {
            if (future.isDone()) {
                codMarts.add(future.get().getCodMart());
                if (future.get().getArticoloGrigliaId() == null) toInsert.add(future.get());
                else toUpdate.add(future.get());
            } else {
                future.cancel(true);
            }
        }

        var tmp = new HashMap<String, Object>();

        tmp.put("toInsert", toInsert);
        tmp.put("toUpdate", toUpdate);
        tmp.put("codMarts", codMarts);

        return tmp;
    }

    public void findArticoloByBarcodeAndGriglia(String barcode, int idGriglia, RunnableArgs<ArticoloGriglia> onSuccess, RunnableArgs<Exception> onError) {
        execute(() -> mArticoloGrigliaDao.findArticoloByBarcodeAndGriglia(barcode, idGriglia), onSuccess, onError);
    }

    public void findArticoloByCodMartAndGriglia(String codMart, int idGriglia, RunnableArgs<ArticoloGriglia> onSuccess, RunnableArgs<Exception> onError) {
        execute(() -> mArticoloGrigliaDao.findArticoloByCodMartAndGriglia(codMart, idGriglia), onSuccess, onError);
    }

    public void findArticoloByScanAndGriglia(String scan, int idGriglia, RunnableArgs<ArticoloGriglia> onSuccess, RunnableArgs<Exception> onError) {

        executorService.execute(() -> {
            try {
                ArticoloGriglia articolo = mArticoloGrigliaDao.findArticoloByCodMartAndGriglia(scan, idGriglia);

                if (articolo == null) {
                    articolo = mArticoloGrigliaDao.findArticoloByBarcodeAndGriglia(scan, idGriglia);
                }

                if (articolo == null) {
                    String barcode = StringUtils.leftPad(scan, 13, "0");
                    articolo = mArticoloGrigliaDao.findArticoloByBarcodeAndGriglia(barcode, idGriglia);
                }

                final ArticoloGriglia finalArticolo = articolo;
                handler.post(() -> onSuccess.run(finalArticolo));
            } catch (Exception e) {
                onError.run(e);
            }
        });
    }

    public void findNewArticoliInGrigla(int idGriglia, RunnableArgs<List<ArticoloGriglia>> onSuccess, RunnableArgs<Exception> onError) {
        execute(() -> mArticoloGrigliaDao.getNewArticoliInGriglia(idGriglia), onSuccess, onError);
    }
}
