package de.avatic.lcc.service.excelMapper;


import de.avatic.lcc.excelmodel.ExcelPackaging;
import de.avatic.lcc.model.bulk.HiddenNodeHeader;
import de.avatic.lcc.model.bulk.HiddenTableType;
import de.avatic.lcc.model.bulk.PackagingHeader;
import de.avatic.lcc.model.packaging.Packaging;
import de.avatic.lcc.model.packaging.PackagingDimension;
import de.avatic.lcc.model.properties.PropertyType;
import de.avatic.lcc.model.utils.DimensionUnit;
import de.avatic.lcc.model.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 org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

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

    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.PART_NUMBER.ordinal()).setCellValue(materialRepository.getById(packaging.getMaterialId()).orElseThrow().getPartNumber());
        row.createCell(PackagingHeader.SUPPLIER.ordinal()).setCellValue(nodeRepository.getById(packaging.getSupplierId()).orElseThrow().getExternalMappingId());

        mapToCell(row.createCell(PackagingHeader.SHU_HEIGHT.ordinal()), shu, PackagingDimension::getHeight);
        mapToCell(row.createCell(PackagingHeader.SHU_WIDTH.ordinal()), shu, PackagingDimension::getWidth);
        mapToCell(row.createCell(PackagingHeader.SHU_LENGTH.ordinal()), shu, PackagingDimension::getLength);
        mapToCell(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);

        mapToCell(row.createCell(PackagingHeader.HU_HEIGHT.ordinal()), hu, PackagingDimension::getHeight);
        mapToCell(row.createCell(PackagingHeader.HU_WIDTH.ordinal()), hu, PackagingDimension::getWidth);
        mapToCell(row.createCell(PackagingHeader.HU_LENGTH.ordinal()), hu, PackagingDimension::getLength);
        mapToCell(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 <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());
        } else cell.setBlank();
    }

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

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


        //TODO: check hu dimensions...

        //todo check properties?

    }

    public List<ExcelPackaging> extractSheet(Sheet sheet) {
        if (!headerGenerator.validateHeader(sheet, PackagingHeader.class)) return null;

        var packaging = new ArrayList<ExcelPackaging>();
        sheet.forEach(row -> packaging.add(mapToEntity(row)));
        return packaging;
    }


    private ExcelPackaging mapToEntity(Row row) {
        ExcelPackaging entity = new ExcelPackaging();

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

        return entity;

    }
}
