package xyz.wbsite.frame.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.CellStyle;
import xyz.wbsite.frame.excel.annotation.ExcelNote;
import xyz.wbsite.frame.excel.annotation.ExcelSelect;
import xyz.wbsite.frame.excel.annotation.ExcelSheet;
import xyz.wbsite.frame.excel.converter.BooleanConverter;
import xyz.wbsite.frame.excel.exception.TemplateNotMatchException;
import xyz.wbsite.frame.excel.handler.HeadWriteHandler;
import xyz.wbsite.frame.excel.handler.WCellWriteHandler;
import xyz.wbsite.frame.excel.handler.WRowWriteHandler;
import xyz.wbsite.frame.excel.handler.WSheetWriteHandler;
import xyz.wbsite.frame.excel.listener.WReadListener;
import xyz.wbsite.frame.utils.ClassUtil;
import xyz.wbsite.frame.utils.FileUtil;
import xyz.wbsite.frame.utils.ValidationUtil;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class WExcel<T> {

    /**
     * 工作簿名称,可通过注解添加{@link ExcelSelect}.
     */
    private String name;

    /**
     * 模板类
     */
    private Class<T> clazz;

    /**
     * 表头的集合{@link com.alibaba.excel.annotation.ExcelProperty}.
     */
    private List<WHead> wHeads;

    /**
     * 数据
     */
    private List<T> data;

    /**
     * 错误Map key:行号,String:错误集
     */
    private Map<Integer, List<String>> errMap;

    /**
     * 样式合集,请参考{@link WSheetWriteHandler#beforeSheetCreate}.
     * <p>
     * 说明:styles[0]为表头样式，styles[1]错误单元样式
     */
    private CellStyle[] styles = new CellStyle[2];

    /**
     * 根据模板类初始化
     */
    public WExcel(Class<T> clazz) {
        this.clazz = clazz;
        this.wHeads = new ArrayList<>();
        this.data = new ArrayList<>();
        this.errMap = new LinkedHashMap<>();
        //获取工作簿名称，没有则以类名为默认工作簿名称
        if (clazz.isAnnotationPresent(ExcelSheet.class)) {
            ExcelSheet ExcelSheet = clazz.getAnnotation(ExcelSheet.class);
            this.name = ExcelSheet.value();
        } else {
            this.name = clazz.getSimpleName();
        }

        // 获取实体所有字段
        Field[] fields = ClassUtil.getFields(clazz, true);
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field field : fields) {
            fieldMap.put(field.getName(), field);
        }


        Map<String, WHead> wHeadMap = new LinkedHashMap<>();
        // 获取导出Head
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        EasyExcel.write(outputStream)
                .head(this.clazz)
                .useDefaultStyle(false)
                .registerConverter(new BooleanConverter())
                .registerWriteHandler(new HeadWriteHandler() {
                    @Override
                    protected void handlerHead(Head head) {
                        WHead wHead = new WHead(head);
                        Field field = fieldMap.get(wHead.getHead().getFieldName());

                        // 获取HeadName
                        wHead.setName(head.getHeadNameList().get(head.getHeadNameList().size() - 1));
                        // 获取注释
                        if (field.isAnnotationPresent(ExcelNote.class)) {
                            wHead.setNote(field.getAnnotation(ExcelNote.class).value());
                        }
                        // 获取下拉列表
                        if (field.isAnnotationPresent(ExcelSelect.class)) {
                            wHead.setSelectList(field.getAnnotation(ExcelSelect.class).value());
                        }

                        wHeadMap.put(head.getFieldName(), wHead);
                    }
                })
                .sheet(this.name).doWrite(Collections.EMPTY_LIST);
        wHeads.addAll(wHeadMap.values());
    }

    /**
     * 添加数据集
     *
     * @param os 待添加数据集
     * @return WExcel
     */
    public WExcel addDatas(List<T> os) {
        return addDatas(os, null);
    }

    /**
     * 添加数据集
     *
     * @param os        待添加数据集
     * @param processor 执行器
     * @return WExcel
     */
    public WExcel addDatas(List<T> os, Processor<T> processor) {
        for (T o : os) {
            addData(o, processor);
        }
        return this;
    }

    /**
     * 添加数据
     *
     * @param o 待添加数据
     * @return WExcel
     */
    public WExcel addData(T o) {
        return addData(o, null);
    }

    /**
     * 添加数据
     *
     * @param o 待添加数据
     * @param processor 执行器
     * @return WExcel
     */
    public WExcel addData(T o, Processor<T> processor) {
        // 注解检查验证
        List<String> validate = ValidationUtil.validate(o);
        // 处理器执行
        if ((validate == null || validate.isEmpty()) && processor != null) {
            List<String> exec = processor.exec(o, this.data.size());
            if (exec != null && exec.size() > 0) {
                errMap.computeIfAbsent(data.size(), k -> new ArrayList<>());
                List<String> errs = errMap.get(data.size());
                errs.addAll(exec);
            }
        }
        this.data.add(o);
        return this;
    }

    /**
     * @return 所有已添加数据
     */
    public List<T> getDatas() {
        return this.data;
    }

    /**
     * @return Excel文件字节数组
     */
    public byte[] getBytes() {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        EasyExcel.write(outputStream)
                .head(this.clazz)
                .useDefaultStyle(false)
                .registerConverter(new BooleanConverter())
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .registerWriteHandler(new WSheetWriteHandler(wHeads, styles))
                .registerWriteHandler(new WRowWriteHandler(wHeads, errMap))
                .registerWriteHandler(new WCellWriteHandler(wHeads, styles, errMap))
                .sheet(this.name).doWrite(data);
        return outputStream.toByteArray();
    }

    /**
     * 将Excel文件输出到文件
     *
     * @param file 文件
     */
    public void toFile(File file) {
        byte[] aByte = getBytes();
        FileUtil.writeBytesToFile(aByte, file);
    }

    /**
     * @param file      文件
     * @param processor 执行器
     * @return WExcel
     * @throws TemplateNotMatchException
     * @throws IOException
     */
    public WExcel<T> read(File file, Processor<T> processor) throws TemplateNotMatchException, IOException {
        ExcelTypeEnum et;
        if (file.getName().endsWith(".xls")) {
            et = ExcelTypeEnum.XLS;
        } else if (file.getName().endsWith(".xlsx")) {
            et = ExcelTypeEnum.XLSX;
        } else {
            throw new RuntimeException("The file is not an excel file!");
        }
        byte[] bytes = FileUtil.readFileToByteArray(file);
        return read(bytes, et, processor);
    }

    /**
     * @param bytes     字节数组
     * @return WExcel
     * @throws TemplateNotMatchException
     * @throws IOException
     */
    public WExcel<T> read(byte[] bytes) throws TemplateNotMatchException, IOException {
        return read(bytes, ExcelTypeEnum.XLSX, null);
    }

    /**
     * @param bytes     字节数组
     * @param processor 执行器
     * @return WExcel
     * @throws TemplateNotMatchException
     * @throws IOException
     */
    public WExcel<T> read(byte[] bytes, Processor<T> processor) throws TemplateNotMatchException, IOException {
        return read(bytes, ExcelTypeEnum.XLSX, processor);
    }

    /**
     * @param bytes     字节数组
     * @param et        Excel文件类型
     * @param processor 执行器
     * @return WExcel
     * @throws TemplateNotMatchException
     * @throws IOException
     */
    public WExcel<T> read(byte[] bytes, ExcelTypeEnum et, Processor<T> processor) throws TemplateNotMatchException, IOException {
        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
        return read(stream, et, processor);
    }

    /**
     * @param in 输入流
     * @return WExcel
     * @throws TemplateNotMatchException
     * @throws IOException
     */
    public WExcel<T> read(InputStream in) throws TemplateNotMatchException {
        return  read(in, ExcelTypeEnum.XLSX, null);
    }

    /**
     * @param in        输入流
     * @param processor 执行器
     * @return WExcel
     * @throws TemplateNotMatchException
     * @throws IOException
     */
    public WExcel<T> read(InputStream in, Processor<T> processor) throws TemplateNotMatchException {
        return  read(in, ExcelTypeEnum.XLSX, processor);
    }

    /**
     * @param in        输入流
     * @param processor 执行器
     * @param et        Excel文件类型
     * @return WExcel
     * @throws TemplateNotMatchException
     * @throws IOException
     */
    public WExcel<T> read(InputStream in, ExcelTypeEnum et, Processor<T> processor) throws TemplateNotMatchException {
        final TemplateNotMatchException[] e = new TemplateNotMatchException[1];
        // 读取Head 验证模板是否符合情况
        EasyExcel.read(in)
                .excelType(et)
                .head(this.clazz)
                .registerConverter(new BooleanConverter())
                .registerReadListener(new WReadListener<T>(wHeads, e) {
                    @Override
                    public void onException(Exception exception, AnalysisContext context) throws Exception {
                        if (exception instanceof ExcelDataConvertException) {
                            ExcelDataConvertException convertException = (ExcelDataConvertException) exception;
                            errMap.computeIfAbsent(data.size(), k -> new ArrayList<>());
                            List<String> errs = errMap.get(data.size());
                            errs.add(convertException.getCause().getMessage());
                        }else {
                            super.onException(exception, context);
                        }
                    }

                    @Override
                    public void invoke(T o) {
                        if (processor != null) {
                            List<String> exec = processor.exec(o, data.size());
                            if (exec != null && exec.size() > 0) {
                                errMap.computeIfAbsent(data.size(), k -> new ArrayList<>());
                                List<String> errs = errMap.get(data.size());
                                errs.addAll(exec);
                            }
                        }
                        data.add(o);
                    }
                })
                .sheet().doRead();
        if (e[0] != null) {
            throw e[0];
        }
        return this;
    }

    public interface Processor<T> {
        List<String> exec(T t, int index);
    }

    /**
     * @return 是否有错
     */
    public boolean hasError() {
        return !errMap.isEmpty();
    }

    /**
     * @return 获取名称
     */
    public String getName() {
        return name;
    }
}
