package com.izouma.nineth.utils.excel;

import cn.hutool.core.util.ClassUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.row.SimpleRowHeightStyleStrategy;
import io.swagger.annotations.ApiModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.ss.usermodel.*;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

@Slf4j
public class ExcelUtils<T> {

    public static List<Converter> cacheConverterList = new ArrayList<>();

    static{
        Set<Class<?>> clazzSet = ClassUtil.scanPackage("com.izouma.nineth.enums");
        clazzSet.forEach(clazz -> {
            if(!Enum.class.isAssignableFrom(clazz)){
                return;
            }

            cacheConverterList.add(new Converter() {
                @Override
                public Class supportJavaTypeKey() {
                    return clazz;
                }

                @Override
                public CellDataTypeEnum supportExcelTypeKey() {
                    return CellDataTypeEnum.STRING;
                }

                @Override
                public Object convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
                    return null;
                }

                @Override
                public CellData convertToExcelData(Object value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
                    try {
                        Method mo = value.getClass().getMethod("getDescription");
                        Object cellValue = mo.invoke(value);
                        return new CellData(CellDataTypeEnum.STRING, Objects.toString(cellValue));
                    } catch (Exception e) {
                        log.error("convertError:" + e.getMessage());
                        return null;
                    }
                }
            });
        });
    }

    public static <T> void export(HttpServletResponse response, List<T> data) throws IOException {
        String fileName = "data.xlsx";
        Class<?> classHead = null;
        if (data != null && !data.isEmpty()) {
            fileName = data.get(0).getClass().getSimpleName() + ".xlsx";
            ApiModel apiModel = data.get(0).getClass().getAnnotation(ApiModel.class);
            if (apiModel != null) {
                if (StringUtils.isNotEmpty(apiModel.value())) {
                    fileName = apiModel.value() + ".xlsx";
                }
            }

            classHead = data.get(0).getClass();
        }
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        response.setHeader("File-Name", URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));

        ExcelWriterSheetBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream(), classHead)
                .sheet("sheet")
                .registerWriteHandler(customStyle())
                .registerWriteHandler(new SimpleRowHeightStyleStrategy((short) 20, (short) 20))
                .registerConverter(new LocalDateConverter())
                .registerConverter(new LocalDateTimeConverter())
                .registerConverter(new YearMonthConverter())
                .registerConverter(new BooleanConverter())
                .registerConverter(new BigIntegerConverter())
                .registerConverter(new LongStringConverter())
                .registerConverter(new ListConverter());

                //注册枚举转换
                cacheConverterList.forEach(converter -> {
                    excelWriterBuilder.registerConverter(converter);
                });
        excelWriterBuilder.doWrite(data);
    }

    public static <T> void export(HttpServletResponse response, List<T> data, Class<?> head) throws IOException {
        String fileName = "data.xlsx";
        if (head != null) {
            fileName = head.getSimpleName() + ".xlsx";

            ApiModel apiModel = head.getAnnotation(ApiModel.class);
            if (apiModel != null) {
                if (StringUtils.isNotEmpty(apiModel.value())) {
                    fileName = apiModel.value() + ".xlsx";
                }
            }
        }

        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        response.setHeader("File-Name", URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));


        ExcelWriterSheetBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream(), head)
                .sheet("sheet")
                .registerWriteHandler(customStyle())
                .registerWriteHandler(new SimpleRowHeightStyleStrategy((short) 20, (short) 20))
                .registerConverter(new LocalDateConverter())
                .registerConverter(new LocalDateTimeConverter())
                .registerConverter(new YearMonthConverter())
                .registerConverter(new BooleanConverter())
                .registerConverter(new BigIntegerConverter())
                .registerConverter(new LongStringConverter())
                .registerConverter(new ListConverter());

        //注册枚举转换
        cacheConverterList.forEach(converter -> {
            excelWriterBuilder.registerConverter(converter);
        });
        excelWriterBuilder.doWrite(data);
    }


    /**
     * 从Excel中读取文件，读取的文件是一个DTO类，该类必须继承BaseRowModel
     * 具体实例参考 ： MemberMarketDto.java
     * 参考：https://github.com/alibaba/easyexcel
     * 字符流必须支持标记，FileInputStream 不支持标记，可以使用BufferedInputStream 代替
     * BufferedInputStream bis = new BufferedInputStream(new FileInputStream(...));
     */
    public static <T extends BaseRowModel> List<T> readExcel(final InputStream inputStream, final Class<? extends BaseRowModel> clazz, int sheetNo, int headLineMun) {
        if (null == inputStream) {
            throw new NullPointerException("the inputStream is null!");
        }
        ExcelListener<T> listener = new ExcelListener<>();
        // 这里因为EasyExcel-1.1.1版本的bug，所以需要选用下面这个标记已经过期的版本
        ExcelReader reader = new ExcelReader(inputStream, valueOf(inputStream), null, listener);
        reader.read(new Sheet(sheetNo, headLineMun, clazz));

        return listener.getRows();
    }


    public static void writeExcel(final File file, List<? extends BaseRowModel> list) {
        try (OutputStream out = new FileOutputStream(file)) {
            ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
            //写第一个sheet,  有模型映射关系
            Class<? extends BaseRowModel> t = list.get(0).getClass();
            Sheet sheet = new Sheet(1, 0, t);
            writer.write(list, sheet);
            writer.finish();
        } catch (IOException e) {
            log.warn("fail to write to excel file: file[{}]", file.getName(), e);
        }
    }


    /**
     * 根据输入流，判断为xls还是xlsx，该方法原本存在于easyexcel 1.1.0 的ExcelTypeEnum中。
     */
    public static ExcelTypeEnum valueOf(InputStream inputStream) {
        try {
            FileMagic fileMagic = FileMagic.valueOf(inputStream);
            if (FileMagic.OLE2.equals(fileMagic)) {
                return ExcelTypeEnum.XLS;
            }
            if (FileMagic.OOXML.equals(fileMagic)) {
                return ExcelTypeEnum.XLSX;
            }
            throw new IllegalArgumentException("excelTypeEnum can not null");

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static HorizontalCellStyleStrategy customStyle() {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontName("微软雅黑");
        headWriteFont.setBold(false);
        headWriteFont.setFontHeightInPoints((short) 12);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontName("微软雅黑");
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short) 12);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

    public static CellStyle contentCellStyle(Workbook workbook) {
        // 正文样式
        Font cellStyleFont = workbook.createFont();
        cellStyleFont.setFontHeightInPoints((short) 12);
        cellStyleFont.setColor(IndexedColors.BLACK.getIndex());
        cellStyleFont.setFontName("微软雅黑");

        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER); // 居中设置
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setWrapText(true);
        cellStyle.setBorderRight(BorderStyle.THIN);
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setFont(cellStyleFont);
        return cellStyle;
    }

    public static CellStyle headerCellStyle(Workbook workbook) {
        // 文件头样式
        CellStyle headerStyle = workbook.createCellStyle();
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // 前景色
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 颜色填充方式
        headerStyle.setWrapText(true);
        headerStyle.setBorderRight(BorderStyle.THIN); // 设置边界
        headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderTop(BorderStyle.THIN);
        headerStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        Font headerFont = workbook.createFont();
        headerFont.setFontHeightInPoints((short) 12);
        headerFont.setColor(IndexedColors.BLACK.getIndex());
        headerFont.setFontName("微软雅黑");
        headerStyle.setFont(headerFont);
        return headerStyle;
    }

    public static CellStyle titleCellStyle(Workbook workbook) {
        // 文件头样式
        CellStyle titleStyle = workbook.createCellStyle();
        titleStyle.setAlignment(HorizontalAlignment.CENTER); // 水平对齐
        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直对齐
        titleStyle.setLocked(true); // 样式锁定
        titleStyle.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex());
        Font titleFont = workbook.createFont();
        titleFont.setFontHeightInPoints((short) 16);
        titleFont.setBold(true);
        titleFont.setFontName("微软雅黑");
        titleStyle.setFont(titleFont);
        return titleStyle;
    }
}
