package com.yd.oss.service.service.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import com.alibaba.fastjson.JSON;
import com.yd.oss.feign.dto.ExportParam;
import com.yd.oss.feign.dto.ExportResult;
import com.yd.oss.service.dto.OssUploadFileResDto;
import com.yd.oss.service.service.ExcelExportService;
import com.yd.oss.service.service.OssService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * excel导出实现类
 */
@Slf4j
@Service
public class ExcelExportServiceImpl implements ExcelExportService {

    // 注入OSS服务，用于文件上传
    @Autowired
    private OssService ossService;

    /**
     * 导出Excel并上传到OSS
     * @param dataList 需要导出的数据列表
     * @param exportParam 导出参数配置
     * @param entityClass 实体类类型
     * @return 导出结果
     */
    @Override
    public ExportResult exportAndUploadToOss(List<?> dataList, ExportParam exportParam, Class<?> entityClass) {
        log.info("导出Excel并上传到OSS:dataList,{}", JSON.toJSONString(dataList));
        log.info("导出Excel并上传到OSS:exportParam,{}", JSON.toJSONString(exportParam));
        log.info("导出Excel并上传到OSS:entityClass,{}", JSON.toJSONString(entityClass));
        // 添加参数校验
        if (exportParam == null) {
            return ExportResult.error("导出参数不能为空");
        }
        if (CollectionUtils.isEmpty(exportParam.getFieldNames())) {
            return ExportResult.error("导出字段列表不能为空");
        }

        // 声明输出流和工作簿变量，用于后续资源关闭
        ByteArrayOutputStream outputStream = null;
        Workbook workbook = null;

        try {
            // 根据实体类字段和导出参数构建Excel导出列配置
            List<ExcelExportEntity> entityList = buildExportEntities(exportParam.getFieldNames(), entityClass);

            // 将数据列表转换为Map结构，便于EasyPOI处理
            List<Map<String, Object>> dataMapList = buildDataMapList(dataList, exportParam.getFieldNames(), entityClass);

            // 添加空数据检查
            if (CollectionUtils.isEmpty(dataMapList)) {
                // 创建一个空数据的Map，确保至少有表头
                dataMapList = new ArrayList<>();
                Map<String, Object> emptyMap = new HashMap<>();
                for (String fieldName : exportParam.getFieldNames()) {
                    emptyMap.put(fieldName, "");
                }
                dataMapList.add(emptyMap);
            }

            // 检查entityList是否为空
            if (CollectionUtils.isEmpty(entityList)) {
                return ExportResult.error("导出列配置不能为空");
            }

            // 创建字节数组输出流，用于将Excel数据写入内存
            outputStream = new ByteArrayOutputStream();
            // 设置导出参数：无标题、工作表名称为sheet1、使用XSSF格式（支持.xlsx）
            ExportParams params = new ExportParams(null, "sheet1", ExcelType.XSSF);

            // 确保params不为null
            if (params == null) {
                params = new ExportParams();
            }

            // 使用EasyPOI导出Excel到工作簿
            workbook = ExcelExportUtil.exportExcel(params, entityList, dataMapList);
            // 检查workbook是否创建成功
            if (workbook == null) {
                return ExportResult.error("Excel工作簿创建失败");
            }
            // 将工作簿内容写入输出流
            workbook.write(outputStream);

            // 获取Excel文件的字节数组和文件大小
            byte[] excelBytes = outputStream.toByteArray();
            long fileSize = excelBytes.length;

            // 根据参数决定是否上传到OSS
            if (Boolean.TRUE.equals(exportParam.getUploadToOss())) {
                // 将字节数组转换为输入流，供OSS上传使用
                ByteArrayInputStream inputStream = new ByteArrayInputStream(excelBytes);

                // 生成包含时间戳的文件名，避免重复
                String fileName = generateExcelFileName(exportParam.getFileName());

                // 调用OSS服务上传文件到云端存储
                OssUploadFileResDto uploadResult = ossService.uploadFile(
                        inputStream,        // Excel文件输入流
                        fileName,           // 生成的文件名
                        "",                 // 存储桶名称（空字符串表示使用默认存储桶）
                        "",                 // 上传用户标识（空字符串表示匿名或系统用户）
                        "excel",             // 文件分类类型，对应OSS文件夹目录
                        ""
                );

                // 返回成功结果，包含文件URL和大小信息
                return ExportResult.success(uploadResult.getUrl(), fileSize);
            } else {
                // 如果不需要上传到OSS，仅返回文件大小信息
                return ExportResult.success(null, fileSize);
            }

        } catch (Exception e) {
            // 打印异常堆栈，便于调试和问题排查
            e.printStackTrace();
            // 返回错误结果，包含异常信息
            return ExportResult.error("导出失败: " + e.getMessage());
        } finally {
            // 资源清理块，确保工作簿和输出流正确关闭
            try {
                // 关闭工作簿，释放内存资源
                if (workbook != null) {
                    workbook.close();
                }
                // 关闭输出流，释放系统资源
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (Exception e) {
                // 打印资源关闭时的异常信息
                e.printStackTrace();
            }
        }
    }

    /**
     * 生成Excel文件名（包含时间戳）
     * @param baseName 基础文件名
     * @return 生成的文件名
     */
    private String generateExcelFileName(String baseName) {
        // 如果基础文件名为空，使用默认文件名
        if (StringUtils.isBlank(baseName)) {
            baseName = "export_data";
        }

        // 去除已有文件扩展名，确保统一使用.xlsx格式
        if (baseName.contains(".")) {
            baseName = baseName.substring(0, baseName.lastIndexOf('.'));
        }

        // 生成时间戳，格式为年月日_时分秒
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        // 组合基础文件名、时间戳和文件扩展名
        return baseName + "_" + timestamp + ".xlsx";
    }

    /**
     * 构建导出列实体配置
     * @param fieldNames 字段名称列表
     * @param entityClass 实体类类型
     * @return Excel导出列配置列表
     */
    private List<ExcelExportEntity> buildExportEntities(List<String> fieldNames, Class<?> entityClass) {
        // 创建导出列配置列表
        List<ExcelExportEntity> entityList = new ArrayList<>();
        // 获取实体类字段的注解映射（字段名->Excel列名）
        Map<String, String> fieldAnnotationMap = null;
        if (!Objects.isNull(entityClass)) {
            fieldAnnotationMap = getFieldAnnotations(entityClass);
        }

        // 遍历所有需要导出的字段
        for (String fieldName : fieldNames) {
            // 检查字段是否存在于注解映射中
            if (!Objects.isNull(fieldAnnotationMap) && fieldAnnotationMap.containsKey(fieldName)) {
                //传入了entityClass实体类型
                // 创建Excel导出列实体：参数1为Excel列标题，参数2为实体字段名
                ExcelExportEntity entity = new ExcelExportEntity(fieldAnnotationMap.get(fieldName), fieldName);
                // 将列配置添加到列表
                entityList.add(entity);
            }else {
                //没有传入entityClass实体类型，那就取fieldNames做为ExcelExportEntity参数1和参数2值
                // 创建Excel导出列实体：参数1为Excel列标题，参数2为实体字段名
                ExcelExportEntity entity = new ExcelExportEntity(fieldName, fieldName);
                // 将列配置添加到列表
                entityList.add(entity);
            }
        }
        return entityList;
    }

    /**
     * 获取字段注解映射（字段名->Excel列名）
     * @param entityClass 实体类类型
     * @return 字段注解映射表
     */
    private Map<String, String> getFieldAnnotations(Class<?> entityClass) {
        // 创建字段注解映射表
        Map<String, String> fieldAnnotationMap = new HashMap<>();
        // 获取实体类声明的所有字段
        Field[] fields = entityClass.getDeclaredFields();

        // 遍历所有字段
        for (Field field : fields) {
            // 获取字段上的Excel注解配置
            cn.afterturn.easypoi.excel.annotation.Excel excelAnnotation =
                    field.getAnnotation(cn.afterturn.easypoi.excel.annotation.Excel.class);
            // 如果字段有Excel注解，记录映射关系
            if (excelAnnotation != null) {
                // key: 字段名, value: Excel列名
                fieldAnnotationMap.put(field.getName(), excelAnnotation.name());
            }
        }
        return fieldAnnotationMap;
    }

    /**
     * 构建数据Map列表，将对象列表转换为EasyPOI所需的Map结构
     * @param dataList 数据对象列表
     * @param fieldNames 需要导出的字段名称列表
     * @param entityClass 实体类类型
     * @return 数据Map列表
     */
    private List<Map<String, Object>> buildDataMapList(List<?> dataList, List<String> fieldNames, Class<?> entityClass) {
        // 创建数据Map列表
        List<Map<String, Object>> dataMapList = new ArrayList<>();
        if (CollectionUtils.isEmpty(dataList)) {
            return dataMapList;
        }
        // 遍历数据列表中的每个对象
        for (Object data : dataList) {
            // 为每个对象创建字段-值的映射
            Map<String, Object> map = new HashMap<>();
            // 遍历所有需要导出的字段
            for (String fieldName : fieldNames) {
                try {
                    // 通过反射获取字段对象
                    Field field = entityClass.getDeclaredField(fieldName);
                    // 设置字段可访问（突破private限制）
                    field.setAccessible(true);
                    // 将字段值放入Map，key为字段名，value为字段值
                    map.put(fieldName, field.get(data));
                } catch (Exception e) {
                    // 如果获取字段值失败，设置为空字符串
                    map.put(fieldName, "");
                }
            }
            // 将当前对象的字段映射添加到列表
            dataMapList.add(map);
        }
        return dataMapList;
    }
}