package com.dayou.utils;

import cn.hutool.core.util.ObjectUtil;
import com.dayou.annotation.Excel;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

public class ExcelOneToManyExportUtils {

    /**
     * 导出一对多excel
     *
     * @param response    response
     * @param parentList  数据
     * @param parentClazz 父级类
     * @param childClazz  子级类
     * @param title       标题
     * @param listName    子级的集合字段(属性)名
     */
    public static void exportOneToMany(HttpServletResponse response, List<?> parentList, Class<?> parentClazz, Class<?> childClazz, String title, String listName) throws IOException, NoSuchFieldException, IllegalAccessException {
        // 获取注解信息
        List<Excel> annoFieldInfo = new ArrayList<>();
        annoFieldInfo.addAll(getAnnotatedFieldNames(parentClazz));
        annoFieldInfo.addAll(getAnnotatedFieldNames(childClazz));

        // 父级字段(属性)名
        List<String> parentFieldNames = getFieldNames(parentClazz);

        // 子级字段(属性)名
        List<String> childFieldNames = getFieldNames(childClazz);

        XSSFWorkbook workbook = new XSSFWorkbook();
        // 创建表
        Sheet sheet = workbook.createSheet(title);
        // 创建标题行(表头)
        Row titleRow = sheet.createRow(0);
        int nameIndex = 0;
        for (Excel excelInfo : annoFieldInfo) {
            titleRow.createCell(nameIndex).setCellValue(excelInfo.name());
            titleRow.getCell(nameIndex).setCellStyle(getCellStyleBorderWithColor(workbook, IndexedColors.GREY_50_PERCENT.getIndex(), IndexedColors.WHITE.getIndex()));
            sheet.setColumnWidth(nameIndex, (int) ((excelInfo.width() + 0.72) * 256));
            nameIndex++;
        }
        int rowIndex = 1; // 开始数据行的索引
        for (Object parent : parentList) {
            // 创建行
            Row parentRow = sheet.createRow(rowIndex++);
            // 填充父对象数据
            int dataIndex = 0;
            for (String fieldName : parentFieldNames) {
                // 判断数据是否为空
                Object parentValue = getFieldValueByName(parent, fieldName);
                if (ObjectUtil.isNotNull(parentValue)) {
                    parentRow.createCell(dataIndex).setCellValue(parentValue.toString());
                } else {
                    parentRow.createCell(dataIndex).setCellValue("");
                }
                parentRow.getCell(dataIndex).setCellStyle(getNormalCellStyle(workbook));

                // 父级写入完成,获取子级数据
                if (dataIndex + 1 == parentFieldNames.size()) {
                    // 获取子级集合
                    List<?> children = (List<?>) getFieldValueByName(parent, listName);
                    int childWriteLoop = 0;
                    // 循环子级集合
                    for (Object child : children) {
                        // 判断是否第一次循环子级集合
                        if (childWriteLoop > 0) { // 非第一次循环
                            int childDataIndex = dataIndex + 1;
                            // 新建行
                            Row childRow = sheet.createRow(rowIndex++);
                            // 需要写入空数据,否则设置样式会空指针异常
                            for (int nullIndex = 0; nullIndex <= dataIndex; nullIndex++) {
                                childRow.createCell(nullIndex).setCellValue("");
                                childRow.getCell(nullIndex).setCellStyle(getNormalCellStyle(workbook));
                            }
                            for (String childFieldName : childFieldNames) {
                                // 判断数据是否为空
                                Object childValue = getFieldValueByName(child, childFieldName);
                                if (ObjectUtil.isNotNull(childValue)) {
                                    childRow.createCell(childDataIndex).setCellValue(childValue.toString());
                                } else {
                                    childRow.createCell(childDataIndex).setCellValue("");
                                }
                                childRow.getCell(childDataIndex).setCellStyle(getNormalCellStyle(workbook));
                                childDataIndex++;
                            }
                        } else { // 第一次循环不新建行,直接跟在父级行后面填充数据
                            int childDataIndex = dataIndex + 1;
                            for (String childFieldName : childFieldNames) {
                                // 判断数据是否为空
                                Object childValue = getFieldValueByName(child, childFieldName);
                                if (ObjectUtil.isNotNull(childValue)) {
                                    parentRow.createCell(childDataIndex).setCellValue(childValue.toString());
                                } else {
                                    parentRow.createCell(childDataIndex).setCellValue("");
                                }
                                parentRow.getCell(childDataIndex).setCellStyle(getNormalCellStyle(workbook));
                                childDataIndex++;
                            }
                        }
                        childWriteLoop++;
                    }
                    // 如果子级数据是空的,也需要填充空数据,否侧设置样式会空指针
                    if (children.isEmpty()) {
                        int childDataIndex = dataIndex + 1;
                        for (String childFieldName : childFieldNames) {
                            parentRow.createCell(childDataIndex).setCellValue("");
                            parentRow.getCell(childDataIndex).setCellStyle(getNormalCellStyle(workbook));
                            childDataIndex++;
                        }
                    }
                    // 子级数据填充完成后,且子数据集合大于一合并父级对象单元格
                    if (!children.isEmpty() && children.size() > 1) {
                        for (int i = 0; i < parentFieldNames.size(); i++) {
                            sheet.addMergedRegion(new CellRangeAddress(rowIndex - 2, rowIndex - 1, i, i));
                        }
                    }
                }
                dataIndex++;
            }
        }

        // 设置响应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(title, "UTF-8") + ".xlsx");

        // 写入Excel文件
        OutputStream outputStream = response.getOutputStream();
        workbook.write(outputStream);

    }

    /**
     * 通过字段(属性)名获取指定字段的值
     *
     * @param obj       对象
     * @param fieldName 字段名
     * @return String
     */
    public static Object getFieldValueByName(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
            // 获取对象的Class对象
            Class<?> clazz = obj.getClass();
            // 通过反射获取指定名称的字段
            Field field = clazz.getDeclaredField(fieldName);
            // 设置字段可访问，以便能够访问私有字段
            field.setAccessible(true);
            // 获取字段值
            return field.get(obj);
    }

    /**
     * 获取类的字段名
     *
     * @param clazz class
     * @return List<String>
     */
    public static List<String> getFieldNames(Class<?> clazz) {

        List<String> fieldNames = new ArrayList<>();

        // 获取类的所有声明字段（不包括继承的字段）
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(Excel.class)) {
                fieldNames.add(field.getName());
            }
        }
        return fieldNames;
    }

    /**
     * 获取clazz的注解name属性的值
     *
     * @param clazz 类
     * @return List<String>
     */
    public static List<Excel> getAnnotatedFieldNames(Class<?> clazz) {
        List<Excel> annoFieldInfo = new ArrayList<>();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true); // 确保可以访问私有字段
            if (field.isAnnotationPresent(Excel.class)) {
                Excel excelAnnotation = field.getAnnotation(Excel.class);
                annoFieldInfo.add(excelAnnotation);
            }
        }

        return annoFieldInfo;
    }

    /**
     * 设置单元格边框线与字体、颜色等
     *
     * @param workbook  工作簿
     * @param backColor 背景色
     * @param textColor 字体颜色
     * @return CellStyle
     */
    public static CellStyle getCellStyleBorderWithColor(XSSFWorkbook workbook, Short backColor, Short textColor) {
        // 设置单元格背景色
        CellStyle style = workbook.createCellStyle();
        if (ObjectUtil.isNotNull(backColor)) {
            style.setFillForegroundColor(backColor);
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        // 设置字体样式
        Font headerFont = workbook.createFont();
        headerFont.setFontName("Arial");
        headerFont.setFontHeightInPoints((short) 10);
        if (ObjectUtil.isNotNull(textColor)) {
            headerFont.setBold(true);
            headerFont.setColor(IndexedColors.WHITE.getIndex());
        }
        // 设置边框样式
        style.setFont(headerFont);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setBorderRight(BorderStyle.THIN);
        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderLeft(BorderStyle.THIN);
        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderTop(BorderStyle.THIN);
        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderBottom(BorderStyle.THIN);
        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        return style;
    }

    /**
     * 设置单元格边框线
     *
     * @param workbook  工作簿
     * @return CellStyle
     */
    public static CellStyle getNormalCellStyle(XSSFWorkbook workbook) {
        CellStyle style = workbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        style.setBorderRight(BorderStyle.THIN);
        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderLeft(BorderStyle.THIN);
        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderTop(BorderStyle.THIN);
        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderBottom(BorderStyle.THIN);
        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        return style;
    }
}

