package ru.nbch.indicsearchservice.exporter;

import jakarta.annotation.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.apache.commons.lang3.mutable.MutableLong;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import ru.nbch.indicsearchservice.config.settings.ApplicationSettings;

@Component
public class ExcelExporter implements Exporter {
    private static final Logger log = LoggerFactory.getLogger(ExcelExporter.class);
    private static final Long EXCEL_SHEET_MAX_SIZE = 1_048_575L;

    private final ApplicationSettings settings;

    public ExcelExporter(ApplicationSettings settings) {
        this.settings = settings;
    }

    public void writeExcelFile(@Nullable Path aggregatedFile, UUID taskId, Long totalRows) {
        if (aggregatedFile == null) {
            return;
        }

        Path excelFile = settings.getStorageDirectory().toPath().resolve(taskId.toString() + ".xlsx");

        try (OutputStream os = Files.newOutputStream(excelFile);
             BufferedReader aggregatedFileReader = Files.newBufferedReader(aggregatedFile);
             SXSSFWorkbook workbook = new SXSSFWorkbook()) {
            CellStyle headerCellStyle = createHeaderCellStyle(workbook);
            CellStyle cellStyle = createCellStyle(workbook);

            Iterator<String> iterator = aggregatedFileReader.lines().iterator();

            MutableLong sheetRowCounter = new MutableLong(0L);
            MutableLong sheetTableCounter = new MutableLong(1L);

            long count = 0L;

            boolean isFirst = true;
            String lastTableName;
            List<String> rows = new ArrayList<>();
            while (iterator.hasNext()) {
                String line = iterator.next();
                List<String> lineList = Arrays.asList(line.split("%%%"));

                log.info("Processing row: {} / {}", count++, totalRows);

                if (!isFirst && (FIRST_LEVEL_TABLES.contains(lineList.getFirst()) || line.equals("EOF"))) {
                    lastTableName = rows.getFirst().split("%%%")[0];
                    writeToExcel(workbook, rows, sheetRowCounter, sheetTableCounter, headerCellStyle, cellStyle);
                    if (lastTableName != null && !lastTableName.equals(lineList.getFirst())) {
                        sheetRowCounter.setValue(0L);
                        sheetTableCounter.setValue(1L);
                    }
                    rows.clear();
                }

                rows.add(line);
                isFirst = false;
            }

            workbook.write(os);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    private void writeToExcel(Workbook workbook,
                              List<String> rows,
                              MutableLong sheetRowCounter,
                              MutableLong sheetTableCounter,
                              CellStyle headerCellStyle,
                              CellStyle cellStyle) {
        if (rows == null || rows.isEmpty()) {
            return;
        }

        String parentTableName = rows.getFirst().split("%%%")[0];

        Sheet tableSheet;
        int rowsApproximateSize = rows.stream().mapToInt(r -> r.split("%%%", -1).length).sum();
        if (sheetRowCounter.longValue() + (rowsApproximateSize * 2L) >= EXCEL_SHEET_MAX_SIZE) {
            tableSheet = createSheet(workbook, parentTableName.toUpperCase() + "_" + sheetTableCounter.incrementAndGet());
            sheetRowCounter.setValue(0L);
        } else {
            tableSheet = createSheet(workbook, parentTableName.toUpperCase());
        }

        for (String row : rows) {
            List<String> rowList = Arrays.asList(row.split("%%%", -1));
            String tableName = rowList.getFirst();

            List<String> rowSublist = rowList.subList(1, rowList.size());
            Map<String, String> rowValues = new LinkedHashMap<>();
            for (int i = 0; i + 1 <= rowSublist.size(); i = i + 2) {
                try {
                    rowValues.put(rowSublist.get(i), rowSublist.get(i + 1));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            createHeaderRow(
                tableName,
                tableSheet,
                sheetRowCounter,
                headerCellStyle
            );

            for (Map.Entry<String, String> entry : rowValues.entrySet()) {
                String columnName = entry.getKey();
                String columnValue = entry.getValue();

                writeFieldDataIntoSheetRow(
                    columnName,
                    columnName,
                    columnValue,
                    tableSheet,
                    sheetRowCounter,
                    cellStyle
                );
            }

            sheetRowCounter.increment();
        }
    }

    private CellStyle createHeaderCellStyle(Workbook workbook) {
        CellStyle headerCellStyle = workbook.createCellStyle();
        headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        XSSFFont font = (XSSFFont) workbook.createFont();
        font.setFontName("SansSerif");
        font.setFontHeightInPoints((short) 11);
        font.setBold(true);
        headerCellStyle.setFont(font);

        return headerCellStyle;
    }

    private CellStyle createCellStyle(Workbook workbook) {
        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setBorderRight(BorderStyle.THIN);

        XSSFFont font = (XSSFFont) workbook.createFont();
        font.setFontName("Calibri");
        font.setFontHeightInPoints((short) 11);
        cellStyle.setFont(font);

        return cellStyle;
    }

    private Sheet createSheet(Workbook workbook, String sheetName) {
        Sheet foundSheet = workbook.getSheet(sheetName);

        if (foundSheet == null) {
            Sheet sheet = workbook.createSheet(sheetName);
            sheet.setColumnWidth(0, 25 * 256);
            sheet.setColumnWidth(1, 115 * 256);
            sheet.setColumnWidth(2, 38 * 256);

            return sheet;
        }

        return foundSheet;
    }

    private void createHeaderRow(String localizedTableName,
                                 Sheet sheet,
                                 MutableLong currentRow,
                                 CellStyle headerCellStyle) {
        Row headerRow = sheet.createRow(currentRow.intValue());
        headerRow.setHeightInPoints(15);

        Cell headerCell = headerRow.createCell(0);
        headerCell.setCellValue(localizedTableName);
        headerCell.setCellStyle(headerCellStyle);
        sheet.addMergedRegion(new CellRangeAddress(currentRow.intValue(), currentRow.intValue(), 0, 2));

        currentRow.increment();
    }

    private void writeFieldDataIntoSheetRow(String fieldKey,
                                            String localizedFieldKey,
                                            @Nullable Object fieldValue,
                                            Sheet sheet,
                                            MutableLong currentRow,
                                            CellStyle cellStyle) {
        Row dataRow = sheet.createRow(currentRow.intValue());
        dataRow.setHeightInPoints(15);

        Cell dataFieldCell = dataRow.createCell(0);
        dataFieldCell.setCellValue(fieldKey.toUpperCase());
        dataFieldCell.setCellStyle(cellStyle);

        Cell descriptionCell = dataRow.createCell(1);
        descriptionCell.setCellValue(localizedFieldKey);
        descriptionCell.setCellStyle(cellStyle);

        Cell dataValueCell = dataRow.createCell(2);
        dataValueCell.setCellValue(Optional.ofNullable(fieldValue).orElse("").toString());
        dataValueCell.setCellStyle(cellStyle);

        currentRow.increment();
    }
}
