package cn.iocoder.yudao.framework.excel.core.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import cn.iocoder.yudao.framework.excel.core.handler.CellTextWrapHandler;
import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
import cn.iocoder.yudao.framework.excel.core.handler.SetColumnWidthHandler;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.hssf.usermodel.DVConstraint;
import org.apache.poi.hssf.usermodel.HSSFDataValidation;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

/**
 * Excel 工具类
 *
 * @author 芋道源码
 */
public class ExcelUtils {

    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param <T>       泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data) throws IOException {
        // 输出 Excel
        EasyExcel.write(response.getOutputStream(), head)
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度，自动适配。最大 255 宽度
                .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
                .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
                .sheet(sheetName).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param <T>       泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data, Map<Integer, Integer> columnWidthMap) throws IOException {
        // 输出 Excel
        EasyExcel.write(response.getOutputStream(), head)
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
//                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度，自动适配。最大 255 宽度
                .registerWriteHandler(new SetColumnWidthHandler(columnWidthMap)) // 自定义列宽
                .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
                .registerWriteHandler(new CellTextWrapHandler())  // 注册单元格文字换行处理器
                .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
                .sheet(sheetName).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        return EasyExcel.read(file.getInputStream(), head, null)
                .autoCloseStream(false)  // 不要自动关闭，交给 Servlet 自己处理
                .doReadAllSync();
    }

    /**
     * 导出空白模板Excel，并为指定列添加下拉框和日期时间格式验证，同时支持自定义示例数据。
     *
     * @param response         HttpServletResponse，用于将生成的Excel文件写入响应流。
     * @param fileName         导出的Excel文件名。
     * @param sheetName        工作表名称。
     * @param headerArray      表头字段数组（每个元素代表一列的表头名称）。
     * @param dropdownColumns  带下拉框的列及选项（列索引 -> 下拉框选项数组）。
     * @param isMultiSelect    是否允许多选的下拉框选项。
     * @param exampleDataList  自定义的示例数据（每个元素是一个包含多列数据的List）。
     * @param textColumns      需要设置为文本格式的列索引列表。
     * @throws IOException     如果在写入文件或操作Excel时发生错误。
     */
    public static void exportBlankTemplate(HttpServletResponse response, String fileName, String sheetName,
                                           String[] headerArray,  // 接收一个字符串数组作为表头
                                           Map<Integer, String[]> dropdownColumns,
                                           boolean isMultiSelect,
                                           List<List<String>> exampleDataList,
                                           List<Integer> textColumns) throws IOException {

        // 创建工作簿对象，支持xlsx格式
        Workbook workbook = new XSSFWorkbook();
        // 创建一个工作表
        Sheet sheet = workbook.createSheet(sheetName);

        // 创建表头
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < headerArray.length; i++) {
            // 根据传入的表头数组动态创建每一列的表头
            headerRow.createCell(i).setCellValue(headerArray[i]);  // 使用数组来填充表头
        }



        if (textColumns != null) {
            // 创建一个 CellStyle 对象，用于设置文本格式
            CellStyle textCellStyle = workbook.createCellStyle();
            DataFormat dataFormat = workbook.createDataFormat();
            textCellStyle.setDataFormat(dataFormat.getFormat("@"));  // "@" 表示文本格式
            // 为每个需要文本格式的列设置整个列的文本格式
            for (Integer columnIndex : textColumns) {
                // 调用辅助方法，设置特定列的单元格为文本格式
                setColumnTextFormat(sheet, columnIndex, textCellStyle);
            }
        }

        if (exampleDataList != null) {
            // 添加多行自定义示例数据
            for (int i = 0; i < exampleDataList.size(); i++) {
                // 获取每一行的示例数据
                List<String> exampleData = exampleDataList.get(i);
                // 在表格中创建新的一行
                Row exampleRow = sheet.createRow(i + 1);  // 示例数据从第二行开始
                for (int j = 0; j < exampleData.size(); j++) {
                    // 在每一列添加数据
                    Cell cell = exampleRow.createCell(j);
                    cell.setCellValue(exampleData.get(j));
                }
            }
        }

        // 添加下拉框到指定列
        if (dropdownColumns != null) {
            // 遍历所有需要添加下拉框的列
            for (Map.Entry<Integer, String[]> entry : dropdownColumns.entrySet()) {
                // 调用辅助方法添加下拉框
                addDropdownValidation(sheet, entry.getKey(), entry.getValue(), isMultiSelect);
            }
        }

        // 设置响应头，指示浏览器下载文件
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + ".xlsx");

        // 将生成的 Excel 文件写入到输出流
        try (ServletOutputStream out = response.getOutputStream()) {
            workbook.write(out);  // 写入文件
            out.flush();  // 刷新输出流
        } finally {
            // 关闭工作簿对象，释放资源
            workbook.close();
        }
    }


    // 修改下拉框验证方法，支持多选功能
    private static void addDropdownValidation(Sheet sheet, int column, String[] options, boolean isMultiSelect) {
        DataValidationHelper validationHelper = sheet.getDataValidationHelper();
        DataValidationConstraint constraint;

        // 判断是否启用多选下拉框
        if (isMultiSelect) {
            // 如果是多选，则使用逗号分隔的方式，构建一个包含所有选项的下拉框
            String combinedOptions = String.join(",", options);
            constraint = validationHelper.createExplicitListConstraint(new String[]{combinedOptions});
        } else {
            // 否则使用单选下拉框
            constraint = validationHelper.createExplicitListConstraint(options);
        }

        // 设置下拉框范围（从第2行开始到第65535行，第column列）
        CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, column, column);
        DataValidation validation = validationHelper.createValidation(constraint, addressList);
        sheet.addValidationData(validation);
    }

    /**
     * 为指定列设置整个列的文本格式
     */
    private static void setColumnTextFormat(Sheet sheet, int columnIndex, CellStyle textCellStyle) {
        int rowCount = 1000; // 获取当前表格中的总行数
        for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
            Row row = sheet.getRow(rowIndex);
            if (row == null) {
                row = sheet.createRow(rowIndex);  // 如果该行不存在，则创建它
            }
            Cell cell = row.getCell(columnIndex);
            if (cell == null) {
                cell = row.createCell(columnIndex);  // 如果该单元格不存在，则创建它
            }
            cell.setCellStyle(textCellStyle); // 设置为文本格式
        }
    }

        /**
         * 通用 Excel 导出方法
         *
         * @param title       Excel标题
         * @param rows        数据行
         * @param mergeCols   需要增强或特殊处理的列索引（可选）
         * @param response    HttpServletResponse
         * @throws IOException
         */
        public static void exportExcel(String title, List<List<String>> rows, List<Integer> mergeCols, HttpServletResponse response) throws IOException {
            if (CollUtil.isEmpty(rows)) {
                throw new IllegalArgumentException("导出数据为空！");
            }

            // 创建 ExcelWriter
            ExcelWriter writer = ExcelUtil.getWriter();

            // ====== 合并单元格写标题 ======
            writer.merge(rows.get(0).size() - 1, title); // 根据第一行列数合并单元格
            writer.setRowHeight(0, 30); // 设置标题行高

            // 写入数据，true 表示包含表头
            writer.write(rows, true);

            // 如果有需要增强的列
            if (mergeCols != null && !mergeCols.isEmpty()) {
                ExcelExtraHelper.enhanceExcel(writer, rows, mergeCols, null);
            }

            // 设置响应头
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(title + ".xls", "UTF-8"));

            try (ServletOutputStream out = response.getOutputStream()) {
                writer.flush(out, true);
            } finally {
                writer.close(); // 释放资源
            }

    }


}
