package com.unis.framework.excel.core.util;

import cn.hutool.core.io.FileUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.enums.WriteDirectionEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.unis.framework.excel.core.handler.MyExcelMergeCellHandler;
import com.unis.framework.excel.core.handler.SelectSheetWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import static com.unis.framework.common.exception.enums.GlobalErrorCodeConstants.EXCEL_ERROR;
import static com.unis.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * Excel 工具类
 *
 * @author 芋道源码
 */
@Slf4j
@Component
public class ExcelUtils {

    public static final String EXCEL_PATH = "file" + File.separator + "excel" + File.separator;

    @Value("${delivery.linux_excel_path}")
    private String linuxExcelPath;
    @Value("${spring.profiles.active}")
    private String profilesActive;

    public static String LINUX_EXCEL_PATH;

    @PostConstruct
    public void init() {
        if("local".equals(profilesActive)){
            LINUX_EXCEL_PATH = getXlsPath();
        }else{
            LINUX_EXCEL_PATH = linuxExcelPath;
        }
    }

    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param <T>       泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data) throws IOException {
        // 输出 Excel
        EasyExcel.write(response.getOutputStream(), head)
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度，自动适配。最大 255 宽度
                .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
                .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
                .sheet(sheetName).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        return EasyExcel.read(file.getInputStream(), head, null)
                .autoCloseStream(false)  // 不要自动关闭，交给 Servlet 自己处理
                .doReadAllSync();
    }


    /**
     *  获取模板文件路径，基于项目路径(返回 server 路径下的，不是当前模块，所以不能用）
     * @return 文件路径
     */
    public static String getXlsPath() {
        return rootPath() + EXCEL_PATH;
    }
    public static String rootPath() {
        return ExcelUtils.class.getResource("/").getPath();
    }

    /**
     * 根据模板生成Excel文件
     *
     * @param response 返回文件流
     * @param fileName 文件名  a.xlsx
     * @param templateFile   模板文件名 t.xlsx
     * @param list0 数据列表
     */
    public static <T1,T2> void listToExcel(HttpServletResponse response,String fileName, String templateFile, List<T1> list0) {
        listToExcel(response,fileName, templateFile, 0,null, list0, null,null);
    }


    /**
     * 根据模板生成Excel文件
     *
     * @param response 返回文件流
     * @param fileName 文件名  a.xlsx
     * @param templateFile   模板文件名 t.xlsx
     * @param firstIndex   两行相同数据合并单元格，如果ABC列需要合并，传3
     * @param strnumToNumColumnIndex 有的数字传的string类型，需要把单元格格式改成数字格式，比如AB列需要改成数字格式，传0，1
     * @param list0,list1    多个list 第二个list不配置创建新行 list0 必填
     */
    public static <T1,T2> void listToExcel(HttpServletResponse response,String fileName, String templateFile, int firstIndex, List<Integer> strnumToNumColumnIndex,
                                           List<T1> list0, List<T2> list1) {
        listToExcel(response,fileName, templateFile, firstIndex,strnumToNumColumnIndex, list0, list1,null);
    }



    /**
     * 根据模板生成Excel文件 - 和上面一样，这个多一个list
     *
     * @param response 返回文件流
     * @param fileName 文件名  a.xlsx
     * @param templateFile   模板文件名 t.xlsx
     * @param firstIndex   两行相同数据合并单元格，如果ABC列需要合并，传3
     * @param strnumToNumColumnIndex 有的数字传的string类型，需要把单元格格式改成数字格式，比如AB列需要改成数字格式，传0，1
     * @param list0,list1    多个list 第二个list不配置创建新行
     */
    public static <T1,T2,T3> void listToExcel(HttpServletResponse response, String fileName,String templateFile, int firstIndex, List<Integer> strnumToNumColumnIndex,
                                              List<T1> list0, List<T2> list1, List<T3> list2) {
        try{

            InputStream tem = ClassUtils.getDefaultClassLoader().getResourceAsStream(EXCEL_PATH + templateFile);
            // 读模板，写文件
            ExcelWriter excelWriter = EasyExcel.write(getOutputStream(fileName,response))
                    // 不要自动关闭，交给 Servlet 自己处理
//                    .autoCloseStream(false)
                    // 注入处理器 合并单元格的 可以注册到下面 sheet 上
                    .registerWriteHandler(new MyExcelMergeCellHandler(firstIndex,strnumToNumColumnIndex))
                    // 这里是设置单元格自动换行
                    .registerWriteHandler(getHorizontalCellStyleStrategy())
                    .withTemplate(tem).build();

            // 创建writeSheet
            WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
            // 填写配置，forceNewRow true表示自动创建一行，后面的数据后移
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            //填写数据 name是模板key  {a.name}
            // 多个list，传入标识符key 和list

            if(list0 != null && !list0.isEmpty()){
                excelWriter.fill(new FillWrapper("list0",list0), fillConfig, writeSheet);
            }
            if(list1 != null && !list1.isEmpty()){
                excelWriter.fill(new FillWrapper("list1",list1), writeSheet);
            }
            if(list2 != null && !list2.isEmpty()){
                excelWriter.fill(new FillWrapper("list2",list2), writeSheet);
            }
            // 关闭填写
            excelWriter.finish();
        }catch (Exception e){
            log.error("生成 Excel异常", e);
            throw exception(EXCEL_ERROR);
        }
    }

    /**
     * 简单横向填充
     *
     * @param fileName 文件名  a.xlsx
     * @param templateFile   模板文件名 t.xlsx
     * @param list0,list1    多个list 第二个list不配置创建新行
     */
    public static <T1> void listToExcelHorizontal(String fileName,String templateFile, List<T1> list0) {
        try{
            //如果文件存在，先删除
            if (FileUtil.exist(LINUX_EXCEL_PATH + fileName)) {
                FileUtil.del(LINUX_EXCEL_PATH + fileName);
            }
            // 读模板，写文件
            ExcelWriterBuilder write = EasyExcel.write(LINUX_EXCEL_PATH + fileName);
            ExcelWriter excelWriter = write
                    // 不要自动关闭，交给 Servlet 自己处理
//                    .autoCloseStream(false)
                    // 这里是设置单元格自动换行
                    .registerWriteHandler(getHorizontalCellStyleStrategy())
                    .withTemplate(LINUX_EXCEL_PATH + templateFile).build();

            // 创建writeSheet
            WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
            // 填写配置，forceNewRow true表示自动创建一行，后面的数据后移
            FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).forceNewRow(Boolean.TRUE).build();
            //填写数据 name是模板key  {a.name}

            if(list0 != null && !list0.isEmpty()){
                excelWriter.fill(new FillWrapper("list0",list0), fillConfig, writeSheet);
            }
            // 关闭填写
            excelWriter.finish();
        }catch (Exception e){
            log.error("生成 Excel异常", e);
            throw exception(EXCEL_ERROR);
        }
    }

    /**
     * 根据模板生成Excel文件 -
     *
     * @param response 返回文件流
     * @param fileName 文件名  a.xlsx
     * @param templateFile   模板文件名 t.xlsx
     * @param list0,list1    多个list 第二个list不配置创建新行
     */
    public static <T1> void listToExcelByMyTableHeader(HttpServletResponse response, String fileName, String templateFile,
                                                       List<T1> list0) {
        try{
            // 读模板，写文件
            ExcelWriter excelWriter = EasyExcel.write(getOutputStream(fileName,response) )
                    // 不要自动关闭，交给 Servlet 自己处理
//                    .autoCloseStream(false)
                    // 这里是设置单元格自动换行
                    .registerWriteHandler(getHorizontalCellStyleStrategy())
                    .withTemplate(LINUX_EXCEL_PATH + templateFile).build();

            // 创建writeSheet
            WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
            // 填写配置，forceNewRow true表示自动创建一行，后面的数据后移
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            //填写数据 name是模板key  {a.name}
            // 多个list，传入标识符key 和list

            if(list0 != null && !list0.isEmpty()){
                excelWriter.fill(new FillWrapper("list0",list0), fillConfig, writeSheet);
            }
            // 关闭填写
            excelWriter.finish();
        }catch (Exception e){
            log.error("生成 Excel异常", e);
            throw exception(EXCEL_ERROR);
        }
    }

    private static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        contentWriteCellStyle.setWrapped(true);
        return new HorizontalCellStyleStrategy(null, contentWriteCellStyle);
    }


    /**
     * 导出文件时为Writer生成OutputStream.
     *
     * @param fileName 文件名
     * @param response response
     * @return ""
     */
    public static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
        try {
            fileName = URLEncoder.encode(fileName, "UTF-8");
            // 为了解决多浏览器文件名乱码。
            String agent = response.getHeader("USER-AGENT");
            //IE请求头的user-agent信息开始靠近火狐，这里只能比对ie与火狐的agen区分浏览器了
            if (agent != null) {
                if (agent.contains("Mozilla")) {
                    fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), "ISO8859-1");
                }
            }

            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/vnd.ms-excel");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
            response.setHeader("Pragma", "public");
            response.setHeader("Cache-Control", "no-store");
            response.addHeader("Cache-Control", "max-age=0");
            return response.getOutputStream();
        } catch (IOException e) {
            throw new Exception("导出excel表格失败!", e);
        }
    }

    public static void exportExcelFileToWeb(String fileName, HttpServletResponse response) throws Exception {
        String filePath = LINUX_EXCEL_PATH + fileName;
        try (
                InputStream inputStream = Files.newInputStream(Paths.get(filePath));
                OutputStream outputStream = ExcelUtils.getOutputStream(fileName, response)
        ) {
            IOUtils.copy(inputStream, outputStream);
            outputStream.flush();
        } catch (IOException e) {
            throw new Exception("导出excel表格失败!", e);
        }
    }

}
