package com.ruoyi.common.utils.poi;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.Type;
import com.ruoyi.common.annotation.Excels;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Excel相关处理 - 基于EasyExcel实现
 *
 * @author ruoyi
 */
public class ExcelUtil<T>
{
    private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);

    public static final String SEPARATOR = ",";

    /**
     * 用于dictType属性数据存储，避免重复查缓存
     */
    public Map<String, String> sysDictMap = new HashMap<String, String>();

    /**
     * 导出类型（EXPORT:导出数据；IMPORT：导入模板）
     */
    private Type type;

    /**
     * 导入导出数据列表
     */
    private List<T> list;

    /**
     * 注解列表
     */
    private List<FieldInfo> fields;

    /**
     * 实体对象
     */
    public Class<T> clazz;

    /**
     * 需要显示列属性
     */
    public String[] includeFields;

    /**
     * 需要排除列属性
     */
    public String[] excludeFields;

    /**
     * Sheet名称
     */
    private String sheetName;

    /**
     * 标题
     */
    private String title;

    public ExcelUtil(Class<T> clazz)
    {
        this.clazz = clazz;
    }

    /**
     * 仅在Excel中显示列属性
     *
     * @param fields 列属性名 示例[单个"name"/多个"id","name"]
     */
    public void showColumn(String... fields)
    {
        this.includeFields = fields;
    }

    /**
     * 隐藏Excel中列属性
     *
     * @param fields 列属性名 示例[单个"name"/多个"id","name"]
     */
    public void hideColumn(String... fields)
    {
        this.excludeFields = fields;
    }

    /**
     * 对excel表单默认第一个索引名转换成list
     *
     * @param is 输入流
     * @return 转换后集合
     */
    public List<T> importExcel(InputStream is)
    {
        return importExcel(is, 0);
    }

    /**
     * 对excel表单默认第一个索引名转换成list
     *
     * @param is 输入流
     * @param titleNum 标题占用行数
     * @return 转换后集合
     */
    public List<T> importExcel(InputStream is, int titleNum)
    {
        List<T> list = null;
        try
        {
            list = importExcel(StringUtils.EMPTY, is, titleNum);
        }
        catch (Exception e)
        {
            log.error("导入Excel异常{}", e.getMessage());
            throw new UtilException(e.getMessage());
        }
        return list;
    }

    /**
     * 对excel表单指定表格索引名转换成list
     *
     * @param sheetName 表格索引名
     * @param titleNum 标题占用行数
     * @param is 输入流
     * @return 转换后集合
     */
    public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
    {
        this.type = Type.IMPORT;
        this.fields = getFields();

        List<T> dataList = new ArrayList<T>();

        EasyExcel.read(is, clazz, new ReadListener<T>()
        {
            @Override
            public void invoke(T data, AnalysisContext context)
            {
                // 处理导入的数据转换
                processImportData(data);
                dataList.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context)
            {
                log.debug("Excel导入完成，共{}行数据", dataList.size());
            }
        }).sheet(sheetName).headRowNumber(titleNum).doRead();

        return dataList;
    }

    /**
     * 处理导入的数据转换（反向转换）
     */
    private void processImportData(T data)
    {
        if (data == null)
        {
            return;
        }

        for (FieldInfo fieldInfo : fields)
        {
            Field field = fieldInfo.field;
            Excel attr = fieldInfo.excel;

            try
            {
                // 处理字典反向转换（标签转值）
                if (StringUtils.isNotEmpty(attr.dictType()))
                {
                    Object value = ReflectUtils.invokeGetter(data, field.getName());
                    if (StringUtils.isNotNull(value))
                    {
                        String dictValue = reverseDictByExp(Convert.toStr(value), attr.dictType(), attr.separator());
                        ReflectUtils.invokeSetter(data, field.getName(), dictValue);
                    }
                }
                // 处理表达式反向转换
                else if (StringUtils.isNotEmpty(attr.readConverterExp()))
                {
                    Object value = ReflectUtils.invokeGetter(data, field.getName());
                    if (StringUtils.isNotNull(value))
                    {
                        String convertedValue = reverseByExp(Convert.toStr(value), attr.readConverterExp(), attr.separator());
                        ReflectUtils.invokeSetter(data, field.getName(), convertedValue);
                    }
                }
            }
            catch (Exception e)
            {
                log.error("处理导入字段{}异常", field.getName(), e);
            }
        }
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @return 结果
     */
    public AjaxResult exportExcel(List<T> list, String sheetName)
    {
        return exportExcel(list, sheetName, StringUtils.EMPTY);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param title 标题
     * @return 结果
     */
    public AjaxResult exportExcel(List<T> list, String sheetName, String title)
    {
        this.list = list;
        this.sheetName = sheetName;
        this.title = title;
        this.type = Type.EXPORT;
        return exportExcel();
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param response 返回数据
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @return 结果
     */
    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName)
    {
        exportExcel(response, list, sheetName, StringUtils.EMPTY);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param response 返回数据
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param title 标题
     * @return 结果
     */
    public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title)
    {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        this.list = list;
        this.sheetName = sheetName;
        this.title = title;
        this.type = Type.EXPORT;
        exportExcel(response);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param sheetName 工作表的名称
     * @return 结果
     */
    public AjaxResult importTemplateExcel(String sheetName)
    {
        return importTemplateExcel(sheetName, StringUtils.EMPTY);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param sheetName 工作表的名称
     * @param title 标题
     * @return 结果
     */
    public AjaxResult importTemplateExcel(String sheetName, String title)
    {
        this.list = new ArrayList<T>();
        this.sheetName = sheetName;
        this.title = title;
        this.type = Type.IMPORT;
        return exportExcel();
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param sheetName 工作表的名称
     * @return 结果
     */
    public void importTemplateExcel(HttpServletResponse response, String sheetName)
    {
        importTemplateExcel(response, sheetName, StringUtils.EMPTY);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @param sheetName 工作表的名称
     * @param title 标题
     * @return 结果
     */
    public void importTemplateExcel(HttpServletResponse response, String sheetName, String title)
    {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        this.list = new ArrayList<T>();
        this.sheetName = sheetName;
        this.title = title;
        this.type = Type.IMPORT;
        exportExcel(response);
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @return 结果
     */
    public void exportExcel(HttpServletResponse response)
    {
        try
        {
            this.fields = getFields();
            // 展平数据，处理嵌套对象
            List<Map<Integer, String>> flatData = flattenData();
            // 使用动态表头和 Map 数据导出
            EasyExcel.write(response.getOutputStream())
                .head(dynamicHead())
                .sheet(sheetName)
                .registerWriteHandler(new ExcelStyleHandler(fields))
                .doWrite(flatData);
        }
        catch (Exception e)
        {
            log.error("导出Excel异常{}", e.getMessage());
        }
    }

    /**
     * 对list数据源将其里面的数据导入到excel表单
     *
     * @return 结果
     */
    public AjaxResult exportExcel()
    {
        OutputStream out = null;
        try
        {
            this.fields = getFields();
            String filename = encodingFilename(sheetName);
            out = new FileOutputStream(getAbsoluteFile(filename));

            // 展平数据，处理嵌套对象
            List<Map<Integer, String>> flatData = flattenData();
            // 使用动态表头和 Map 数据导出
            EasyExcel.write(out)
                .head(dynamicHead())
                .sheet(sheetName)
                .registerWriteHandler(new ExcelStyleHandler(fields))
                .doWrite(flatData);

            return AjaxResult.success(filename);
        }
        catch (Exception e)
        {
            log.error("导出Excel异常{}", e.getMessage());
            throw new UtilException("导出Excel失败，请联系网站管理员！");
        }
        finally
        {
            try
            {
                if (out != null)
                {
                    out.close();
                }
            }
            catch (Exception e)
            {
                log.error("关闭输出流异常", e);
            }
        }
    }

    /**
     * 展平数据，处理嵌套对象和字典转换
     */
    private List<Map<Integer, String>> flattenData()
    {
        List<Map<Integer, String>> result = new ArrayList<>();
        for (T item : list)
        {
            Map<Integer, String> row = new HashMap<>();
            for (int i = 0; i < fields.size(); i++)
            {
                FieldInfo fieldInfo = fields.get(i);
                Field field = fieldInfo.field;
                Excel attr = fieldInfo.excel;

                try
                {
                    // 获取字段值
                    Object value = getFieldValue(item, field, attr);

                    // 格式化值
                    String stringValue = formatValue(value, attr);

                    // 存储到 row 中，使用列索引作为 key
                    row.put(i, stringValue);
                }
                catch (Exception e)
                {
                    log.error("处理字段{}异常", field.getName(), e);
                    row.put(i, "");
                }
            }
            result.add(row);
        }
        return result;
    }

    /**
     * 获取字段值，支持嵌套对象
     */
    private Object getFieldValue(T item, Field field, Excel attr) throws Exception
    {
        field.setAccessible(true);
        Object value = field.get(item);

        // 处理嵌套属性
        if (StringUtils.isNotEmpty(attr.targetAttr()) && value != null)
        {
            String target = attr.targetAttr();
            if (target.contains("."))
            {
                String[] targets = target.split("\\.");
                for (String name : targets)
                {
                    value = getValue(value, name);
                    if (value == null)
                    {
                        break;
                    }
                }
            }
            else
            {
                value = getValue(value, target);
            }
        }

        return value;
    }

    /**
     * 从对象中获取属性值
     */
    private Object getValue(Object obj, String name) throws Exception
    {
        if (StringUtils.isNotNull(obj) && StringUtils.isNotEmpty(name))
        {
            Class<?> clazz = obj.getClass();
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            return field.get(obj);
        }
        return null;
    }

    /**
     * 格式化值
     */
    private String formatValue(Object value, Excel attr)
    {
        if (value == null)
        {
            return attr.defaultValue();
        }

        // 日期格式化
        if (value instanceof Date && StringUtils.isNotEmpty(attr.dateFormat()))
        {
            return DateUtils.parseDateToStr(attr.dateFormat(), (Date) value);
        }
        else if (value instanceof LocalDateTime && StringUtils.isNotEmpty(attr.dateFormat()))
        {
            return DateUtils.parseDateToStr(attr.dateFormat(), DateUtils.toDate((LocalDateTime) value));
        }
        else if (value instanceof LocalDate && StringUtils.isNotEmpty(attr.dateFormat()))
        {
            return DateUtils.parseDateToStr(attr.dateFormat(), DateUtils.toDate((LocalDate) value));
        }

        String strValue = Convert.toStr(value);

        // BigDecimal 精度处理
        if (value instanceof BigDecimal && attr.scale() != -1)
        {
            strValue = ((BigDecimal) value).setScale(attr.scale(), attr.roundingMode()).toString();
        }

        // 字典转换
        if (StringUtils.isNotEmpty(attr.dictType()))
        {
            if (!sysDictMap.containsKey(attr.dictType() + strValue))
            {
                String label = convertDictByExp(strValue, attr.dictType(), attr.separator());
                sysDictMap.put(attr.dictType() + strValue, label);
            }
            strValue = sysDictMap.get(attr.dictType() + strValue);
        }
        // 表达式转换
        else if (StringUtils.isNotEmpty(attr.readConverterExp()))
        {
            strValue = convertByExp(strValue, attr.readConverterExp(), attr.separator());
        }

        // 添加后缀
        if (StringUtils.isNotEmpty(attr.suffix()))
        {
            strValue = strValue + attr.suffix();
        }

        return strValue;
    }

    /**
     * 创建动态表头
     */
    private List<List<String>> dynamicHead()
    {
        List<List<String>> head = new ArrayList<>();
        for (FieldInfo fieldInfo : fields)
        {
            List<String> headLine = new ArrayList<>();
            headLine.add(fieldInfo.excel.name());
            head.add(headLine);
        }
        return head;
    }

    /**
     * 得到所有定义字段
     */
    private List<FieldInfo> getFields()
    {
        List<FieldInfo> fieldList = new ArrayList<FieldInfo>();
        List<Field> tempFields = new ArrayList<>();

        // 获取所有字段（包括父类）
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));

        for (Field field : tempFields)
        {
            // 单注解
            if (field.isAnnotationPresent(Excel.class))
            {
                Excel attr = field.getAnnotation(Excel.class);
                if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
                {
                    // 字段筛选
                    if (StringUtils.isNotEmpty(includeFields))
                    {
                        if (!ArrayUtils.contains(includeFields, field.getName())
                            && !field.isAnnotationPresent(Excels.class))
                        {
                            continue;
                        }
                    }
                    else if (StringUtils.isNotEmpty(excludeFields))
                    {
                        if (ArrayUtils.contains(excludeFields, field.getName()))
                        {
                            continue;
                        }
                    }
                    fieldList.add(new FieldInfo(field, attr));
                }
            }

            // 多注解
            if (field.isAnnotationPresent(Excels.class))
            {
                Excels attrs = field.getAnnotation(Excels.class);
                Excel[] excels = attrs.value();
                for (Excel attr : excels)
                {
                    if (StringUtils.isNotEmpty(includeFields))
                    {
                        if (ArrayUtils.contains(includeFields, field.getName() + "." + attr.targetAttr())
                            && (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
                        {
                            fieldList.add(new FieldInfo(field, attr));
                        }
                    }
                    else
                    {
                        if (!ArrayUtils.contains(excludeFields, field.getName() + "." + attr.targetAttr())
                            && (attr != null && (attr.type() == Type.ALL || attr.type() == type)))
                        {
                            fieldList.add(new FieldInfo(field, attr));
                        }
                    }
                }
            }
        }

        // 按sort排序
        fieldList = fieldList.stream()
            .sorted(Comparator.comparing(f -> f.excel.sort()))
            .collect(Collectors.toList());

        return fieldList;
    }

    /**
     * 解析导出值 0=男,1=女,2=未知
     *
     * @param propertyValue 参数值
     * @param converterExp 翻译注解
     * @param separator 分隔符
     * @return 解析后值
     */
    public static String convertByExp(String propertyValue, String converterExp, String separator)
    {
        StringBuilder propertyString = new StringBuilder();
        String[] convertSource = converterExp.split(SEPARATOR);
        for (String item : convertSource)
        {
            String[] itemArray = item.split("=");
            if (StringUtils.containsAny(propertyValue, separator))
            {
                for (String value : propertyValue.split(separator))
                {
                    if (itemArray[0].equals(value))
                    {
                        propertyString.append(itemArray[1] + separator);
                        break;
                    }
                }
            }
            else
            {
                if (itemArray[0].equals(propertyValue))
                {
                    return itemArray[1];
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 反向解析值 男=0,女=1,未知=2
     *
     * @param propertyValue 参数值
     * @param converterExp 翻译注解
     * @param separator 分隔符
     * @return 解析后值
     */
    public static String reverseByExp(String propertyValue, String converterExp, String separator)
    {
        StringBuilder propertyString = new StringBuilder();
        String[] convertSource = converterExp.split(SEPARATOR);
        for (String item : convertSource)
        {
            String[] itemArray = item.split("=");
            if (StringUtils.containsAny(propertyValue, separator))
            {
                for (String value : propertyValue.split(separator))
                {
                    if (itemArray[1].equals(value))
                    {
                        propertyString.append(itemArray[0] + separator);
                        break;
                    }
                }
            }
            else
            {
                if (itemArray[1].equals(propertyValue))
                {
                    return itemArray[0];
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 解析字典值
     *
     * @param dictValue 字典值
     * @param dictType 字典类型
     * @param separator 分隔符
     * @return 字典标签
     */
    public static String convertDictByExp(String dictValue, String dictType, String separator)
    {
        return DictUtils.getDictLabel(dictType, dictValue, separator);
    }

    /**
     * 反向解析值字典值
     *
     * @param dictLabel 字典标签
     * @param dictType 字典类型
     * @param separator 分隔符
     * @return 字典值
     */
    public static String reverseDictByExp(String dictLabel, String dictType, String separator)
    {
        return DictUtils.getDictValue(dictType, dictLabel, separator);
    }

    /**
     * 编码文件名
     */
    public String encodingFilename(String filename)
    {
        return UUID.randomUUID() + "_" + filename + ".xlsx";
    }

    /**
     * 获取下载路径
     *
     * @param filename 文件名称
     */
    public String getAbsoluteFile(String filename)
    {
        String downloadPath = RuoYiConfig.getDownloadPath() + filename;
        File desc = new File(downloadPath);
        if (!desc.getParentFile().exists())
        {
            desc.getParentFile().mkdirs();
        }
        return downloadPath;
    }

    /**
     * 字段信息内部类
     */
    private static class FieldInfo
    {
        Field field;
        Excel excel;

        FieldInfo(Field field, Excel excel)
        {
            this.field = field;
            this.excel = excel;
        }
    }

    /**
     * 自定义样式处理器
     */
    private class ExcelStyleHandler implements CellWriteHandler
    {
        private List<FieldInfo> fields;

        ExcelStyleHandler(List<FieldInfo> fields)
        {
            this.fields = fields;
        }

        @Override
        public void afterCellDispose(WriteSheetHolder writeSheetHolder,
            WriteTableHolder writeTableHolder,
            List<com.alibaba.excel.metadata.data.WriteCellData<?>> cellDataList,
            Cell cell,
            com.alibaba.excel.metadata.Head head,
            Integer relativeRowIndex,
            Boolean isHead)
        {
            // EasyExcel 3.x 样式处理
            // 样式可以根据注解配置在子类中扩展
        }
    }
}
