package de.avatic.lcc.service.excelMapper;


import de.avatic.lcc.model.excel.ExcelPackaging;
import de.avatic.lcc.model.bulk.BulkInstruction;
import de.avatic.lcc.model.bulk.BulkInstructionType;
import de.avatic.lcc.model.bulk.HiddenTableType;
import de.avatic.lcc.model.bulk.header.HiddenNodeHeader;
import de.avatic.lcc.model.bulk.header.PackagingHeader;
import de.avatic.lcc.model.db.packaging.Packaging;
import de.avatic.lcc.model.db.packaging.PackagingDimension;
import de.avatic.lcc.model.db.properties.PropertyType;
import de.avatic.lcc.model.db.utils.DimensionUnit;
import de.avatic.lcc.model.db.utils.WeightUnit;
import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.packaging.PackagingDimensionRepository;
import de.avatic.lcc.repositories.packaging.PackagingPropertiesRepository;
import de.avatic.lcc.repositories.packaging.PackagingRepository;
import de.avatic.lcc.service.bulk.helper.ConstraintGenerator;
import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class PackagingExcelMapper {


    private final HeaderGenerator headerGenerator;
    private final PackagingRepository packagingRepository;
    private final MaterialRepository materialRepository;
    private final NodeRepository nodeRepository;
    private final PackagingDimensionRepository packagingDimensionRepository;
    private final PackagingPropertiesRepository packagingPropertiesRepository;
    private final ConstraintGenerator constraintGenerator;

    public PackagingExcelMapper(HeaderGenerator headerGenerator, PackagingRepository packagingRepository, MaterialRepository materialRepository, NodeRepository nodeRepository, PackagingDimensionRepository packagingDimensionRepository, PackagingPropertiesRepository packagingPropertiesRepository, ConstraintGenerator constraintGenerator) {
        this.headerGenerator = headerGenerator;
        this.packagingRepository = packagingRepository;
        this.materialRepository = materialRepository;
        this.nodeRepository = nodeRepository;
        this.packagingDimensionRepository = packagingDimensionRepository;
        this.packagingPropertiesRepository = packagingPropertiesRepository;
        this.constraintGenerator = constraintGenerator;
    }

    public void fillSheet(Sheet sheet, CellStyle headerStyle) {
        var headers = new ArrayList<>(EnumSet.allOf(PackagingHeader.class).stream().map(PackagingHeader::getHeader).toList());
        headers.addAll(packagingPropertiesRepository.listTypes().stream().map(PropertyType::getExternalMappingId).toList());

        headerGenerator.generateHeader(sheet, headers.toArray(String[]::new), headerStyle);
        packagingRepository.listAllPackaging().forEach(p -> mapToRow(p, headers, sheet.createRow(sheet.getLastRowNum() + 1)));
        headerGenerator.fixWidth(sheet, headers.toArray(String[]::new));
    }

    private void mapToRow(Packaging packaging, ArrayList<String> headers, Row row) {
        Optional<PackagingDimension> shu = packagingDimensionRepository.getById(packaging.getShuId());
        Optional<PackagingDimension> hu = packagingDimensionRepository.getById(packaging.getShuId());

        row.createCell(PackagingHeader.OPERATION.ordinal()).setCellValue(BulkInstructionType.UPDATE.name());

        row.createCell(PackagingHeader.PART_NUMBER.ordinal()).setCellValue(materialRepository.getByIdIncludeDeprecated(packaging.getMaterialId()).orElseThrow().getPartNumber());
        row.createCell(PackagingHeader.SUPPLIER.ordinal()).setCellValue(nodeRepository.getById(packaging.getSupplierId()).orElseThrow().getExternalMappingId());

        mapDimensionToCell(row.createCell(PackagingHeader.SHU_HEIGHT.ordinal()), shu, PackagingDimension::getHeight);
        mapDimensionToCell(row.createCell(PackagingHeader.SHU_WIDTH.ordinal()), shu, PackagingDimension::getWidth);
        mapDimensionToCell(row.createCell(PackagingHeader.SHU_LENGTH.ordinal()), shu, PackagingDimension::getLength);
        mapWeightToCell(row.createCell(PackagingHeader.SHU_WEIGHT.ordinal()), shu, PackagingDimension::getWeight);
        mapToCell(row.createCell(PackagingHeader.SHU_UNIT_COUNT.ordinal()), shu, PackagingDimension::getContentUnitCount);
        mapToCell(row.createCell(PackagingHeader.SHU_DIMENSION_UNIT.ordinal()), shu, PackagingDimension::getDimensionUnit);
        mapToCell(row.createCell(PackagingHeader.SHU_WEIGHT_UNIT.ordinal()), shu, PackagingDimension::getWeightUnit);

        mapDimensionToCell(row.createCell(PackagingHeader.HU_HEIGHT.ordinal()), hu, PackagingDimension::getHeight);
        mapDimensionToCell(row.createCell(PackagingHeader.HU_WIDTH.ordinal()), hu, PackagingDimension::getWidth);
        mapDimensionToCell(row.createCell(PackagingHeader.HU_LENGTH.ordinal()), hu, PackagingDimension::getLength);
        mapWeightToCell(row.createCell(PackagingHeader.HU_WEIGHT.ordinal()), hu, PackagingDimension::getWeight);
        mapToCell(row.createCell(PackagingHeader.HU_UNIT_COUNT.ordinal()), hu, PackagingDimension::getContentUnitCount);
        mapToCell(row.createCell(PackagingHeader.HU_DIMENSION_UNIT.ordinal()), hu, PackagingDimension::getDimensionUnit);
        mapToCell(row.createCell(PackagingHeader.HU_WEIGHT_UNIT.ordinal()), hu, PackagingDimension::getWeightUnit);

        var properties = packagingPropertiesRepository.getByPackagingId(packaging.getId());
        properties.forEach(p -> row.createCell(headers.indexOf(p.getType().getExternalMappingId())).setCellValue(p.getValue()));

    }

    private void mapDimensionToCell(Cell cell, Optional<PackagingDimension> packaging, Function<PackagingDimension, Integer> provider) {
        if (packaging.isPresent())
            cell.setCellValue(packaging.get().getDimensionUnit().convertFromMM(provider.apply(packaging.get())));
        else cell.setBlank();
    }

    private void mapWeightToCell(Cell cell, Optional<PackagingDimension> packaging, Function<PackagingDimension, Integer> provider) {
        if (packaging.isPresent())
            cell.setCellValue(packaging.get().getWeightUnit().convertFromG(provider.apply(packaging.get())));
        else cell.setBlank();
    }

    private <T, R> void mapToCell(Cell cell, Optional<T> dimension, Function<T, R> resolver) {
        if (dimension.isPresent()) {
            if (resolver.apply(dimension.get()) instanceof Integer)
                cell.setCellValue((Integer) resolver.apply(dimension.get()));
            if (resolver.apply(dimension.get()) instanceof String)
                cell.setCellValue((String) resolver.apply(dimension.get()));
            if (resolver.apply(dimension.get()) instanceof DimensionUnit)
                cell.setCellValue(((DimensionUnit) resolver.apply(dimension.get())).name());
            if (resolver.apply(dimension.get()) instanceof WeightUnit)
                cell.setCellValue(((WeightUnit) resolver.apply(dimension.get())).name());
        } else cell.setBlank();
    }

    public void createConstraints(Workbook workbook, Sheet sheet) {
        var namedRange = constraintGenerator.createReference(workbook, HiddenNodeHeader.MAPPING_ID.ordinal(), HiddenTableType.NODE_HIDDEN_TABLE);

        constraintGenerator.createFormulaListConstraint(sheet, PackagingHeader.SUPPLIER.ordinal(), namedRange.getNameName());
        constraintGenerator.createLengthConstraint(sheet, PackagingHeader.PART_NUMBER.ordinal(), 0, 12);

        constraintGenerator.createEnumConstraint(sheet, PackagingHeader.SHU_DIMENSION_UNIT.ordinal(), DimensionUnit.class);
        constraintGenerator.createEnumConstraint(sheet, PackagingHeader.SHU_WEIGHT_UNIT.ordinal(), WeightUnit.class);
        constraintGenerator.createEnumConstraint(sheet, PackagingHeader.HU_DIMENSION_UNIT.ordinal(), DimensionUnit.class);
        constraintGenerator.createEnumConstraint(sheet, PackagingHeader.HU_WEIGHT_UNIT.ordinal(), WeightUnit.class);

        constraintGenerator.createEnumConstraint(sheet, PackagingHeader.OPERATION.ordinal(), BulkInstructionType.class);


        //TODO: check hu dimensions...

        //todo check properties?

    }

    public boolean isSheet(Sheet sheet) {
        try {
            headerGenerator.validateHeader(sheet, PackagingHeader.class);
        } catch (ExcelValidationError e) {
            return false;
        }
        return true;
    }

    public List<BulkInstruction<ExcelPackaging>> extractSheet(Sheet sheet) {
        headerGenerator.validateHeader(sheet, PackagingHeader.class);
        Map<Integer, String> propertyMappingIds = getPropertyHeaders(sheet.getRow(0));

        var packaging = new ArrayList<BulkInstruction<ExcelPackaging>>();
        sheet.forEach(row -> {
            if (row.getRowNum() == 0) return;
            if (!isEmpty(row))
                packaging.add(mapToEntity(row, propertyMappingIds));
        });
        return packaging;
    }

    private boolean isEmpty(Row row) {

        for (Cell cell : row) {
            if (cell.getCellType() != CellType.BLANK) {
                return false;
            }
        }

        return true;
    }

    private Map<Integer, String> getPropertyHeaders(Row row) {
        var propertyMappingIds = packagingPropertiesRepository.listTypes().stream().map(PropertyType::getExternalMappingId).toList();

        var headers = new HashMap<Integer, String>();
        for (int i = 0; i < row.getLastCellNum(); i++) {
            if (propertyMappingIds.contains(row.getCell(i).getStringCellValue())) {
                headers.put(i, row.getCell(i).getStringCellValue());
            }
        }
        return headers;
    }


    private BulkInstruction<ExcelPackaging> mapToEntity(Row row, Map<Integer, String> propertyMappingIds) {
        ExcelPackaging entity = new ExcelPackaging();

        validateConstraints(row);

        entity.setSupplierMappingId(row.getCell(PackagingHeader.SUPPLIER.ordinal()).getStringCellValue());
        entity.setPartNumber(row.getCell(PackagingHeader.PART_NUMBER.ordinal()).getStringCellValue());

        entity.setHuHeight(row.getCell(PackagingHeader.HU_HEIGHT.ordinal()).getNumericCellValue());
        entity.setHuWidth(row.getCell(PackagingHeader.HU_WIDTH.ordinal()).getNumericCellValue());
        entity.setHuLength(row.getCell(PackagingHeader.HU_LENGTH.ordinal()).getNumericCellValue());
        entity.setHuWeight(row.getCell(PackagingHeader.HU_WEIGHT.ordinal()).getNumericCellValue());

        entity.setHuDimensionUnit(DimensionUnit.valueOf(row.getCell(PackagingHeader.HU_DIMENSION_UNIT.ordinal()).getStringCellValue()));
        entity.setHuWeightUnit(WeightUnit.valueOf(row.getCell(PackagingHeader.HU_WEIGHT_UNIT.ordinal()).getStringCellValue()));

        entity.setShuHeight(row.getCell(PackagingHeader.SHU_HEIGHT.ordinal()).getNumericCellValue());
        entity.setShuWidth(row.getCell(PackagingHeader.SHU_WIDTH.ordinal()).getNumericCellValue());
        entity.setShuLength(row.getCell(PackagingHeader.SHU_LENGTH.ordinal()).getNumericCellValue());
        entity.setShuWeight(row.getCell(PackagingHeader.SHU_WEIGHT.ordinal()).getNumericCellValue());

        entity.setShuDimensionUnit(DimensionUnit.valueOf(row.getCell(PackagingHeader.SHU_DIMENSION_UNIT.ordinal()).getStringCellValue()));
        entity.setShuWeightUnit(WeightUnit.valueOf(row.getCell(PackagingHeader.SHU_WEIGHT_UNIT.ordinal()).getStringCellValue()));

        entity.setHuUnitCount(Double.valueOf(row.getCell(PackagingHeader.HU_UNIT_COUNT.ordinal()).getNumericCellValue()).intValue());
        entity.setShuUnitCount(Double.valueOf(row.getCell(PackagingHeader.SHU_UNIT_COUNT.ordinal()).getNumericCellValue()).intValue());

        entity.setProperties(propertyMappingIds.keySet().stream().collect(Collectors.toMap(propertyMappingIds::get, idx -> row.getCell(idx).getStringCellValue())));

        return new BulkInstruction<>(entity, BulkInstructionType.valueOf(row.getCell(PackagingHeader.OPERATION.ordinal()).getStringCellValue()));

    }

    private void validateConstraints(Row row) {

        constraintGenerator.validateStringCell(row, PackagingHeader.SUPPLIER.ordinal());
        constraintGenerator.validateStringCell(row, PackagingHeader.PART_NUMBER.ordinal());

        constraintGenerator.validateNumericCell(row, PackagingHeader.HU_HEIGHT.ordinal());
        constraintGenerator.validateNumericCell(row, PackagingHeader.HU_WIDTH.ordinal());
        constraintGenerator.validateNumericCell(row, PackagingHeader.HU_LENGTH.ordinal());
        constraintGenerator.validateNumericCell(row, PackagingHeader.HU_WEIGHT.ordinal());
        constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_HEIGHT.ordinal());
        constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_WIDTH.ordinal());
        constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_LENGTH.ordinal());
        constraintGenerator.validateNumericCell(row, PackagingHeader.SHU_WEIGHT.ordinal());

        constraintGenerator.validateIntegerCell(row, PackagingHeader.HU_UNIT_COUNT.ordinal());
        constraintGenerator.validateIntegerCell(row, PackagingHeader.SHU_UNIT_COUNT.ordinal());

        constraintGenerator.validateLengthConstraint(row, PackagingHeader.PART_NUMBER.ordinal(), 0, 12);
        constraintGenerator.validateEnumConstraint(row, PackagingHeader.SHU_DIMENSION_UNIT.ordinal(), DimensionUnit.class);
        constraintGenerator.validateEnumConstraint(row, PackagingHeader.SHU_WEIGHT_UNIT.ordinal(), WeightUnit.class);
        constraintGenerator.validateEnumConstraint(row, PackagingHeader.HU_DIMENSION_UNIT.ordinal(), DimensionUnit.class);
        constraintGenerator.validateEnumConstraint(row, PackagingHeader.HU_WEIGHT_UNIT.ordinal(), WeightUnit.class);
        constraintGenerator.validateEnumConstraint(row, PackagingHeader.OPERATION.ordinal(), BulkInstructionType.class);

    }
}
