package com.testor.common.util.excel;

import cn.hutool.core.collection.IterUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.testor.common.annotation.ExcelSelected;
import com.testor.common.excel.DefaultExcelListener;
import com.testor.common.excel.ExcelResult;
import com.testor.common.excel.SheetData;
import com.testor.common.excel.select.ExcelDynamicSelect;
import com.testor.common.excel.select.ExcelSelectedResolve;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.http.HttpHeaders;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class EasyExcelUtil {

    public static final Map<String, ExcelDynamicSelect<?>> EXCEL_SELECT_MAPPER = new ConcurrentHashMap<>();



    /**
     * 使用校验监听器 异步导入 同步返回
     *
     * @param is         输入流
     * @param clazz      对象类型
     * @param isValidate 是否 Validator 检验 默认为是
     * @return 转换后集合
     */
    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
        DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
        EasyExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 导出单sheet页的excel文件
     *
     * @param response  HttpServletResponse
     * @param fileName  文件名
     * @param sheetName sheet页名
     * @param data      要导出的数据
     */
    public static <T> void writeExcel(HttpServletResponse response, String fileName, String sheetName, List<T> data) {
        try {
            encodeFileName(response, fileName);
            EasyExcel.write(response.getOutputStream(), IterUtil.getElementType(data))
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .sheet(StringUtils.isEmpty(sheetName) ? "Sheet1" : sheetName)
                    .doWrite(data);
        } catch (IOException e) {
            log.error("导出excel文件异常", e);
        }
    }

    /**
     * 导出单sheet页且sheet页中含有下拉框的excel文件
     *
     * @param response  HttpServletResponse
     * @param fileName  文件名
     * @param sheetName sheet页名
     * @param data      要导出的数据
     */
    public static <T> void writeExcelBySelect(HttpServletResponse response, String fileName, String sheetName, List<T> data) {
        try {
            encodeFileName(response, fileName);
            Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(IterUtil.getElementType(data));
            EasyExcel.write(response.getOutputStream(), IterUtil.getElementType(data))
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .registerWriteHandler(selectedSheetWriteHandler(selectedMap))
                    .sheet(StringUtils.isEmpty(sheetName) ? "Sheet1" : sheetName)
                    .doWrite(data);
        } catch (IOException e) {
            log.error("导出excel文件异常", e);
        }
    }

    /**
     * 导出多sheet页且每个sheet页中含有下拉框的excel文件
     *
     * @param response  HttpServletResponse
     * @param fileName  文件名
     * @param sheetData 多个sheet页的数据，每个元素包含sheet名和数据
     */
    public static <T> void writeExcelBySelectMultiSheet(HttpServletResponse response, String fileName,
                                                        List<SheetData<T>> sheetData) {
        try {
            encodeFileName(response, fileName);
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
            for (SheetData<T> sheet : sheetData) {
                String sheetName = StringUtils.isEmpty(sheet.getSheetName()) ? "Sheet" + sheet.getIndex() : sheet.getSheetName();
                Class<?> dataType = IterUtil.getElementType(sheet.getData());
                Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(dataType);
                // 写入当前sheet页
                WriteSheet writeSheet = EasyExcel.writerSheet(sheet.getIndex(), sheetName)
                        .head(dataType)
                        .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                        .registerWriteHandler(selectedSheetWriteHandler(selectedMap))
                        .build();
                excelWriter.write(sheet.getData(), writeSheet);
            }
            excelWriter.finish();
        } catch (IOException e) {
            log.error("导出excel文件异常", e);
        }
    }

    /**
     * 设置文件名
     *
     * @param response HttpServletResponse
     * @param fileName 文件名
     */
    private static void encodeFileName(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s\"", fileName + ".xlsx"));
        response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
        response.setHeader(HttpHeaders.PRAGMA, "no-cache");
        response.setDateHeader(HttpHeaders.EXPIRES, -1);
    }

    /**
     * 解析表头类中的下拉注解
     *
     * @param head 表头类
     * @return Map<下拉框列索引, 下拉框内容> map
     */
    private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {
        Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>(16);
        Field[] fields = head.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (selected != null) {

                List<String> source = new ArrayList<>();
                //固定下拉框数据
                source.addAll(Arrays.asList(selected.source()));
                String type = selected.type();
                if (StringUtils.isNotBlank(type)) {
                ExcelDynamicSelect<?> excelDynamicSelect = EXCEL_SELECT_MAPPER.get(type);
                List<String> dynamicSource = excelDynamicSelect.getSource(selected.param());
                //动态下拉框数据
                source.addAll(dynamicSource);
                }
                ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
                excelSelectedResolve.setSource(source.toArray(new String[0]));
                excelSelectedResolve.setFirstRow(selected.firstRow());
                excelSelectedResolve.setLastRow(selected.lastRow());

                if (property != null && property.index() >= 0) {
                    selectedMap.put(property.index(), excelSelectedResolve);
                } else {
                    selectedMap.put(i, excelSelectedResolve);
                }
            }
        }
        return selectedMap;
    }

    /**
     * 为excel创建下拉框
     *
     * @param selectedMap 下拉框配置数据 Map<下拉框列索引, 下拉框内容>
     * @return intercepts handle sheet creation
     */
    private static SheetWriteHandler selectedSheetWriteHandler(Map<Integer, ExcelSelectedResolve> selectedMap) {
        return new SheetWriteHandler() {
            @Override
            public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

            }

            @Override
            public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
                Sheet sheet = writeSheetHolder.getSheet();
                DataValidationHelper helper = sheet.getDataValidationHelper();
                selectedMap.forEach((k, v) -> {
                    // 获取下拉框的内容
                    String[] source = v.getSource();
                    if (source.length > 0) {
                        CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);
                        DataValidationConstraint constraint = helper.createExplicitListConstraint(source);
                        DataValidation validation = helper.createValidation(constraint, rangeList);
                        validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
                        validation.setShowErrorBox(true);
                        validation.setSuppressDropDownArrow(true);
                        validation.createErrorBox("提示", "请输入下拉选项中的内容");
                        sheet.addValidationData(validation);
                    }
                });
            }
        };
    }
}

