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

import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.application.PersistedEntityAndResult;
import de.accso.flexinale.common.domain.model.AbstractDao;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Mergeable;
import org.apache.poi.ss.usermodel.Cell;
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;
import java.util.Optional;

import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.ADDED;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.UPDATED;

@SuppressWarnings("unused")
@DoNotCheckInArchitectureTests
public abstract class AbstractExcelDataUploadService<E extends Identifiable & Mergeable<E>> 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)) {
                // ignore comments, starting with # , load all other rows
                Cell firstCell = excelRow.getCell(0);
                if (firstCell != null && !firstCell.getStringCellValue().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 Collection<PersistedEntityAndResult<E>> loadDataFromExcelSheetAndPersistAndReturn(
            final InputStream stream, final PersistMode mode) throws IOException {
        Collection<E> dataCollectionFromExcelSheet = loadDataFromExcelSheet(stream);
        return loadDataAndPersistAndReturn(dataCollectionFromExcelSheet, mode);
    }

    @Override
    public Collection<PersistedEntityAndResult<E>> loadDataAndPersistAndReturn(
            final Collection<E> dataCollection, final PersistMode mode) {
        Collection<PersistedEntityAndResult<E>> dataAddedAndUpdated = new HashSet<>();

        AbstractDao<E> dao = getDao();

        String sheetName = getNameOfExcelDataType();

        for (E dataFromExcelSheet : dataCollection) {
            Identifiable.Id id = dataFromExcelSheet.id();

            Optional<E> optionalEntityFromDatabase = dao.findById(id);
            if (optionalEntityFromDatabase.isPresent()) {
                E entityFromDatabase = optionalEntityFromDatabase.get();
                switch(mode) {
                    case ADD_ONLY ->
                        LOGGER.warn("%s %s already exists. Not persisted as mode 'ADD_ONLY' is active".formatted(sheetName, id));
                    case UPDATE -> {
                        LOGGER.info("%s %s already exists. Merged as mode 'UPDATE' is active".formatted(sheetName, id));
                        E mergedEntity = entityFromDatabase.merge(dataFromExcelSheet);
                        dao.save(mergedEntity);

                        dataAddedAndUpdated.add( new PersistedEntityAndResult<>(dataFromExcelSheet, UPDATED) );
                    }
                }
            }
            else {
                LOGGER.info("%s %s does not yet exist. Added as new entity".formatted(sheetName, id));
                dao.save(dataFromExcelSheet);

                dataAddedAndUpdated.add( new PersistedEntityAndResult<>(dataFromExcelSheet, ADDED) );
            }
        }

        return dataAddedAndUpdated;
    }
}
