package de.avatic.lcc.service.bulk.helper;

import de.avatic.lcc.model.bulk.HiddenTableType;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.springframework.stereotype.Service;

import java.util.EnumSet;

@Service
public class ConstraintGenerator {

    private static final int MAX_ROWS = 100000;


    public void createBooleanConstraint(Sheet sheet, Integer columnIdx) {
        createConstraint(sheet, columnIdx, new String[]{"true", "false"});
    }

    public <T extends Enum<T>> void createEnumConstraint(Sheet sheet, Integer columnIdx, Class<T> values) {
        createConstraint(sheet, columnIdx, EnumSet.allOf(values).stream().map(Enum::name).toArray(String[]::new));
    }

    private void createConstraint(Sheet sheet, Integer columnIdx, String[] values) {
        var helper = sheet.getDataValidationHelper();
        var constraint = helper.createExplicitListConstraint(values);


        var validation = helper.createValidation(constraint, new CellRangeAddressList(1, MAX_ROWS, columnIdx, columnIdx));

        String errorMessage = values.length > 8 ? "Please check dropdown for allowed values." :
                String.format("Allowed values: %s", String.join(", ", values));
        if (errorMessage.length() > 200) {
            errorMessage = "Please check dropdown for allowed values.";
        }

        validation.createErrorBox("Invalid Value", errorMessage);
        validation.setShowErrorBox(true);
        sheet.addValidationData(validation);
    }

    public void createFormulaListConstraint(Sheet sheet, Integer columnIdx, String namedReference) {
        var helper = sheet.getDataValidationHelper();
        var constraint = helper.createFormulaListConstraint(namedReference);

        var validation = helper.createValidation(constraint, new CellRangeAddressList(1, MAX_ROWS, columnIdx, columnIdx));
        validation.createErrorBox("Invalid Value", "Please check dropdown for allowed values.");
        validation.setShowErrorBox(true);
        sheet.addValidationData(validation);
    }

    public void createLengthConstraint(Sheet sheet, Integer columnIdx, Integer min, Integer max) {
        var helper = sheet.getDataValidationHelper();
        var constraint = helper.createTextLengthConstraint(DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(min), String.valueOf(max));

        var validation = helper.createValidation(constraint, new CellRangeAddressList(1, MAX_ROWS, columnIdx, columnIdx));
        validation.createErrorBox("Invalid Value", String.format("Allowed length is between %d and %d characters.", min, max));
        validation.setShowErrorBox(true);
        sheet.addValidationData(validation);
    }

    public void createDecimalConstraint(Sheet sheet, Integer columnIdx, Number min, Number max) {
        var helper = sheet.getDataValidationHelper();
        var constraint = helper.createDecimalConstraint(DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(min), String.valueOf(max));

        var validation = helper.createValidation(constraint, new CellRangeAddressList(1, MAX_ROWS, columnIdx, columnIdx));
        validation.createErrorBox("Invalid Value", String.format("Allowed value is between %f and %f.", min.floatValue(), max.floatValue()));
        validation.setShowErrorBox(true);
        sheet.addValidationData(validation);
    }

    public void createIntegerConstraint(Sheet sheet, Integer columnIdx, Number min, Number max) {
        var helper = sheet.getDataValidationHelper();
        var constraint = helper.createIntegerConstraint(DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(min), String.valueOf(max));

        var validation = helper.createValidation(constraint, new CellRangeAddressList(1, MAX_ROWS, columnIdx, columnIdx));
        validation.createErrorBox("Invalid Value", String.format("Allowed value is between %f and %f.", min.floatValue(), max.floatValue()));
        validation.setShowErrorBox(true);
        sheet.addValidationData(validation);
    }

    public void createUniqueConstraint(Sheet sheet, int columnIdx) {

        // SheetConditionalFormatting Objekt holen
        SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();


        String columnLetter = toExcelLetter(columnIdx);


        CellRangeAddress[] regions = {
                CellRangeAddress.valueOf(columnLetter + "2:" + columnLetter + (MAX_ROWS))
        };

        ConditionalFormattingRule rule = sheetCF.createConditionalFormattingRule(
                "COUNTIF($" + columnLetter + "$2:$" + columnLetter + "$" + (MAX_ROWS) + "," + columnLetter + "2)>1"
        );


        createHighlightCellStyle(rule);

        sheetCF.addConditionalFormatting(regions, rule);

    }

    private void createHighlightCellStyle(ConditionalFormattingRule rule) {


        XSSFColor customTextColor = new XSSFColor(new byte[]{(byte) 255, (byte) 255, (byte) 255}, null);
        XSSFColor customColor = new XSSFColor(new byte[]{(byte) 188, (byte) 43, (byte) 114}, null);


        PatternFormatting patternFmt = rule.createPatternFormatting();
        patternFmt.setFillBackgroundColor(customColor);
        patternFmt.setFillPattern(PatternFormatting.SOLID_FOREGROUND);

        FontFormatting fontFmt = rule.createFontFormatting();
        fontFmt.setFontColor(customTextColor);
        fontFmt.setFontStyle(false, true);


    }

    public Name createReference(Workbook workbook, Integer columnIdx, HiddenTableType hiddenTableType) {
        // Check if the referenced sheet exists
        Sheet hiddenSheet = workbook.getSheet(hiddenTableType.getSheetName());
        if (hiddenSheet == null) {
            throw new IllegalStateException("Hidden sheet " + hiddenTableType.getSheetName() + " does not exist.");
        }

        Name namedRange = workbook.createName();
        namedRange.setNameName(hiddenTableType.getReferenceName());

        // Create a more conservative range - only to the last row with data
        int lastRow = Math.max(hiddenSheet.getLastRowNum(), 1); // At least row 1
        String columnLetter = toExcelLetter(columnIdx);
        String reference = hiddenTableType.getSheetName() + "!$" + columnLetter + "$2:$" + columnLetter + "$" + (lastRow + 1);

        namedRange.setRefersToFormula(reference);
        return namedRange;
    }

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

    public void validateDecimalConstraint(Row row, int columnIdx, double min, double max, boolean allowEmpty) {

        if(checkEmptyCell(row, columnIdx, allowEmpty))
            return;

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.NUMERIC) {
            double value = row.getCell(columnIdx).getNumericCellValue();

            if (value >= min && value <= max) {
                return;
            }
        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric value within range:  [" + min + ", " + max + "].");


    }

    public void validateDecimalConstraint(Row row, int columnIdx, double min, double max) {
        validateDecimalConstraint(row, columnIdx, min, max, false);
    }

    public void validateBooleanConstraint(Row row, int columnIdx) {

        checkEmptyCell(row, columnIdx);

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.STRING) {
            try {
                //noinspection ResultOfMethodCallIgnored
                Boolean.valueOf(row.getCell(columnIdx).getStringCellValue());
                return;
            } catch (Exception e) {
                // cont
            }
        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string value within: [true, false].");

    }

    public void validateLengthConstraint(Row row, int columnIdx, int min, int max) {

        checkEmptyCell(row, columnIdx);

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.STRING) {


            String value = row.getCell(columnIdx).getStringCellValue();

            if (value.length() >= min && value.length() <= max) {
                return;
            }
        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string with length within range: [" + min + ", " + max + "].");

    }

    public <T extends Enum<T>> void validateEnumConstraint(Row row, int columnIdx, Class<T> clazz) {

        checkEmptyCell(row, columnIdx);

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.STRING) {

            String value = row.getCell(columnIdx).getStringCellValue();

            if (EnumSet.allOf(clazz).stream().anyMatch(enumValue -> enumValue.name().equals(value))) {
                return;
            }

        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string value within: [" + String.join(", ", EnumSet.allOf(clazz).stream().map(Enum::name).toArray(String[]::new)) + "].");


    }

    public void validateNumericCell(Row row, int columnIdx, boolean allowEmpty) {

        if(checkEmptyCell(row, columnIdx, allowEmpty))
            return;

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.NUMERIC) {

            Double value = row.getCell(columnIdx).getNumericCellValue();
            return;
        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric value.");

    }

    public void validateNumericCell(Row row, int columnIdx) {
        validateNumericCell(row, columnIdx, false);
    }

    public void validateIntegerCell(Row row, int columnIdx) {

        checkEmptyCell(row, columnIdx);

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.NUMERIC) {

            double value = row.getCell(columnIdx).getNumericCellValue();

            if (value % 1 == 0) {
                return;
            }
        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected integer value.");

    }

    public void checkEmptyCell(Row row, int columnIdx) {
        checkEmptyCell(row, columnIdx, false);
    }

    public boolean checkEmptyCell(Row row, int columnIdx, boolean allowEmpty) {
        if (row.getCell(columnIdx) == null) {
            if (allowEmpty) {
                return true;
            }
            throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected value, but empty cell found.");
        }

        CellType cellType = row.getCell(columnIdx).getCellType();

        return cellType == CellType.BLANK && allowEmpty;
    }

    public void validateStringCell(Row row, int columnIdx, Boolean allowEmpty) {

        if(checkEmptyCell(row, columnIdx, allowEmpty))
            return;

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.STRING) {

            String value = row.getCell(columnIdx).getStringCellValue();
            return;
        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected string value.");

    }

    public void validateStringCell(Row row, int columnIdx) {
        validateStringCell(row, columnIdx, false);
    }

    public void validateIntegerConstraint(Row row, int columnIdx, int min, int max) {

        checkEmptyCell(row, columnIdx);

        CellType cellType = row.getCell(columnIdx).getCellType();

        if (cellType == CellType.NUMERIC) {
            double value = row.getCell(columnIdx).getNumericCellValue();

            if (value % 1 == 0 && value >= min && value <= max) {
                return;
            }
        }

        throw new ExcelValidationError("Unable to validate row " + (row.getRowNum() + 1) + " column " + toExcelLetter(columnIdx) + ": Expected numeric integer value within range: [" + min + " , " + max + "].");

    }


}
