package com.sckw.excel.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.fastjson.JSONObject;
import com.sckw.core.common.enums.NumberConstant;
import com.sckw.core.model.constant.Global;
import com.sckw.core.web.response.HttpResult;
import com.sckw.excel.annotation.ExcelContext;
import com.sckw.excel.annotation.ExcelSelected;
import com.sckw.excel.config.easyexcel.*;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * 封装easyexcel
 *
 * @author LengFaQiang
 * @version v1
 * @create 2020-09-24 10:15:49
 * @copyright
 */
@Slf4j
public class ExcelUtil {
    /**
     * 数据导出
     *
     * @param response
     * @param clazz
     * @param data
     * @param <T>
     */
    public static <T> void downData(HttpServletResponse response, Class<T> clazz, List<T> data) {
        Assert.notNull(clazz, "clazz can't be null");
        Assert.isTrue(data != null && !data.isEmpty(), "data can't be empty");
        try {
            ExcelContext excelContext = clazz.getAnnotation(ExcelContext.class);
            buildResponse(response, excelContext.fileName());
            EasyExcel.write(response.getOutputStream(), clazz)
                    .autoCloseStream(Boolean.FALSE)
                    .registerConverter(new LongStringConverter())
                    //设置导出excel的单元格格式为文本
                    .registerWriteHandler(new RowWriteHandlerImpl())
                    // 自定义列宽、行高
                    .registerWriteHandler(new CustomCellWeightWeightConfig())
                    .sheet(excelContext.sheetName()).doWrite(data);
        } catch (Exception e) {
            buildResponse(response, e);
        }
    }

    public static <T> void download(HttpServletResponse response, Class<T> clazz, List<T> data) {
//        StaticComponentContainer.Modules.exportAllToAll();
        Assert.notNull(clazz, "clazz can't be null");
        Assert.isTrue(data != null && !data.isEmpty(), "data can't be empty");
        try {
            ExcelContext excelContext = clazz.getAnnotation(ExcelContext.class);
            buildResponse(response, excelContext.fileName());
            EasyExcel.write(response.getOutputStream(), clazz)
                    .registerConverter(new LocalDateTimeConverter())
                    .registerWriteHandler(new RowWriteHandlerImpl())
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .autoCloseStream(Boolean.FALSE).sheet(excelContext.sheetName())
                    .doWrite(data);
        } catch (Exception e) {
            buildResponse(response, e);
        }
    }

    public static void download(HttpServletResponse response, List<List> source) {
        Assert.isTrue(source != null && source.size() > 0, "source can't be empty");
        ExcelWriter excelWriter = null;
        try {
            String fileName = source.get(NumberConstant.ZERO).get(NumberConstant.ZERO).getClass().getAnnotation(ExcelContext.class).fileName();
            buildResponse(response, fileName);
            excelWriter = EasyExcel.write(response.getOutputStream())
                    .registerConverter(new LocalDateTimeConverter())
                    .registerWriteHandler(new RowWriteHandlerImpl())
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .autoCloseStream(Boolean.FALSE).build();
            for (int i = 0; i < source.size(); i++) {
                List list = source.get(i);
                //Assert.isTrue(!CollectionUtils.isEmpty(list), "source can't be empty");
                if (CollectionUtils.isEmpty(list)) {
                    continue;
                }
                Class<?> clazz = list.get(NumberConstant.ZERO).getClass();
                ExcelContext context = clazz.getAnnotation(ExcelContext.class);
                WriteSheet writeSheet = EasyExcel.writerSheet(i, context.sheetName()).registerWriteHandler(new RowWriteHandlerImpl()).head(clazz).build();
                excelWriter.write(list, writeSheet);
            }
        } catch (Exception e) {
            buildResponse(response, e);
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }

    }

    private static void buildResponse(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
        fileName = URLEncoder.encode(fileName + "-" + DateUtil.localDateTimeToString(LocalDateTime.now()), "UTF-8").replaceAll("\\+", "%20");
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Access-Control-Expose-Headers", "Content-disposition");
        //下面 浏览器下载正常，但是postman下载文件名称乱码
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        //下面这是用postman测名称正常，换到浏览器会多了filename_=utf-8等字样。。）
//        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")+ ".xlsx" + ";filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8")+ ".xlsx");
    }

    private static void buildResponse(HttpServletResponse response, Exception e) {
        StackTraceElement[] stackTrace = e.getStackTrace();
        log.error("Failed to export data. -> {}", String.valueOf(stackTrace[0]) + stackTrace[1] + stackTrace[2], e);
        //response.reset();
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.println(JSONObject.toJSONString(HttpResult.error("导出数据失败, " + e.toString())));
        } catch (Exception ignored) {
        }
    }

    /**
     * 判断是否是excel
     *
     * @param file 文件
     * @return 获取结果
     */
    public static boolean checkExcel(MultipartFile file) {
        String fileName = file.getOriginalFilename();
        String match1 = "^.+\\.(?i)(xls)$";
        String match2 = "^.+\\.(?i)(xlsx)$";
        return !fileName.matches(match1) && !fileName.matches(match2);
    }

    /**
     * 导出单元格内容含有下拉框的excel 需要使用@ExcelSelected在对应字段上设置字段的下拉框的值
     *
     * @param response
     * @param source
     */
    public static void writeSelectedSheet(HttpServletResponse response, List<List> source) {
        Assert.isTrue(source != null && source.size() > 0, "source can't be empty");
        ExcelWriter excelWriter = null;
        try {
//            String fileName = source.get(ZERO).get(ZERO).getClass().getAnnotation(ExcelContext.class).fileName();
            String fileName = source.get(NumberConstant.ZERO).get(NumberConstant.ZERO).getClass().getAnnotation(ExcelContext.class).fileName();
            buildResponse(response, fileName);
            excelWriter = EasyExcel.write(response.getOutputStream())
                    .registerConverter(new LocalDateTimeConverter())
                    .registerWriteHandler(new RowWriteHandlerImpl())
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .autoCloseStream(Boolean.FALSE).build();
            for (int i = 0; i < source.size(); i++) {
                List list = source.get(i);
                //Assert.isTrue(!CollectionUtils.isEmpty(list), "source can't be empty");
                if (CollectionUtils.isEmpty(list)) {
                    continue;
                }
                Class<?> clazz = list.get(NumberConstant.ZERO).getClass();
                ExcelContext context = clazz.getAnnotation(ExcelContext.class);
                Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(clazz);
                WriteSheet writeSheet = EasyExcel.writerSheet(i, context.sheetName()).head(clazz).registerWriteHandler(new RowWriteHandlerImpl()).registerWriteHandler(new SelectedSheetWriteHandler(selectedMap)).build();
                excelWriter.write(list, writeSheet);
            }
        } catch (Exception e) {
            buildResponse(response, e);
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }

    }

    //    private static void buildResponse(HttpServletResponse response, String fileName,HttpServletRequest request) throws UnsupportedEncodingException {
//        response.reset();
//        response.setContentType("application/octet-stream");
//        response.setCharacterEncoding("utf-8");
//        //解决不同浏览器压缩包名字含有中文时乱码的问题
//        String agent = request.getHeader("USER-AGENT");
//        try {
//            if (agent.contains("MSIE") || agent.contains("Trident")) {
//                fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
//            } else {
//                fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
//            }
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        fileName = URLEncoder.encode(fileName + "-" + DateUtil.dateToStr(LocalDate.now()), "UTF-8").replaceAll("\\+", "%20");
//        response.setContentType("application/vnd.ms-excel");
//        response.setCharacterEncoding("UTF-8");
////        response.setHeader("Content-Disposition",String.format("attachment; filename=\"%s\"", fileName));
//        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
//    }

//    private static void buildResponse(HttpServletResponse response, String fileName, HttpServletRequest request) throws IOException {
//        response.setContentType("application/vnd.ms-excel");
//        response.setCharacterEncoding("utf-8");
//        // 这里URLEncoder.encode可以防止中文乱码，所有通过后端的文件下载都可以如此处理
//        fileName = URLEncoder.encode(fileName, "UTF-8");
//        //建议加上该段，否则可能会出现前端无法获取Content-disposition
//        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
//        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
////        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
//    }

    /**
     * 解析表头类中的下拉注解
     *
     * @param head 表头类
     * @param <T>  泛型
     * @return Map<下拉框列索引, 下拉框内容> map
     */
    private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {
        Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);

        // getDeclaredFields(): 返回全部声明的属性；getFields(): 返回public类型的属性
        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) {
                ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected);
                if (source != null && source.length > 0) {
                    excelSelectedResolve.setSource(source);
                    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;
    }

}