package de.accso.flexinale.common.application.services;

import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.domain.dao.AbstractDao;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;

@SuppressWarnings("unused")
@DoNotCheckInArchitectureTests
public abstract class AbstractExcelDataUploadService<E extends Identifiable> implements ExcelDataUploadService<E> {
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExcelDataUploadService.class);

    @Override
    public void beforeLoad(Object... o) {}

    @Override
    public void afterLoad(Object... o)  {}

    @Override
    public final Collection<E> loadDataFromExcelSheet(final String resourceName) throws IOException {
        try (InputStream stream = AbstractExcelDataUploadService.class.getResourceAsStream(resourceName)) {
            if (stream == null) {
                throw new FileNotFoundException("Resource '%s' not found!".formatted(resourceName));
            }
            return loadDataFromExcelSheet(stream);
        }
    }

    @Override
    public final Collection<E> loadDataFromExcelSheet(final InputStream stream) throws IOException {
        Collection<E> dataSet = new HashSet<>();

        // Excel sheet (the sheet, not the file!) needs to be named exactly like the entity's class name
        // (example: "Kino")
        String sheetName = getNameOfExcelDataType();

        try (Workbook workbook = new XSSFWorkbook(stream)) {
            // iterate through each row of first/desired sheet from the workbook
            for (Row excelRow : workbook.getSheet(sheetName)) {
                String firstCell = excelRow.getCell(0).getStringCellValue();

                // ignore comments, starting with # , load all other rows
                if (!firstCell.startsWith("#")) {
                    E data = createDataFromExcelRow(excelRow);
                    dataSet.add(data);
                }
            }
        }

        return dataSet;
    }

    @Override
    public final int loadDataFromExcelSheetAndPersist(
            final String resourceName, final PersistMode mode) throws IOException {
        try (InputStream stream = AbstractExcelDataUploadService.class.getResourceAsStream(resourceName)) {
            if (stream == null) {
                throw new IOException("Resource '%s' not found!".formatted(resourceName));
            }
            return loadDataFromExcelSheetAndPersist(stream, mode);
        }
    }

    @Override
    public final int loadDataFromExcelSheetAndPersist(
            final InputStream stream, final PersistMode mode) throws IOException {
        return loadDataFromExcelSheetAndPersistAndReturn(stream, mode).size();
    }

    @Override
    public final Collection<E> loadDataFromExcelSheetAndPersistAndReturn(
            final InputStream stream, final PersistMode mode) throws IOException {
        Collection<E> dataCollection = loadDataFromExcelSheet(stream);
        Collection<E> dataLoaded = new HashSet<>();
        AbstractDao<E> dao = getDao();

        // in mode "ADD_ONLY" no updates are allowed
        for (E dataFromExcelSheet : dataCollection) {
            Identifiable.Id id = dataFromExcelSheet.id();

            if (mode == PersistMode.ADD_ONLY && dao.findById(id).isPresent()) {
                String sheetName = getNameOfExcelDataType();

                LOGGER.warn("%s %s already exists. Will not be persisted because 'ADD' is active".formatted(sheetName, id));
            } else {
                dao.save(dataFromExcelSheet);
                dataLoaded.add(dataFromExcelSheet);
            }
        }

        return dataLoaded; // only return what really has been loaded
    }
}
