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

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.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
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;

public class ArticoliGrigliaDataSource extends Repository implements ArticoloGrigliaRepository {

    private final ArticoloGrigliaDao mArticoloGrigliaDao;

    @Inject
    public ArticoliGrigliaDataSource(ArticoloGrigliaDao articoloGrigliaDao) {
        this.mArticoloGrigliaDao = articoloGrigliaDao;
    }

    public void saveArticoliToGriglia(List<ArticoloGriglia> updatedArts, Griglia griglia, Runnable onSuccess, RunnableArgs<Exception> onFail) {
        execute(() -> {
            try {
                var tmp = calcItemsToInsertAndUpdate(updatedArts);
                var toInsert = (List<ArticoloGriglia>) tmp.get("toInsert");
                var toUpdate = (List<ArticoloGriglia>) tmp.get("toUpdate");
                var codMarts = (List<String>) tmp.get("codMarts");

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

                onSuccess.run();
            } catch (Exception e) {
                onFail.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>();

        int cores = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(cores);
        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 = executor.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> onFail) {
        execute(() -> {
            try {
                onSuccess.run(mArticoloGrigliaDao.findArticoloByBarcodeAndGriglia(barcode, idGriglia));
            } catch (Exception e) {
                onFail.run(e);
            }
        });
    }

    public void findArticoloByCodMartAndGriglia(String codMart, int idGriglia, RunnableArgs<ArticoloGriglia> onSuccess, RunnableArgs<Exception> onFail) {
        execute(() -> {
            try {
                onSuccess.run(mArticoloGrigliaDao.findArticoloByCodMartAndGriglia(codMart, idGriglia));
            } catch (Exception e) {
                onFail.run(e);
            }
        });
    }

    public void findArticoloByScanAndGriglia(String scan, int idGriglia, RunnableArgs<ArticoloGriglia> onSuccess, RunnableArgs<Exception> onFail) {
        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);
                    }
                }
                onSuccess.run(articolo);
            } catch (Exception e) {
                onFail.run(e);
            }
        });
    }

    public void findNewArticoliInGrigla(int idGriglia, RunnableArgs<List<ArticoloGriglia>> onSuccess, RunnableArgs<Exception> onFail) {
        execute(() -> {
            try {
                onSuccess.run(mArticoloGrigliaDao.getNewArticoliInGriglia(idGriglia));
            } catch (Exception e) {
                onFail.run(e);
            }
        });
    }
}
