package com.yc.ship.module.trade.utils.excel;


import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.yc.ship.module.trade.controller.admin.order.vo.order.TouristExportVisitorVO;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ExcelStyleHandler implements CellWriteHandler {
    private int templateRowIndex;

    private final Map<Integer, CellStyle> templateStyles = new HashMap<>();

    // 保存模板行的合并信息：列索引 -> [起始列, 结束列]
    private final Map<Integer, int[]> templateMergeInfo = new HashMap<>();

    private boolean templateProcessed = false;

    /** 游客列表数据 */
    private List<TouristExportVisitorVO> visitorList;

    /** 数据起始行索引 */
    private static final int DATA_START_ROW = 8;

    /** 记录已处理的单元格数量 */
    private int processedCellCount = 0;

    /** 总数据行数 */
    private int totalRows = 0;

    private int fileType;

    /** 每行数据列数（根据实际Excel模板列数调整） */
    private static final int TOTAL_COLUMNS = 18;

    private static final int TOTAL_COLUMNS2 = 16;
    /** 是否已经执行过合并 */
    private boolean mergeCompleted = false;

    /** 预计算的需要清空的单元格集合，key格式为 "相对行索引_列索引" */
    private Set<String> blankCellSet;

    public ExcelStyleHandler(int templateRowIndex) {
        this.templateRowIndex = templateRowIndex;
    }

    public ExcelStyleHandler(List<TouristExportVisitorVO> visitorList, int fileType) {
        this.visitorList = visitorList;
        this.fileType = fileType;
        this.totalRows = visitorList != null ? visitorList.size() : 0;
        // 预计算需要清空的单元格（合并区域中非首行的单元格）
        this.blankCellSet = computeBlankCells();
    }


    public void afterCellDispose1(CellWriteHandlerContext context) {
        if (Boolean.TRUE.equals(context.getHead())) {
            return;
        }

        Cell cell = context.getCell();
        Row currentRow = cell.getRow();
        int currentRowIndex = currentRow.getRowNum();
        int colIndex = cell.getColumnIndex();
        Sheet sheet = context.getWriteSheetHolder().getSheet();
        Workbook workbook = sheet.getWorkbook();

        // 处理模板行：保存样式和合并信息
        if (currentRowIndex == templateRowIndex) {
            if (!templateProcessed) {
                // 保存样式
                saveTemplateStyles(currentRow, workbook);

                // 保存合并信息
                saveTemplateMergeInfo(sheet);

                templateProcessed = true;
            }
            return;
        }

        // 跳过模板行之前的行
        if (currentRowIndex < templateRowIndex) {
            return;
        }

        // ===== 为所有单元格设置样式（包括合并区域内的）=====
       // CellStyle newStyle = workbook.createCellStyle();

// 应用样式
        if (templateStyles.containsKey(colIndex)) {
            //newStyle.cloneStyleFrom(templateStyles.get(colIndex));

            cell.setCellStyle(templateStyles.get(colIndex));
        }


        // 如果这个列在合并区域内，设置边框
        /*if (templateMergeInfo.containsKey(colIndex)) {
            int[] range = templateMergeInfo.get(colIndex);
            int firstCol = range[0];
            int lastCol = range[1];

            // 根据在合并区域中的位置设置边框
            setMergeCellBorders(newStyle, colIndex, firstCol, lastCol);
        }*/

       // cell.setCellStyle(newStyle);

        // 应用合并（只处理合并区域的第一个列）
        if (templateMergeInfo.containsKey(colIndex)) {
            int[] range = templateMergeInfo.get(colIndex);
            int firstCol = range[0];
            int lastCol = range[1];

            // 只在当前列是合并区域的第一个列时创建合并
            if (colIndex == firstCol) {
                CellRangeAddress cra = new CellRangeAddress(
                        currentRowIndex, currentRowIndex, firstCol, lastCol
                );
                sheet.addMergedRegion(cra);

                // 为合并区域设置完整边框
                setMergeCellBorders(sheet.getColumnStyle(colIndex), colIndex, firstCol, lastCol);

            }

        }




    }


    /**
     * 为合并区域的单元格设置边框
     * @param style 单元格样式
     * @param colIndex 当前列索引
     * @param firstCol 合并区域起始列
     * @param lastCol 合并区域结束列
     */
    private void setMergeCellBorders(CellStyle style,  int colIndex, int firstCol, int lastCol) {
        // 设置所有边框为细线
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);

        // 如果有特殊需求，可以根据位置设置不同边框：
        // - 合并区域的第一个单元格：左、上、下边框
        // - 合并区域的最后一个单元格：右、上、下边框
        // - 中间的单元格：上、下边框
    }

    /**
     * 保存模板行的合并信息
     */
    private void saveTemplateMergeInfo(Sheet sheet) {
        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
        for (CellRangeAddress cra : mergedRegions) {
            // 只处理模板行上的横向合并
            if (cra.getFirstRow() == templateRowIndex && cra.getLastRow() == templateRowIndex) {
                int firstCol = cra.getFirstColumn();
                int lastCol = cra.getLastColumn();

                // 记录这个合并区域涉及的所有列
                for (int col = firstCol; col <= lastCol; col++) {
                    templateMergeInfo.put(col, new int[]{firstCol, lastCol});
                }
            }
        }
    }

    private void saveTemplateStyles(Row templateRow, Workbook workbook) {
        for (Cell cell : templateRow) {
            if (cell != null && cell.getCellStyle() != null) {
                CellStyle newStyle = workbook.createCellStyle();
                newStyle.cloneStyleFrom(cell.getCellStyle());
                templateStyles.put(cell.getColumnIndex(), newStyle);
            }
        }
    }

    /**
     * 单元格写入完成后执行合并
     * 此方法在每个单元格写入完成后检查是否是最后一个单元格，如果是则统一处理合并
     *
     * @param context 单元格写入上下文
     */
    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        // 只处理游客列表场景
        if (visitorList == null || visitorList.isEmpty()) {
            return;
        }

        // 跳过表头行
        if (Boolean.TRUE.equals(context.getHead())) {
            return;
        }

        // 在单元格写入后，立即清空需要合并的非首行单元格的值
        Cell cell = context.getCell();
        int currentRowIndex = cell.getRow().getRowNum();
        int colIndex = cell.getColumnIndex();
        int relativeRow = currentRowIndex - DATA_START_ROW;
        if (relativeRow >= 0 && blankCellSet != null
                && blankCellSet.contains(relativeRow + "_" + colIndex)) {
            cell.setCellValue("");
        }

        // 统计处理的单元格数量
        processedCellCount++;

        int totalCells = 0;
        // 当最后一个单元格写入完成后，执行合并
        if(fileType == 1) {
             totalCells = totalRows * TOTAL_COLUMNS;

        } else {
             totalCells = totalRows * TOTAL_COLUMNS2;
        }
        if (!mergeCompleted && processedCellCount >= totalCells) {
            mergeCompleted = true;
            performMerge(context.getWriteSheetHolder().getSheet());
        }
    }


    /**
     * 优化后：预计算合并区间，批量添加
     * 时间复杂度：O(n × k)，相比原来的 O(n² × k × m) 有数量级提升
     */
    private void performMerge(Sheet sheet) {
        if (visitorList == null || visitorList.size() <= 1) return;

        int n = visitorList.size();

        // 按列收集需要合并的区间：列索引 -> List<起始行(相对), 结束行(相对)>
        Map<Integer, List<int[]>> mergeRanges = new HashMap<>();

        // 预定义每种类型需要合并的列
        List<Integer> sourceCols = Collections.singletonList(0);           // 代理商：第0列
        List<Integer> orderCols = Arrays.asList(1, 2, 3, 4, 5, 6,       // 订单信息：第1-7列
                        fileType == 1 ? 7 : -1, fileType == 1 ? 8 : -1).stream()
                .filter(c -> c >= 0).collect(Collectors.toList());
        List<Integer> roomCols = (fileType == 1
                ? Arrays.asList( 9, 10, 11)                                // 房间信息(fileType=1)：第8-10列
                : Arrays.asList(7, 8, 9));                               // 房间信息(fileType≠1)：第7-9列

        for (int col : sourceCols) collectMergeRanges(mergeRanges, col, n,
                i -> safeString(visitorList.get(i).getSourceName()));
        for (int col : orderCols) collectMergeRanges(mergeRanges, col, n,
                i -> safeString(visitorList.get(i).getOrderNo()));
        for (int col : roomCols) collectMergeRanges(mergeRanges, col, n,
                i -> safeString(visitorList.get(i).getRoomIndexId()) + "_" + safeString(visitorList.get(i).getOrderNo()));

        // 批量添加所有合并区域
        for (Map.Entry<Integer, List<int[]>> entry : mergeRanges.entrySet()) {
            int col = entry.getKey();
            for (int[] range : entry.getValue()) {
                sheet.addMergedRegion(new CellRangeAddress(
                        DATA_START_ROW + range[0], DATA_START_ROW + range[1], col, col));
            }
        }
    }

    /** 收集某一列的所有连续相同值区间 */
    private void collectMergeRanges(Map<Integer, List<int[]>> mergeRanges, int col, int n,
                                    Function<Integer, String> valueGetter) {
        List<int[]> ranges = new ArrayList<>();
        int start = 0;
        String prevValue = valueGetter.apply(0);

        for (int i = 1; i < n; i++) {
            String currValue = valueGetter.apply(i);
            if (!currValue.equals(prevValue)) {
                if (start < i - 1) { // 连续多行相同才需要合并
                    ranges.add(new int[]{start, i - 1});
                }
                start = i;
                prevValue = currValue;
            }
        }
        // 处理最后一组
        if (start < n - 1) {
            ranges.add(new int[]{start, n - 1});
        }

        if (!ranges.isEmpty()) {
            mergeRanges.put(col, ranges);
        }
    }

    private String safeString(String s) {
        return s != null ? s : "";
    }

    /**
     * 预计算合并区域中需要清空的单元格
     * 合并后只有首行需要保留数据，其余行设为空字符串
     * @return 需要清空的单元格集合，key格式为 "相对行索引_列索引"
     */
    private Set<String> computeBlankCells() {
        Set<String> blankSet = new HashSet<>();
        if (visitorList == null || visitorList.size() <= 1) {
            return blankSet;
        }

        int n = visitorList.size();
        Map<Integer, List<int[]>> mergeRanges = new HashMap<>();

        // 预定义每种类型需要合并的列（与 performMerge 中保持一致）
        List<Integer> sourceCols = Collections.singletonList(0);
        List<Integer> orderCols = Arrays.asList(1, 2, 3, 4, 5, 6,
                        fileType == 1 ? 7 : -1, fileType == 1 ? 8 : -1).stream()
                .filter(c -> c >= 0).collect(Collectors.toList());
        List<Integer> roomCols = (fileType == 1
                ? Arrays.asList(9, 10, 11)
                : Arrays.asList(7, 8, 9));

        for (int col : sourceCols) collectMergeRanges(mergeRanges, col, n,
                i -> safeString(visitorList.get(i).getSourceName()));
        for (int col : orderCols) collectMergeRanges(mergeRanges, col, n,
                i -> safeString(visitorList.get(i).getOrderNo()));
        for (int col : roomCols) collectMergeRanges(mergeRanges, col, n,
                i -> safeString(visitorList.get(i).getRoomIndexId()) + "_" + safeString(visitorList.get(i).getOrderNo()));

        // 将合并区域中非首行的单元格加入清空集合
        for (Map.Entry<Integer, List<int[]>> entry : mergeRanges.entrySet()) {
            int col = entry.getKey();
            for (int[] range : entry.getValue()) {
                // range[0]+1 到 range[1] 是非首行，需要清空
                for (int rowIdx = range[0] + 1; rowIdx <= range[1]; rowIdx++) {
                    blankSet.add(rowIdx + "_" + col);
                }
            }
        }

        return blankSet;
    }


    /**
     * 废弃
     * 执行合并操作
     * 此方法在数据写入完成后统一处理合并，避免逐行合并时的磁盘刷新问题
     *
     * 合并规则：
     * 1. 第0列（代理商）：按 sourceName 合并
     * 2. 第1-7列（订单信息）：按 orderNo 合并
     * 3. 第8-10列（房间信息）：按 roomIndexId 合并（同一订单内）
     * 4. 横向不需要合并（按列单独合并）
     *
     * @param sheet Excel工作表
     * TODO 性能低下
     */
    private void performMerge1(Sheet sheet) {
        // 遍历游客列表，从第二行开始与上一行比较
        for (int i = 1; i < visitorList.size(); i++) {
            TouristExportVisitorVO currentVisitor = visitorList.get(i);
            TouristExportVisitorVO preVisitor = visitorList.get(i - 1);

            int currentRowIndex = DATA_START_ROW + i;
            int preRowIndex = DATA_START_ROW + i - 1;

            // ===== 第0列（代理商）：按 sourceName 合并 =====
            if (currentVisitor.getSourceName() != null && preVisitor.getSourceName() != null
                    && currentVisitor.getSourceName().equals(preVisitor.getSourceName())) {
                mergeCell(sheet, currentRowIndex, preRowIndex, 1, 1);
            }

            // ===== 第1-7列（订单信息）：按 orderNo 合并 =====
            // 同一订单的所有游客合并显示订单信息
            if (currentVisitor.getOrderNo() != null
                    && currentVisitor.getOrderNo().equals(preVisitor.getOrderNo())) {
                // 订单号列（第1列）
                mergeCell(sheet, currentRowIndex, preRowIndex, 2, 2);
                // 团号列（第2列）
                mergeCell(sheet, currentRowIndex, preRowIndex, 3, 3);
                // 航向列（第3列）
                mergeCell(sheet, currentRowIndex, preRowIndex, 4, 4);
                // 航期列（第4列）
                mergeCell(sheet, currentRowIndex, preRowIndex, 5, 5);
                // 应收款列（第5列）
                mergeCell(sheet, currentRowIndex, preRowIndex, 6, 6);
                if (fileType == 1) {
                    // 实收款列（第6列）
                    mergeCell(sheet, currentRowIndex, preRowIndex, 7, 7);
                    // 定金列（第7列）
                    mergeCell(sheet, currentRowIndex, preRowIndex, 8, 8);
                }
            }

            // ===== 第8-10列（房间信息）：按 roomIndexId 合并（同一订单内） =====
            String currentRoomIndex = currentVisitor.getRoomIndexId() != null ? currentVisitor.getRoomIndexId() : "";
            String preRoomIndex = preVisitor.getRoomIndexId() != null ? preVisitor.getRoomIndexId() : "";
            if (currentRoomIndex.equals(preRoomIndex)
                    && currentVisitor.getOrderNo() != null
                    && currentVisitor.getOrderNo().equals(preVisitor.getOrderNo())) {
                if (fileType == 1) {
                    // 序号列（第8列）
                    mergeCell(sheet, currentRowIndex, preRowIndex, 9, 9);
                    // 房型列（第9列）
                    mergeCell(sheet, currentRowIndex, preRowIndex, 10, 10);
                    // 入住类型列（第10列）
                    mergeCell(sheet, currentRowIndex, preRowIndex, 11, 11);
                } else {
                    mergeCell(sheet, currentRowIndex, preRowIndex, 7, 7);
                    // 房型列（第9列）
                    mergeCell(sheet, currentRowIndex, preRowIndex, 8, 8);
                    // 入住类型列（第10列）
                    mergeCell(sheet, currentRowIndex, preRowIndex, 9, 9);
                }
            }
        }
    }

    /**
     * 合并单元格并设置边框
     *
     * 功能说明：
     * - 如果目标单元格已存在合并区域，则扩展合并区域
     * - 如果目标单元格未合并，则创建新的合并区域
     * - 设置合并单元格的边框样式
     *
     * @param sheet Excel工作表
     * @param currentRow 当前行索引
     * @param preRow 上一行索引
     * @param startCol 起始列索引
     * @param endCol 结束列索引
     */
    private void mergeCell(Sheet sheet, int currentRow, int preRow, int startCol, int endCol) {
        List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();

        // 检查上一行是否已存在合并区域
        for (int i = 0; i < mergedRegions.size(); i++) {
            CellRangeAddress region = mergedRegions.get(i);
            // 如果合并区域包含上一行的指定列
            if (region.getFirstRow() <= preRow && region.getLastRow() >= preRow
                    && region.getFirstColumn() == startCol && region.getLastColumn() == endCol) {
                // 扩展合并区域
                sheet.removeMergedRegion(i);
                CellRangeAddress newRegion = new CellRangeAddress(region.getFirstRow(), currentRow, startCol, endCol);
                sheet.addMergedRegion(newRegion);
                // setBorder(newRegion, sheet);
                return;
            }
        }

        // 如果没有找到已存在的合并区域，创建新的合并区域
        CellRangeAddress newRegion = new CellRangeAddress(preRow, currentRow, startCol, endCol);
        sheet.addMergedRegion(newRegion);
        // setBorder(newRegion, sheet);
    }

    /**
     * 设置合并单元格边框
     *
     * 功能说明：
     * 为合并区域设置细边框样式，使合并后的单元格显示边框
     *
     * @param cra 合并区域对象
     * @param sheet Excel工作表
     */
    private void setBorder(CellRangeAddress cra, Sheet sheet) {
        RegionUtil.setBorderBottom(BorderStyle.THIN, cra, sheet);
        RegionUtil.setBorderLeft(BorderStyle.THIN, cra, sheet);
        RegionUtil.setBorderRight(BorderStyle.THIN, cra, sheet);
        RegionUtil.setBorderTop(BorderStyle.THIN, cra, sheet);
    }
}
