package com.codvision.base.config.excel;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.ListUtils;
import com.codvision.base.entity.ExcelCheck;
import com.codvision.base.entity.ExcelErrorRecord;
import com.codvision.base.service.IExcelErrorRecordService;
import com.codvision.commoncore.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @Author huxb
 * @description: excel导入通用读数据
 * 有个很重要的点 Listener 不能被spring管理，要每次读取excel都要new,然后里面用到spring可以构造方法传进去
 * @create 2023/2/22 11:07
 */
@SuppressWarnings("all")
@Slf4j
public abstract class ExcelDataReadListener<T> implements ReadListener<T> {

    /**
     * 数据批量入库阈值
     */
    private static final int BATCH_COUNT = 100;

    /**
     * 数据校验对象
     */
    private final ExcelCheck<T> check = new ExcelCheck(BATCH_COUNT);

    /**
     * 数据异常记录Mapper
     */
    private final IExcelErrorRecordService service;


    private final Consumer<List<T>> consumer;

    /**
     * 数据校验
     *
     * @param data
     * @param context
     * @return
     */
    protected abstract void validator(T data, ExcelCheck<T> check, AnalysisContext context);

    public ExcelDataReadListener(Consumer<List<T>> consumer) {
        this.service = SpringUtil.getBean(IExcelErrorRecordService.class);
        this.consumer = consumer;
    }

    public boolean isHeadAbnormal() {
        return check.getHeadAbnormal();
    }

    public boolean existErrorData() {
        return check.getExistErrorData();
    }

    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        Map<Integer, String> head = new HashMap<>();
        Map<Integer, String> map = ConverterUtils.convertToStringMap(headMap, context);
        if (map.size() <= 0) {
            check.setHeadAbnormal(true);
            throw new BusinessException("文件格式错误，请检查导入模板");
        }
        try {
            head = getIndexNameMap((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        Set<Integer> keySet = head.keySet();
        for (Integer key : keySet) {
            if (StringUtils.isEmpty(map.get(key)) || !map.get(key).equals(head.get(key))) {
                check.setHeadAbnormal(true);
                throw new BusinessException("文件格式错误，请检查导入模板");
            }
        }
    }

    /**
     * 读取每一行数据都会回调该方法
     *
     * @param data
     * @param context
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        // 若一行数据均为空值，则过滤
        if (isLineNullValue(data)) {
            return;
        }

        // 数据校验
        validator(data, check, context);

        // 判断是否达到阈值，进行数据入库，防止大量数据存储在内存中造成OOM
        if (check.getCacheList().size() >= BATCH_COUNT) {
            consumer.accept(check.getCacheList());
            check.setCacheList(ListUtils.newArrayListWithExpectedSize(BATCH_COUNT));
        }

        if (check.getErrorList().size() >= BATCH_COUNT) {
            check.setExistErrorData(true);
            saveErrorInfo(check.getErrorList());
            check.setErrorList(ListUtils.newArrayListWithExpectedSize(BATCH_COUNT));
        }
    }

    /**
     * 所有行的读取都结束后触发
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，因为最后一次的遍历可能不满100条的时候，不会触发invoke()方法中的数据入库逻辑，以便确保遗留的数据也存储到数据库
        if (CollectionUtils.isNotEmpty(check.getCacheList())) {
            consumer.accept(check.getCacheList());
        }
        if (CollectionUtils.isNotEmpty(check.getErrorList())) {
            check.setExistErrorData(true);
            saveErrorInfo(check.getErrorList());
        }
        log.info("所有数据解析完成！");
    }


    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析失败，但是继续解析下一行:{}", exception.getMessage());
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.error("第{}行，第{}列解析异常，数据为:{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }

        if (exception instanceof BusinessException) {
            exception.printStackTrace();
        }
    }

    /**
     * 异常校验信息入库
     *
     * @param errors
     */
    public void saveErrorInfo(List<ExcelErrorRecord> errors) {
        if (CollectionUtils.isNotEmpty(errors)) {
            service.saveBatch(errors);
        }
    }

    private Map<Integer, String> getIndexNameMap(Class clazz) throws NoSuchFieldException {
        Map<Integer, String> result = new HashMap<>();
        Field field;
        Field[] fields = clazz.getDeclaredFields();
        int n = 0;
        for (int i = 0; i < fields.length; i++) {
            field = clazz.getDeclaredField(fields[i].getName());
            field.setAccessible(true);
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            if (excelProperty != null) {
                String[] values = excelProperty.value();
                StringBuilder value = new StringBuilder();
                for (String v : values) {
                    value.append(v);
                }
                result.put(n++, value.toString());
            }
        }
        return result;
    }

    private boolean isLineNullValue(T data) {
        if (data instanceof String) {
            return StringUtils.isBlank((String) data);
        }
        try {
            List<Field> fields = Arrays.stream(data.getClass().getDeclaredFields())
                    .filter(f -> f.isAnnotationPresent(ExcelProperty.class))
                    .collect(Collectors.toList());
            List<Boolean> lineNullList = new ArrayList<>(fields.size());
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.get(data) == null) {
                    lineNullList.add(Boolean.TRUE);
                } else {
                    lineNullList.add(Boolean.FALSE);
                }
            }
            return lineNullList.stream().allMatch(Boolean.TRUE::equals);
        } catch (Exception e) {
        }
        return true;
    }
}
