package com.ruoyi.info.collection.handler;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.utils.DictUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;

import java.lang.reflect.Field;
import java.util.*;

/**
 * EasyExcel字典下拉框写入处理器
 * 在Excel模板生成时，为标注了@DictDropdown注解的字段添加下拉框
 *
 * @author ruoyi
 */
@Slf4j
public class DictDropdownWriteHandler implements SheetWriteHandler {

    /**
     * Excel下拉列表直接写入的最大字符数限制
     */
    private static final int MAX_DIRECT_LENGTH = 255;

    /**
     * 实体类Class对象
     */
    private final Class<?> modelClass;

    /**
     * 构造函数
     *
     * @param modelClass 实体类Class对象
     */
    public DictDropdownWriteHandler(Class<?> modelClass) {
        this.modelClass = modelClass;
    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 获取工作簿和工作表
        Workbook workbook = writeWorkbookHolder.getWorkbook();
        Sheet sheet = writeSheetHolder.getSheet();

        // 创建数据验证助手
        DataValidationHelper validationHelper = sheet.getDataValidationHelper();

        // 解析实体类中的字段及其注解
        Map<Integer, DictDropdown> dropdownMap = parseDropdownFields();

        // 为每个需要下拉框的字段添加数据验证
        for (Map.Entry<Integer, DictDropdown> entry : dropdownMap.entrySet()) {
            Integer columnIndex = entry.getKey();
            DictDropdown dropdown = entry.getValue();

            try {
                // 获取字典数据
                List<SysDictData> dictDataList = getDictData(dropdown.dictType());
                if (dictDataList == null || dictDataList.isEmpty()) {
                    log.warn("字典类型[{}]没有可用数据，跳过下拉框创建", dropdown.dictType());
                    continue;
                }

                // 获取下拉选项列表
                String[] dropdownOptions = extractOptions(dictDataList, dropdown.displayType());
                if (dropdownOptions.length == 0) {
                    log.warn("字典类型[{}]提取选项为空，跳过下拉框创建", dropdown.dictType());
                    continue;
                }

                // 创建数据验证
                DataValidation validation = createDataValidation(
                    workbook,
                    sheet,
                    validationHelper,
                    columnIndex,
                    dropdownOptions,
                    dropdown.hiddenSheetName(),
                    dropdown.strict()
                );

                if (validation != null) {
                    sheet.addValidationData(validation);
                    log.info("成功为列[{}]添加字典[{}]的下拉框，选项数量：{}", columnIndex, dropdown.dictType(), dropdownOptions.length);
                }
            } catch (Exception e) {
                log.error("为列[{}]添加字典[{}]下拉框失败：{}", columnIndex, dropdown.dictType(), e.getMessage(), e);
            }
        }
    }

    /**
     * 解析实体类中的字段，获取需要添加下拉框的字段及其注解
     *
     * @return 字段索引与注解的映射
     */
    private Map<Integer, DictDropdown> parseDropdownFields() {
        Map<Integer, DictDropdown> result = new HashMap<>();

        // 获取所有字段（包括父类的）
        List<Field> fields = getAllFields(modelClass);

        for (Field field : fields) {
            // 检查是否有@DictDropdown注解
            DictDropdown dropdown = field.getAnnotation(DictDropdown.class);
            if (dropdown == null) {
                continue;
            }

            // 获取列索引
            Integer columnIndex = getColumnIndex(field);
            if (columnIndex == null) {
                log.warn("字段[{}]没有指定@ExcelProperty的index，跳过下拉框创建", field.getName());
                continue;
            }

            result.put(columnIndex, dropdown);
        }

        return result;
    }

    /**
     * 获取类的所有字段（包括父类的）
     *
     * @param clazz 类对象
     * @return 字段列表
     */
    private List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null && clazz != Object.class) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    /**
     * 获取字段对应的列索引
     *
     * @param field 字段对象
     * @return 列索引
     */
    private Integer getColumnIndex(Field field) {
        ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
        if (excelProperty != null && excelProperty.index() >= 0) {
            return excelProperty.index();
        }
        return null;
    }

    /**
     * 获取字典数据
     *
     * @param dictType 字典类型
     * @return 字典数据列表
     */
    private List<SysDictData> getDictData(String dictType) {
        try {
            // 先从缓存获取
            List<SysDictData> dictDataList = DictUtils.getDictCache(dictType);

            if (dictDataList == null || dictDataList.isEmpty()) {
                log.warn("从缓存获取字典[{}]数据为空", dictType);
            }

            return dictDataList;
        } catch (Exception e) {
            log.error("获取字典[{}]数据失败：{}", dictType, e.getMessage(), e);
            return Collections.emptyList();
        }
    }

    /**
     * 从字典数据中提取下拉选项
     *
     * @param dictDataList 字典数据列表
     * @param displayType  显示类型
     * @return 下拉选项数组
     */
    private String[] extractOptions(List<SysDictData> dictDataList, DictDropdown.DisplayType displayType) {
        List<String> options = new ArrayList<>();

        for (SysDictData dictData : dictDataList) {
            String optionValue;
            if (displayType == DictDropdown.DisplayType.VALUE) {
                optionValue = dictData.getDictValue();
            } else {
                optionValue = dictData.getDictLabel();
            }

            if (optionValue != null && !optionValue.isEmpty()) {
                options.add(optionValue);
            }
        }

        return options.toArray(new String[0]);
    }

    /**
     * 创建数据验证
     *
     * @param workbook        工作簿
     * @param sheet           工作表
     * @param validationHelper 数据验证助手
     * @param columnIndex     列索引
     * @param options         下拉选项
     * @param hiddenSheetName 隐藏Sheet名称
     * @param strict          是否严格模式
     * @return 数据验证对象
     */
    private DataValidation createDataValidation(
        Workbook workbook,
        Sheet sheet,
        DataValidationHelper validationHelper,
        int columnIndex,
        String[] options,
        String hiddenSheetName,
        boolean strict
    ) {
        // 计算选项字符串总长度
        int totalLength = Arrays.stream(options)
            .mapToInt(String::length)
            .sum() + options.length - 1; // 加上分隔符

        // 设置数据验证的范围（从第2行开始，第1行是表头）
        int firstRow = 1; // 从第2行开始（0-based索引）
        int lastRow = sheet.getLastRowNum() + 1000; // 扩展到足够多的行
        if (lastRow < 100) {
            lastRow = 100; // 至少100行
        }
        CellRangeAddressList addressList = new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex);

        // 判断是否需要使用隐藏Sheet
        if (totalLength > MAX_DIRECT_LENGTH) {
            // 使用隐藏Sheet存储选项
            return createHiddenSheetValidation(workbook, sheet, validationHelper, addressList, options, hiddenSheetName, strict);
        } else {
            // 直接创建下拉列表
            return createDirectValidation(validationHelper, addressList, options, strict);
        }
    }

    /**
     * 使用隐藏Sheet创建数据验证（适用于选项较多的情况）
     *
     * @param workbook         工作簿
     * @param sheet            主工作表
     * @param validationHelper 数据验证助手
     * @param addressList      单元格范围
     * @param options          下拉选项
     * @param hiddenSheetName  隐藏Sheet名称
     * @param strict           是否严格模式
     * @return 数据验证对象
     */
    private DataValidation createHiddenSheetValidation(
        Workbook workbook,
        Sheet sheet,
        DataValidationHelper validationHelper,
        CellRangeAddressList addressList,
        String[] options,
        String hiddenSheetName,
        boolean strict
    ) {
        try {
            // 创建或获取隐藏Sheet
            Sheet hiddenSheet = workbook.getSheet(hiddenSheetName);
            if (hiddenSheet == null) {
                hiddenSheet = workbook.createSheet(hiddenSheetName);
            }

            // 写入选项到隐藏Sheet
            Row row = hiddenSheet.createRow(0);
            for (int i = 0; i < options.length; i++) {
                Cell cell = row.createCell(i);
                cell.setCellValue(options[i]);
            }

            // 构建公式引用
            String formula = String.format("%s!$A$1:$%s$1", hiddenSheetName,
                (char) ('A' + options.length - 1));

            // 创建列表验证
            DataValidationConstraint constraint = validationHelper.createFormulaListConstraint(formula);

            // 设置验证属性
            DataValidation validation = validationHelper.createValidation(constraint, addressList);
            validation.setSuppressDropDownArrow(true);
            validation.setErrorStyle(strict ? DataValidation.ErrorStyle.STOP : DataValidation.ErrorStyle.WARNING);
            validation.setShowErrorBox(true);

            // 隐藏Sheet（在Excel中无法通过界面隐藏，需要通过代码设置）
            workbook.setSheetHidden(workbook.getSheetIndex(hiddenSheetName), true);

            return validation;
        } catch (Exception e) {
            log.error("创建隐藏Sheet数据验证失败：{}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 直接创建下拉列表验证（适用于选项较少的情况）
     *
     * @param validationHelper 数据验证助手
     * @param addressList      单元格范围
     * @param options          下拉选项
     * @param strict           是否严格模式
     * @return 数据验证对象
     */
    private DataValidation createDirectValidation(
        DataValidationHelper validationHelper,
        CellRangeAddressList addressList,
        String[] options,
        boolean strict
    ) {
        // 创建显式列表验证
        DataValidationConstraint constraint = validationHelper.createExplicitListConstraint(options);

        // 创建数据验证
        DataValidation validation = validationHelper.createValidation(constraint, addressList);

        // 设置验证属性
        validation.setSuppressDropDownArrow(true);
        validation.setErrorStyle(strict ? DataValidation.ErrorStyle.STOP : DataValidation.ErrorStyle.WARNING);
        validation.setShowErrorBox(true);

        return validation;
    }
}
