package de.avatic.lcc.service.excelMapper;

import de.avatic.lcc.model.bulk.BulkInstruction;
import de.avatic.lcc.model.bulk.BulkInstructionType;
import de.avatic.lcc.model.bulk.header.MaterialHeader;
import de.avatic.lcc.model.db.materials.Material;
import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.util.exception.internalerror.ExcelValidationError;
import org.dhatim.fastexcel.Workbook;
import org.dhatim.fastexcel.Worksheet;
import org.dhatim.fastexcel.reader.ReadableWorkbook;
import org.dhatim.fastexcel.reader.Row;
import org.dhatim.fastexcel.reader.Sheet;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class MaterialFastExcelMapper {

    private final MaterialRepository materialRepository;

    public MaterialFastExcelMapper(MaterialRepository materialRepository) {
        this.materialRepository = materialRepository;
    }

    /**
     * Exports materials to Excel using FastExcel
     */
    public byte[] exportToExcel() throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        try (Workbook workbook = new Workbook(outputStream, "LCC", "1.0")) {
            Worksheet worksheet = workbook.newWorksheet("Materials");

            // Create header row
            createHeaderRow(worksheet);

            // Fill data rows
            List<Material> materials = materialRepository.listAllMaterials();
            int rowIndex = 1;
            for (Material material : materials) {
                mapToRow(material, worksheet, rowIndex);
                rowIndex++;
            }

            // Auto-size columns
            for (int i = 0; i < MaterialHeader.values().length; i++) {
                worksheet.width(i, 20);
            }

            workbook.finish();
        }

        return outputStream.toByteArray();
    }

    /**
     * Creates the header row with proper formatting matching Apache POI style
     */
    private void createHeaderRow(Worksheet worksheet) {
        int colIndex = 0;
        for (MaterialHeader header : MaterialHeader.values()) {
            worksheet.value(0, colIndex, header.name());

            // Apply styling to match HeaderCellStyleProvider
            worksheet.style(0, colIndex)
                    .bold()
                    .fontName("Arial")
                    .fontSize(10)
                    .fontColor("002F54")           // RGB(0, 47, 84) - Blue text
                    .fillColor("5AF0B4")           // RGB(90, 240, 180) - Green background
                    .horizontalAlignment("center")
                    .borderStyle("thin")           // All borders thin
                    .set();

            colIndex++;
        }
    }

    /**
     * Maps a Material entity to a row in the worksheet
     */
    private void mapToRow(Material material, Worksheet worksheet, int rowIndex) {
        worksheet.value(rowIndex, MaterialHeader.OPERATION.ordinal(), BulkInstructionType.UPDATE.name());
        worksheet.value(rowIndex, MaterialHeader.PART_NUMBER.ordinal(), material.getPartNumber());
        worksheet.value(rowIndex, MaterialHeader.DESCRIPTION.ordinal(), material.getName());
        worksheet.value(rowIndex, MaterialHeader.HS_CODE.ordinal(), material.getHsCode());
    }

    /**
     * Imports materials from Excel using FastExcel
     */
    public List<BulkInstruction<Material>> importFromExcel(byte[] fileData) throws IOException {
        List<BulkInstruction<Material>> materials = new ArrayList<>();

        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(fileData);
             ReadableWorkbook workbook = new ReadableWorkbook(inputStream)) {

            Sheet sheet = workbook.getFirstSheet();

            // Validate header
            validateHeader(sheet);

            // Process data rows (skip header row at index 0)
            List<Row> rows = sheet.read();
            for (int i = 1; i < rows.size(); i++) {
                Row row = rows.get(i);
                if (!isEmpty(row)) {
                    materials.add(mapToEntity(row, i + 1));
                }
            }
        }

        return materials;
    }

    /**
     * Validates that the Excel file has the correct header structure
     */
    private void validateHeader(Sheet sheet) throws IOException {
        List<Row> rows = sheet.read();
        if (rows.isEmpty()) {
            throw new ExcelValidationError("Excel file is empty");
        }

        Row headerRow = rows.get(0);
        List<String> expectedHeaders = Arrays.stream(MaterialHeader.values())
                .map(Enum::name)
                .collect(Collectors.toList());

        for (int i = 0; i < expectedHeaders.size(); i++) {
            String cellValue = headerRow.getCellAsString(i).orElse("");
            if (!expectedHeaders.get(i).equals(cellValue)) {
                throw new ExcelValidationError(
                        String.format("Invalid header at column %d. Expected '%s' but found '%s'",
                                i, expectedHeaders.get(i), cellValue)
                );
            }
        }
    }

    /**
     * Checks if a row is empty
     */
    private boolean isEmpty(Row row) {
        for (int i = 0; i < MaterialHeader.values().length; i++) {
            if (row.getCellAsString(i).isPresent() && !row.getCellAsString(i).get().trim().isEmpty()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Maps a row from Excel to a BulkInstruction<Material>
     */
    private BulkInstruction<Material> mapToEntity(Row row, int rowNumber) {
        Material entity = new Material();

        try {
            // Extract and validate data
            String partNumber = getCellValue(row, MaterialHeader.PART_NUMBER.ordinal(), rowNumber);
            String description = getCellValue(row, MaterialHeader.DESCRIPTION.ordinal(), rowNumber);
            String hsCode = getCellValueAllowEmpty(row, MaterialHeader.HS_CODE.ordinal(), rowNumber);
            String operation = getCellValue(row, MaterialHeader.OPERATION.ordinal(), rowNumber);

            // Validate lengths
            validateLength(partNumber, 0, 12, "Part Number", rowNumber);
            if (hsCode != null)
                validateLength(hsCode, 0, 11, "HS Code", rowNumber);
            validateLength(description, 1, 500, "Description", rowNumber);

            // Validate operation enum
            BulkInstructionType instructionType;
            try {
                instructionType = BulkInstructionType.valueOf(operation);
            } catch (IllegalArgumentException e) {
                throw new ExcelValidationError(
                        String.format("Invalid operation '%s' at row %d. Must be one of: %s",
                                operation, rowNumber, Arrays.toString(BulkInstructionType.values()))
                );
            }

            // Set entity properties
            entity.setPartNumber(partNumber);
            entity.setName(description);
            entity.setHsCode(hsCode);
            entity.setNormalizedPartNumber(normalizePartNumber(partNumber));
            entity.setDeprecated(false);

            // Validate HS Code
            if (!validateHsCode(entity.getHsCode())) {
                throw new ExcelValidationError(
                        String.format("Invalid HS Code '%s' at row %d", hsCode, rowNumber)
                );
            }

            return new BulkInstruction<>(entity, instructionType);

        } catch (ExcelValidationError e) {
            throw e;
        } catch (Exception e) {
            throw new ExcelValidationError(
                    String.format("Error processing row %d: %s", rowNumber, e.getMessage())
            );
        }
    }

    /**
     * Gets a cell value as string with proper error handling
     */
    private String getCellValueAllowEmpty(Row row, int columnIndex, int rowNumber) {
        return row.getCellAsString(columnIndex).orElse(null);
    }

    /**
     * Gets a cell value as string with proper error handling
     */
    private String getCellValue(Row row, int columnIndex, int rowNumber) {
        return row.getCellAsString(columnIndex)
                .orElseThrow(() -> new ExcelValidationError(
                        String.format("Missing value at row %d, column %d", rowNumber, columnIndex)
                ));
    }

    /**
     * Validates the length of a field
     */
    private void validateLength(String value, int minLength, int maxLength, String fieldName, int rowNumber) {
        if (value.length() < minLength || value.length() > maxLength) {
            throw new ExcelValidationError(
                    String.format("%s at row %d must be between %d and %d characters (found %d)",
                            fieldName, rowNumber, minLength, maxLength, value.length())
            );
        }
    }

    /**
     * Normalizes part number by padding with zeros
     */
    private String normalizePartNumber(String partNumber) {
        if (partNumber.length() > 12) {
            throw new IllegalArgumentException("Part number must be less than 12 characters");
        }
        return "000000000000".concat(partNumber).substring(partNumber.length());
    }

    /**
     * Validates HS Code (placeholder for API validation)
     */
    private boolean validateHsCode(String hsCode) {
        return hsCode == null || (hsCode.length() >= 8 && hsCode.length() <= 12 && hsCode.matches("[0-9]+"));
    }
}