package com.czg.excel;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.czg.exception.CzgException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;

/**
 * EasyExcel导出工具类
 *
 * @author yjjie
 * @date 2026/1/28 10:48
 */
@Slf4j
public class ExcelExportUtil {

    private static final ExcelExportConfig DEFAULT_CONFIG = new ExcelExportConfig();

    public static OnceAbsoluteMergeStrategy createMergeStrategy(int firstRow, int lastRow, int firstCol, int lastCol) {
        return new OnceAbsoluteMergeStrategy(firstRow, lastRow, firstCol, lastCol);
    }

    /**
     * 导出Excel到HttpServletResponse
     *
     * @param data     数据列表
     * @param clazz    数据类型
     * @param fileName 文件名（不含扩展名）
     * @param response HttpServletResponse
     * @param <T>      数据类型
     */
    public static <T> void exportToResponse(List<T> data, Class<T> clazz,
                                            String fileName, HttpServletResponse response) {
        exportToResponse(data, clazz, fileName, DEFAULT_CONFIG, response);
    }

    /**
     * 导出Excel到HttpServletResponse（自定义配置）
     *
     * @param data     数据列表
     * @param clazz    数据类型
     * @param fileName 文件名（不含扩展名）
     * @param config   配置信息
     * @param response HttpServletResponse
     * @param <T>      数据类型
     */
    public static <T> void exportToResponse(List<T> data, Class<T> clazz,
                                            String fileName, ExcelExportConfig config,
                                            HttpServletResponse response) {
        if (data == null) {
            data = Collections.emptyList();
        }

        setResponseHeader(response, fileName, config);

        try (OutputStream outputStream = response.getOutputStream()) {
            ExcelWriter excelWriter = EasyExcel.write(outputStream, clazz)
                    .autoCloseStream(config.isAutoCloseStream())
                    .build();

            WriteSheet writeSheet = EasyExcel.writerSheet(config.getDefaultSheetName()).build();
            excelWriter.write(data, writeSheet);
            excelWriter.finish();

            log.info("Excel导出成功，文件名：{}，数据量：{}", fileName, data.size());
        } catch (IOException e) {
            log.error("Excel导出失败", e);
            throw new CzgException("Excel导出失败", e);
        }
    }

    /**
     * 导出Excel到文件
     *
     * @param data     数据列表
     * @param clazz    数据类型
     * @param filePath 文件路径
     * @param <T>      数据类型
     */
    public static <T> void exportToFile(List<T> data, Class<T> clazz, String filePath) {
        exportToFile(data, clazz, filePath, DEFAULT_CONFIG);
    }

    /**
     * 导出Excel到文件（自定义配置）
     *
     * @param data     数据列表
     * @param clazz    数据类型
     * @param filePath 文件路径
     * @param config   配置信息
     * @param <T>      数据类型
     */
    public static <T> void exportToFile(List<T> data, Class<T> clazz,
                                        String filePath, ExcelExportConfig config) {
        if (data == null) {
            data = Collections.emptyList();
        }

        try {
            EasyExcel.write(filePath, clazz)
                    .sheet(config.getDefaultSheetName())
                    .doWrite(data);
            log.info("Excel文件导出成功，路径：{}，数据量：{}", filePath, data.size());
        } catch (Exception e) {
            log.error("Excel文件导出失败", e);
            throw new CzgException("Excel文件导出失败", e);
        }
    }

    /**
     * 带样式的Excel导出到Response
     *
     * @param data     数据列表
     * @param clazz    数据类型
     * @param fileName 文件名
     * @param response HttpServletResponse
     * @param <T>      数据类型
     */
    public static <T> void exportWithStyleToResponse(List<T> data, Class<T> clazz,
                                                     String fileName, HttpServletResponse response) {
        if (data == null) {
            data = Collections.emptyList();
        }

        setResponseHeader(response, fileName, DEFAULT_CONFIG);

        try (OutputStream outputStream = response.getOutputStream()) {
            // 设置表格样式
            HorizontalCellStyleStrategy styleStrategy = createCellStyleStrategy();

            ExcelWriter excelWriter = EasyExcel.write(outputStream, clazz)
                    .registerWriteHandler(styleStrategy)
                    .autoCloseStream(true)
                    .build();

            WriteSheet writeSheet = EasyExcel.writerSheet(DEFAULT_CONFIG.getDefaultSheetName()).build();
            excelWriter.write(data, writeSheet);
            excelWriter.finish();

            log.info("带样式Excel导出成功，文件名：{}，数据量：{}", fileName, data.size());
        } catch (IOException e) {
            log.error("带样式Excel导出失败", e);
            throw new CzgException("Excel导出失败", e);
        }
    }

    /**
     * 大数据量分批导出（避免内存溢出）
     *
     * @param dataSupplier 数据提供者（分页获取数据）
     * @param clazz        数据类型
     * @param fileName     文件名
     * @param response     HttpServletResponse
     * @param batchSize    每批大小
     * @param <T>          数据类型
     */
    public static <T> void exportBigDataToResponse(DataSupplier<T> dataSupplier,
                                                   Class<T> clazz, String fileName,
                                                   HttpServletResponse response, int batchSize) {
        setResponseHeader(response, fileName, DEFAULT_CONFIG);

        try (OutputStream outputStream = response.getOutputStream()) {
            ExcelWriter excelWriter = EasyExcel.write(outputStream, clazz)
                    .autoCloseStream(true)
                    .build();

            WriteSheet writeSheet = EasyExcel.writerSheet(DEFAULT_CONFIG.getDefaultSheetName()).build();

            int pageNum = 1;
            List<T> batchData;
            boolean hasNext = true;

            while (hasNext) {
                batchData = dataSupplier.getData(pageNum, batchSize);
                if (batchData != null && !batchData.isEmpty()) {
                    excelWriter.write(batchData, writeSheet);
                    pageNum++;
                } else {
                    hasNext = false;
                }
            }

            excelWriter.finish();
            log.info("大数据量Excel导出成功，文件名：{}，总页数：{}", fileName, pageNum - 1);
        } catch (IOException e) {
            log.error("大数据量Excel导出失败", e);
            throw new CzgException("Excel导出失败", e);
        }
    }

    /**
     * 设置响应头
     */
    private static void setResponseHeader(HttpServletResponse response, String fileName, ExcelExportConfig config) {
        try {
            String encodedFileName = URLEncoder.encode(fileName, config.getCharset())
                    .replaceAll("\\+", "%20");
            String contentDisposition = "attachment;filename*=utf-8''" + encodedFileName + ".xlsx";

            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding(config.getCharset());
            response.setHeader("Content-Disposition", contentDisposition);
        } catch (Exception e) {
            log.warn("设置响应头失败", e);
        }
    }

    /**
     * 创建表格样式策略
     */
    private static HorizontalCellStyleStrategy createCellStyleStrategy() {
        // 表头样式
        WriteCellStyle headStyle = new WriteCellStyle();
        headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        headStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 内容样式
        WriteCellStyle contentStyle = new WriteCellStyle();
        contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
        contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        return new HorizontalCellStyleStrategy(headStyle, contentStyle);
    }

    /**
     * 获取数据总行数（用于前端显示进度）
     */
    public static <T> int getDataCount(Class<T> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        int count = 0;
        for (Field field : fields) {
            if (field.isAnnotationPresent(ExcelProperty.class)) {
                count++;
            }
        }
        return count;
    }

    /**
     * 带合并单元格的导出到Response
     * 多sheet导出到response
     *
     * @param sheetDataList 数据列表
     * @param fileName      文件名
     * @param response      HttpServletResponse
     */
    public static void exportMultipleSheetsToResponse(List<SheetData> sheetDataList,
                                                      String fileName,
                                                      HttpServletResponse response) {
        if (CollectionUtil.isEmpty(sheetDataList)) {
            throw new CzgException("数据列表不能为空");
        }

        setResponseHeader(response, fileName, DEFAULT_CONFIG);

        try (OutputStream outputStream = response.getOutputStream()) {
            // 创建样式策略
            WriteCellStyle headStyle = new WriteCellStyle();
            headStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
            headStyle.setVerticalAlignment(VerticalAlignment.CENTER);

            WriteCellStyle contentStyle = new WriteCellStyle();
            contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
            contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);

            HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(headStyle, contentStyle);

            // 创建ExcelWriter
            ExcelWriterBuilder builder = EasyExcel.write(outputStream)
                    .autoCloseStream(true)
                    .registerConverter(new LocalTimeConverter())
                    .registerWriteHandler(styleStrategy);

            ExcelWriter excelWriter = builder.build();

            // 逐个写入sheet
            for (int i = 0; i < sheetDataList.size(); i++) {
                SheetData sheetData = sheetDataList.get(i);
                String sheetName = StrUtil.isNotBlank(sheetData.getSheetName())
                        ? sheetData.getSheetName()
                        : DEFAULT_CONFIG.getDefaultSheetName() + (i + 1);

                ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet(sheetName);

                // 注册该sheet的合并处理器
                if (sheetData.getHandlers() != null && !sheetData.getHandlers().isEmpty()) {
                    for (SheetWriteHandler handler : sheetData.getHandlers()) {
                        sheetBuilder.registerWriteHandler(handler);
                    }
                }

                WriteSheet writeSheet = sheetBuilder.head(sheetData.getClazz()).build();
                excelWriter.write(sheetData.getData(), writeSheet);
            }

            excelWriter.finish();
            log.info("多sheet商品Excel导出成功，文件名：{}，共{}个sheet", fileName, sheetDataList.size());
        } catch (IOException e) {
            log.error("多sheet商品Excel导出失败", e);
            throw new CzgException("Excel导出失败", e);
        }
    }

}
