/*
 * Copyright (c) 2023 codvision.com All Rights Reserved.
 */

package com.codvision.base.support;

import cn.hutool.core.util.ArrayUtil;
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.codvision.base.annotation.ExcelSelected;
import com.codvision.base.config.excel.select.CustomSheetWriteHandler;
import com.codvision.base.config.excel.select.ExcelSelectedResolve;
import lombok.SneakyThrows;

import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author huxb
 * @description: 基于easyExcel的导入导出工具类
 * @create 2023/2/16 9:20
 */
public class EasyExcelUtil<T> {

    /**
     * 普通的单次写入（5000行以内），数据量大的，请使用多次重复写入
     */
    public void simpleWriteExcel(String fileName, String sheetName, List<T> data, Class<T> c) {
        //String fileName = "TestFileUtil.getPath()" + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写，然后写到第一个sheet，名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, c).sheet(sheetName).doWrite(data);
    }


    /**
     * 重复多次写入到同一个sheet
     */
    public void repeatWriteToOneExcel(WriteSheet writeSheet, String fileName, List<T> data, Class<T> c) {
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, c).build()) {
            // 这里注意 如果同一个sheet只要创建一次，外部调用时候传入创建好的sheet
            //WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
            // 分页去数据库查询数据 这里是数据库查询每一页的数据
            excelWriter.write(data, writeSheet);
        }
    }


    /**
     * 重复多次写入到不同的sheet，每个sheet是同一个对象
     */
    public void repeatWriteToManyExcel(String fileName, Integer pageNum, String sheetName, List<T> data, Class<T> c) {
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, c).build()) {
            // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
            WriteSheet writeSheet = EasyExcel.writerSheet(pageNum, sheetName + pageNum).build();
            // 外部传入的实际每次分页去数据库查询的当前页的数据：data。
            // 此时每个sheet里的数据就是每个分页的总数据。
            excelWriter.write(data, writeSheet);
        }
    }


    /**
     * 重复多次写入到不同的sheet，每个sheet可以是不同的对象
     */
    public void repeatWriteToManyExcelObject(String fileName, Integer pageNum, String sheetName, List<T> data, Class<T> c) {
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, c).build()) {
            // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
            WriteSheet writeSheet = EasyExcel.writerSheet(pageNum, sheetName + pageNum).head(c).build();
            // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
            excelWriter.write(data, writeSheet);
        }
    }

    @SneakyThrows
    private static void response(String fileName, HttpServletResponse response) {
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.setDateHeader("Expires", -1);
        //设置响应头部信息，格式为附件，以及文件名
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
    }

    @SneakyThrows
    public static <T> void downloadTemplate(String fileName, String sheetName, Class<T> targetClass, HttpServletResponse response) {
        response(fileName, response);
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), targetClass).build();
        WriteSheet writeSheet = writeSelectedSheet(targetClass, 0, sheetName);
        excelWriter.write(new ArrayList<>(), writeSheet);
        excelWriter.finish();
    }

    @SneakyThrows
    public static <T> void export(String fileName, String sheetName, Class<T> targetClass, HttpServletResponse response, List<T> data) {
        response(fileName, response);
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), targetClass).build();
        WriteSheet writeSheet = writeSelectedSheet(targetClass, 0, sheetName);
        excelWriter.write(data, writeSheet);
        excelWriter.finish();
    }

    /**
     * 创建即将导出的sheet页（sheet页中含有带下拉框的列）
     *
     * @param head      导出的表头信息和配置
     * @param sheetNo   sheet索引
     * @param sheetName sheet名称
     * @return sheet页
     */
    public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName) {
        Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(head);
        return EasyExcel.writerSheet(sheetNo, sheetName)
                .head(head)
                .registerWriteHandler(new CustomSheetWriteHandler(selectedMap))
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .build();
    }

    /**
     * 解析表头类中的下拉框选择注解
     *
     * @param head 表头类
     * @param <T>  泛型
     * @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) {
                ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected);
                if (ArrayUtil.isNotEmpty(source)) {
                    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;
    }
}
