package com.bofeng.excel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.ExcelColumnProperty;
import com.alibaba.excel.util.TypeUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yvan.platform.JsonWapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import net.sf.cglib.beans.BeanMap;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ExcelDataReader<T> extends AnalysisEventListener<List<String>> {

    public static final String ATTRIB_NAME = "ExcelDataReader";
    private static final int ERROR_ROWS_LIMIT = 100;

    private final AtomicInteger cursor = new AtomicInteger(-1);
    private final List<RowInfo<T>> data = Lists.newArrayList();
    private ExcelHeadProperty2<T> headProperty;
    private final int limitRows;
    private boolean limitExecuted = false;
    public String FILE_NAME = "";

    private final AtomicInteger errorCount = new AtomicInteger(0);

    /**
     * 全局错误（不是在行、不是在值上的错误）
     */
    private final List<String> globalError = Lists.newArrayList();

    /**
     * 业务数据
     */
    private Object bizData;

    /**
     * 业务导入行数
     */
    private AtomicInteger successRows = new AtomicInteger(0);

    public ExcelDataReader(Class<T> clazz, int limitRows) {
        headProperty = new ExcelHeadProperty2<>(clazz);
        this.limitRows = limitRows;
    }

    public boolean hasNext() {
        return (data.size() > cursor.get() + 1);
    }

    public RowInfo<T> next() {
        return data.get(cursor.incrementAndGet());
    }

    @Override
    @SneakyThrows
    public void invoke(List<String> object, AnalysisContext context) {
        if (limitRows > 0 && data.size() >= limitRows) {
            limitExecuted = true;
            return;
        }
        T resultModel = headProperty.getHeadClazz().newInstance();
        RowInfo<T> rowInfo = new RowInfo<>(this, context.getCurrentRowNum(), resultModel);
        data.add(rowInfo);

        //转换制类型
        Map map = new HashMap();
        for (int i = 0; i < object.size(); i++) {
            ExcelColumnProperty columnProperty = headProperty.getExcelColumnProperty(i);
            if (columnProperty != null) {

                try {
                    Object value = TypeUtil.convert(object.get(i), columnProperty.getField(),
                            columnProperty.getFormat(), context.use1904WindowDate());
                    if (value != null) {
                        map.put(columnProperty.getField().getName(), value);
                    }
                } catch (Exception e) {

                    log.error("格式错误", e);

                    //填充错误值
                    rowInfo.errorValue(columnProperty.getField().getName(), object.get(i));

                    //写入错误异常
                    if (e instanceof NumberFormatException) {
                        rowInfo.errorInColumn(columnProperty.getField().getName(), "数据格式错误");

                    } else {
                        rowInfo.errorInColumn(columnProperty.getField().getName(), e.toString());
                    }
                }
            }
        }

        BeanMap.create(resultModel).putAll(map);

        if (!rowInfo.hasError()) {
            //执行注解校验
            ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
            Validator validator = vf.getValidator();
            Set<ConstraintViolation<T>> set = validator.validate(resultModel);
            for (ConstraintViolation<T> constraintViolation : set) {
                rowInfo.errorInColumn(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
            }
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }

    public void begin() {
        finish();
    }

    public void finish() {
        if (globalError.size() > 0) {
            throw new RuntimeException("无法导入," + globalError.get(0));
        }

        if (this.data.size() <= 0) {
            error("空表格,没有任何数据!");
        }

        if (limitExecuted) {
            error("不能超过" + limitRows + "行, 请分批导入!");
        }

        if (globalError.size() > 0) {
            throw new RuntimeException("无法导入," + globalError.get(0));
        }

        for (val rowInfo : data) {
            if (rowInfo.hasError()) {
                throw new RuntimeException("无法导入, excel" + rowInfo.excelRowNum + "行有异常");
            }
        }
    }

    public ExcelHeadProperty2 getHead() {
        return headProperty;
    }

    /**
     * 标记全局异常（不是在行、不是在值上的错误）
     */
    public void error(String msg) {
        errorCount.incrementAndGet();
        globalError.add(msg);
    }

    public static class RowInfo<T> {

        private T data;
        private int excelRowNum;
        private final ExcelDataReader<T> parent;
        private final Map<String, String> errorValue = Maps.newLinkedHashMap();
        private final Map<String, List<String>> errorInColumn = Maps.newLinkedHashMap();
        private boolean error = false;

        private RowInfo(ExcelDataReader<T> parent, int excelRowNum, T data) {
            this.parent = parent;
            this.excelRowNum = excelRowNum;
            this.data = data;
        }

        /**
         * 标记该行数据有错误
         */
        public void errorInRow(String msg) {
            errorInColumn("row_error", msg);
        }

        /**
         * 标记该行，某列数据有错误
         */
        public void errorInColumn(String columnName, String msg) {
            error = true;
            parent.errorCount.incrementAndGet();
            List<String> errors = errorInColumn.get(columnName);
            if (errors == null) {
                errors = Lists.newArrayList();
                errorInColumn.put(columnName, errors);
            }
            errors.add(msg);
        }

        /**
         * 记录异常的数据（因为实体类有可能接受不了该数据，用Map来存储数据）
         */
        public void errorValue(String columnName, String value) {
            errorValue.put(columnName, value);
        }

        /**
         * 判断该行是否有异常
         */
        public boolean hasError() {
            return error;
        }

        public int getExcelRowNum() {
            return excelRowNum;
        }

        public T getData() {
            return data;
        }
    }

    /**
     * 构造错误窗口
     */
    @SneakyThrows
    public List<Map<String, Object>> buildErrorRows() {
        List list = Lists.newArrayList();

        for (RowInfo<T> rowInfo : this.data) {
            //过滤没有错误的行
            if (!rowInfo.hasError())
                continue;

            //只显示固定数量的行
            if (list.size() >= ERROR_ROWS_LIMIT) {
                continue;
            }

            //序列化行数据
            JsonWapper jw = new JsonWapper(rowInfo.data);

            //写入 excel 行号
            jw.set("excelRowNum", rowInfo.getExcelRowNum() + 1);

            //写入异常原因
            jw.set("errorCollection", rowInfo.errorInColumn);

            //合并错误值
            for (Map.Entry<String, String> entry : rowInfo.errorValue.entrySet()) {
                jw.set(entry.getKey(), entry.getValue());
            }

            list.add(jw.getInnerMap());
        }

        return list;
    }

    public List<String> getGlobalError() {
        return globalError;
    }

    public int getSuccessRows() {
        return this.successRows.get();
    }

    public void markSuccess() {
        this.successRows.incrementAndGet();
    }

    public Object getBizData() {
        return bizData;
    }

    public int getErrorCount() {
        return errorCount.get();
    }

    public void setBizData(Object bizData) {
        this.bizData = bizData;
    }
}
