package de.avatic.lcc.service.excelMapper;

import de.avatic.lcc.dto.generic.TransportType;
import de.avatic.lcc.model.bulk.HiddenTableType;
import de.avatic.lcc.model.bulk.header.ContainerRateHeader;
import de.avatic.lcc.model.bulk.header.HiddenNodeHeader;
import de.avatic.lcc.model.db.rates.ContainerRate;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.rates.ContainerRateRepository;
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.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@Service
public class ContainerRateExcelMapper {

    private final HeaderGenerator headerGenerator;
    private final ContainerRateRepository containerRateRepository;
    private final NodeRepository nodeRepository;
    private final ConstraintGenerator constraintGenerator;

    public ContainerRateExcelMapper(HeaderGenerator headerGenerator, ContainerRateRepository containerRateRepository, NodeRepository nodeRepository, ConstraintGenerator constraintGenerator) {
        this.headerGenerator = headerGenerator;
        this.containerRateRepository = containerRateRepository;
        this.nodeRepository = nodeRepository;
        this.constraintGenerator = constraintGenerator;
    }

    public void fillSheet(Sheet sheet, CellStyle headerStyle, Integer periodId) {
        headerGenerator.generateHeader(sheet, ContainerRateHeader.class, headerStyle);
        containerRateRepository.listAllRatesByPeriodId(periodId).forEach(rate -> mapToRow(rate, sheet.createRow(sheet.getLastRowNum() + 1)));
        headerGenerator.fixWidth(sheet, ContainerRateHeader.class);
    }

    private void mapToRow(ContainerRate rate, Row row) {
        row.createCell(ContainerRateHeader.FROM_NODE.ordinal()).setCellValue(nodeRepository.getById(rate.getFromNodeId()).orElseThrow().getExternalMappingId());
        row.createCell(ContainerRateHeader.TO_NODE.ordinal()).setCellValue(nodeRepository.getById(rate.getToNodeId()).orElseThrow().getExternalMappingId());
        row.createCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).setCellValue(rate.getType().name());
        row.createCell(ContainerRateHeader.RATE_FEU.ordinal()).setCellValue(rate.getRateFeu().doubleValue());
        row.createCell(ContainerRateHeader.RATE_TEU.ordinal()).setCellValue(rate.getRateTeu().doubleValue());
        row.createCell(ContainerRateHeader.RATE_HC.ordinal()).setCellValue(rate.getRateHc().doubleValue());
        row.createCell(ContainerRateHeader.LEAD_TIME.ordinal()).setCellValue(rate.getLeadTime());
    }

    public void createConstraints(Workbook workbook, Sheet sheet) {

        var namedRange = constraintGenerator.createReference(workbook, HiddenNodeHeader.MAPPING_ID.ordinal(), HiddenTableType.NODE_HIDDEN_TABLE);

        constraintGenerator.createFormulaListConstraint(sheet, ContainerRateHeader.FROM_NODE.ordinal(), namedRange.getNameName());
        constraintGenerator.createFormulaListConstraint(sheet, ContainerRateHeader.TO_NODE.ordinal(), namedRange.getNameName());

        constraintGenerator.createEnumConstraint(sheet, ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal(), TransportType.class);
        constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_FEU.ordinal(), 0.0, 1000000.0);
        constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_TEU.ordinal(), 0.0, 1000000.0);
        constraintGenerator.createDecimalConstraint(sheet, ContainerRateHeader.RATE_HC.ordinal(), 0.0, 1000000.0);

        constraintGenerator.createIntegerConstraint(sheet, ContainerRateHeader.LEAD_TIME.ordinal(), 0, 365);

    }

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

    public List<ContainerRate> extractSheet(Sheet sheet) {
        headerGenerator.validateHeader(sheet, ContainerRateHeader.class);

        var rates = new ArrayList<ContainerRate>();

        sheet.forEach(row -> {
            if (row.getRowNum() == 0) return;
            if (!isEmpty(row))
                rates.add(mapToEntity(row));
        });

        return rates;
    }

    private boolean isEmpty(Row row) {

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

        return true;
    }

    private String toExcelLetter(int columnIdx) {
        StringBuilder result = new StringBuilder();
        columnIdx++; // Convert from 0-based to 1-based for the algorithm
        while (columnIdx > 0) {
            columnIdx--; // Adjust for 1-based indexing
            result.insert(0, (char) ('A' + columnIdx % 26));
            columnIdx /= 26;
        }
        return result.toString();
    }

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

        validateConstraints(row);

        var fromNode = nodeRepository.getByExternalMappingId(row.getCell(ContainerRateHeader.FROM_NODE.ordinal()).getStringCellValue());
        var toNode = nodeRepository.getByExternalMappingId(row.getCell(ContainerRateHeader.TO_NODE.ordinal()).getStringCellValue());

        if (fromNode.isEmpty() || fromNode.get().getDeprecated()) {
            throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(ContainerRateHeader.FROM_NODE.ordinal()) + ": Node with mapping id " + row.getCell(ContainerRateHeader.FROM_NODE.ordinal()).getStringCellValue() + " not found.");
        }

        if (toNode.isEmpty() || toNode.get().getDeprecated()) {
            throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(ContainerRateHeader.TO_NODE.ordinal()) + ": Node with mapping id " + row.getCell(ContainerRateHeader.TO_NODE.ordinal()).getStringCellValue() + " not found.");
        }

        entity.setFromNodeId(fromNode.orElseThrow().getId());
        entity.setToNodeId(toNode.orElseThrow().getId());
        entity.setType(TransportType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
        entity.setLeadTime(Double.valueOf(row.getCell(ContainerRateHeader.LEAD_TIME.ordinal()).getNumericCellValue()).intValue());
        entity.setType(TransportType.valueOf(row.getCell(ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal()).getStringCellValue()));
        entity.setRateFeu(BigDecimal.valueOf(row.getCell(ContainerRateHeader.RATE_FEU.ordinal()).getNumericCellValue()));
        entity.setRateTeu(BigDecimal.valueOf(row.getCell(ContainerRateHeader.RATE_TEU.ordinal()).getNumericCellValue()));
        entity.setRateHc(BigDecimal.valueOf(row.getCell(ContainerRateHeader.RATE_HC.ordinal()).getNumericCellValue()));

        return entity;
    }

    private void validateConstraints(Row row) {

        constraintGenerator.validateStringCell(row, ContainerRateHeader.FROM_NODE.ordinal());
        constraintGenerator.validateStringCell(row, ContainerRateHeader.TO_NODE.ordinal());
        constraintGenerator.validateEnumConstraint(row, ContainerRateHeader.CONTAINER_RATE_TYPE.ordinal(), TransportType.class);
        constraintGenerator.validateDecimalConstraint(row, ContainerRateHeader.RATE_FEU.ordinal(), 0.0, 1000000.0);
        constraintGenerator.validateDecimalConstraint(row, ContainerRateHeader.RATE_TEU.ordinal(), 0.0, 1000000.0);
        constraintGenerator.validateDecimalConstraint(row, ContainerRateHeader.RATE_HC.ordinal(), 0.0, 1000000.0);
        constraintGenerator.validateIntegerConstraint(row, ContainerRateHeader.LEAD_TIME.ordinal(), 0, 365);

    }
}
