package com.skr.mdm.api.equipment.excel;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.skr.mdm.bean.dto.EquipmentImportFailDTO;
import com.skr.mdm.bean.qo.GaodeTerminalQO;
import com.skr.mdm.constant.MinIoMessageConstants;
import com.skr.mdm.constant.RegexConstants;
import com.skr.mdm.dao.AuUserDao;
import com.skr.mdm.dao.DeviceEquipmentDao;
import com.skr.mdm.entity.AuUserDO;
import com.skr.mdm.entity.DeviceEquipmentDO;
import com.skr.mdm.enums.DeleteStateEnum;
import com.skr.mdm.manager.oss.MinIoManager;
import com.skr.mdm.util.RegisterEquipmentUtil;
import com.skr.mdm.util.ServerSystemUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author nfq
 * @date 2021/11/16 11:38
 */
@Slf4j
public class EquipmentImportDataListener extends AnalysisEventListener<EquipmentImportData> {

    private final AuUserDao userDao;
    private final DeviceEquipmentDao equipmentDao;

    @Setter
    private MinIoManager minIoManager;

    private final Integer companyId;

    @Setter
    private Integer sid;

    @Setter
    private String companyName;

    @Setter
    private Integer allowUseNumber;

    @Setter
    private Long existEquipmentCount;

    private final List<EquipmentImportFailDTO> equipmentImportFailDTOList = Lists.newArrayListWithExpectedSize(256);

    private Set<String> imeiSet = Sets.newHashSetWithExpectedSize(256);

    private Map<Integer, Set<String>> equipmentNameMap = Maps.newHashMapWithExpectedSize(256);

    private Map<String, AuUserDO> userDOMap = Maps.newHashMapWithExpectedSize(256);

    private final List<DeviceEquipmentDO> equipmentDOList = Lists.newArrayListWithExpectedSize(256);


    /**
     * 导入总条数
     */
    @Getter
    private Integer allCount = 0;
    /**
     * 成功条数
     */
    @Getter
    private int successCount = 0;
    /**
     * 失败条数
     */
    @Getter
    private int failCount = 0;

    @Getter
    private String failFileUrl;

    private int headRowNumber = 1;


    /**
     * @param userDao
     */
    public EquipmentImportDataListener(AuUserDao userDao, DeviceEquipmentDao equipmentDao, Integer companyId) {
        this.userDao = userDao;
        this.equipmentDao = equipmentDao;
        this.companyId = companyId;
        initData();
    }

    private static boolean checkExcelHead(Map<Integer, String> headMap) {
        return !headMap.containsKey(0) || !headMap.containsKey(1) || !headMap.containsKey(2)
                || !headMap.get(0).equals("手机号") || !headMap.get(1).equals("IMEI") || !headMap.get(2).equals("设备名");
    }

    private void initData() {
        List<AuUserDO> userDOList = new LambdaQueryChainWrapper<>(userDao)
                .eq(AuUserDO::getCompanyId, companyId)
                .eq(AuUserDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .list();
        if (CollectionUtils.isNotEmpty(userDOList)) {
            this.userDOMap = userDOList.stream()
                    .filter(userDO -> StringUtils.isNotBlank(userDO.getPhoneNumber()))
                    .collect(Collectors.toMap(AuUserDO::getPhoneNumber, userDO -> userDO));
        }
        List<DeviceEquipmentDO> deviceEquipmentDOList = new LambdaQueryChainWrapper<>(equipmentDao)
                .eq(DeviceEquipmentDO::getCompanyId, companyId)
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .list();
        if (CollectionUtils.isNotEmpty(deviceEquipmentDOList)) {
            this.imeiSet = deviceEquipmentDOList.stream()
                    .map(DeviceEquipmentDO::getImei)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toSet());
            this.equipmentNameMap = deviceEquipmentDOList.stream()
                    .collect(Collectors.groupingBy(DeviceEquipmentDO::getUserId,
                            Collectors.mapping(DeviceEquipmentDO::getEquipmentName, Collectors.toSet())));
        }
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        // 第二行表头校验包括失败信息
        if (headRowNumber == 2) {
            // 表头数量校验
            if (headMap.size() != 3 && headMap.size() != 4) {
                throw new ExcelAnalysisException("您上传的文件格式与模板格式不一致，请检查后重新上传");
            }
            // 表头值校验
            if (checkExcelHead(headMap)) {
                throw new ExcelAnalysisException("您上传的文件格式与模板格式不一致，请检查后重新上传");
            }
        }
        headRowNumber++;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(EquipmentImportData data, AnalysisContext context) {
        log.info("设备导入解析到一条数据:{}", JSONUtil.toJsonStr(data));
        allCount++;
        // 判空
        if (checkBlank(data)) {
            failCount++;
            return;
        }
        // 格式校验
        if (checkFormat(data)) {
            failCount++;
            return;
        }
        // 检查是否超过授权数量
        if (isOverAllowUseNumber()) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "设备数量超出限制，无法注册，请联系管理员"));
            failCount++;
            return;
        }
        // 手机号存在校验 不存在返回
        if (MapUtils.isEmpty(userDOMap) || !userDOMap.containsKey(data.getPhoneNumber())) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "手机号系统中不存在"));
            failCount++;
            return;
        }
        // imei查重
        if (CollectionUtils.isNotEmpty(imeiSet) && imeiSet.contains(data.getImei())) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "IMEI与系统中重复"));
            failCount++;
            return;
        }
        imeiSet.add(data.getImei());
        // 如果存在设备名进行查重 不存在自动生成设备名
        AuUserDO userDO = userDOMap.get(data.getPhoneNumber());
        String equipmentName = data.getEquipmentName();
        if (StringUtils.isNotBlank(equipmentName)) {
            boolean isExistEquipmentNameMap = MapUtils.isNotEmpty(equipmentNameMap) &&
                    equipmentNameMap.containsKey(userDO.getId()) &&
                    equipmentNameMap.get(userDO.getId()).contains(equipmentName);
            if (isExistEquipmentNameMap) {
                equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "设备名与系统中重复"));
                failCount++;
                return;
            }
        } else {
            // 自动生成设备名
            equipmentName = getEquipmentName(userDO.getId(), userDO.getUserName(), userDO.getPhoneNumber());
        }
        if (equipmentNameMap.containsKey(userDO.getId())) {
            equipmentNameMap.get(userDO.getId()).add(equipmentName);
        } else {
            equipmentNameMap.put(userDO.getId(), Sets.newHashSet(equipmentName));
        }
        // 保存
        addEquipmentDO(data, userDO, equipmentName);
        successCount++;
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("设备导入所有数据解析完成 总条数：{} 成功条数：{} 失败条数：{}", allCount, successCount, failCount);
        // 保存数据
        saveData();
        // 生成错误报告，并上传到minio
        uploadErrorReport();
        equipmentDOList.clear();
        imeiSet.clear();
        equipmentNameMap.clear();
        userDOMap.clear();
    }

    private void uploadErrorReport() {
        if (CollectionUtils.isNotEmpty(equipmentImportFailDTOList)) {
            ClassPathResource classPathResource = new ClassPathResource("static/设备导入失败模板.xlsx");
            String fileName = "设备导入失败报告" + System.currentTimeMillis() + ".xlsx";
            String filePath = ServerSystemUtil.userDir() + File.separator + fileName;
            File file = new File(filePath);
            try (InputStream inputStream = classPathResource.getInputStream()) {
                EasyExcel.write(file)
                        .withTemplate(inputStream)
                        .sheet("设备导入模板")
                        .doWrite(equipmentImportFailDTOList);
            } catch (IOException e) {
                log.error("生成设备导入错误报告异常", e);
            }
            if (file.exists()) {
                String minioObject = MinIoMessageConstants.IMPORT_ERROR + fileName;
                failFileUrl = minIoManager.putObjectMinIo(companyId, minioObject, file, false);
                log.info("设备导入错误报告上传成功");
                // 清除缓存数据
                equipmentImportFailDTOList.clear();
                boolean isDelete = file.delete();
                if (isDelete) {
                    log.info("设备导入错误报告本地文件缓存清除成功");
                }
            }
        }
    }

    private void addEquipmentDO(EquipmentImportData data, AuUserDO userDO, String equipmentName) {
        DeviceEquipmentDO equipmentDO = new DeviceEquipmentDO();
        equipmentDO.setEquipmentName(equipmentName);
        equipmentDO.setUserId(userDO.getId());
        equipmentDO.setUserName(userDO.getUserName());
        equipmentDO.setEquipmentType(1);
        equipmentDO.setImei(data.getImei());
        equipmentDO.setDepartmentId(userDO.getDepartmentId());
        equipmentDO.setDepartmentName(userDO.getDepartmentName());
        equipmentDO.setCompanyId(companyId);
        // 注册高德终端
        registerGaodeTerminal(equipmentName, equipmentDO, userDO.getPhoneNumber());
        equipmentDOList.add(equipmentDO);
    }

    private void registerGaodeTerminal(String equipmentName, DeviceEquipmentDO equipmentDO, String phoneNumber) {
        if (sid != null) {
            GaodeTerminalQO gaodeTerminalQO = GaodeTerminalQO.builder()
                    .sid(sid)
                    .phoneNumber(phoneNumber)
                    .equipmentName(equipmentName)
                    .userName(equipmentDO.getUserName())
                    .departmentName(equipmentDO.getDepartmentName())
                    .companyName(companyName)
                    .imei(equipmentDO.getImei())
                    .build();
            RegisterEquipmentUtil.registerGaodeTerminal(gaodeTerminalQO, equipmentDO);
        }
    }

    private Long countEquipmentName(String equipmentName, Integer userId) {
        return new LambdaQueryChainWrapper<>(equipmentDao)
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .eq(DeviceEquipmentDO::getUserId, userId)
                .eq(DeviceEquipmentDO::getEquipmentName, equipmentName)
                .count();
    }

    private boolean checkFormat(EquipmentImportData data) {
        if (!Pattern.matches(RegexConstants.PHONE_NUMBER_REGEX, data.getPhoneNumber())) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "手机号格式错误"));
            return true;
        }
        if (!Pattern.matches(RegexConstants.IMEI_REGEX, data.getImei()) && !Pattern.matches(RegexConstants.IMEI_NUMBER_REGEX, data.getImei())) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "IMEI格式错误"));
            return true;
        }
        if (StringUtils.isNotBlank(data.getEquipmentName()) && !Pattern.matches(RegexConstants.EQUIPMENT_NAME_REGEX, data.getEquipmentName())) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "设备名格式错误"));
            return true;
        }
        return false;
    }

    private boolean checkBlank(EquipmentImportData data) {
        if (StringUtils.isBlank(data.getPhoneNumber())) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "手机号不能为空"));
            return true;
        }
        if (StringUtils.isBlank(data.getImei())) {
            equipmentImportFailDTOList.add(covertEquipmentImportFailDTO(data, "IMEI不能为空"));
            return true;
        }
        return false;
    }


    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("设备导入{}条数据，开始存储数据库！", equipmentDOList.size());
        if (CollectionUtils.isNotEmpty(equipmentDOList)) {
            equipmentDao.insertBatch(equipmentDOList);
        }
        log.info("存储数据库成功！");
    }

    private EquipmentImportFailDTO covertEquipmentImportFailDTO(EquipmentImportData userImportData, String failInfo) {
        return EquipmentImportFailDTO.builder()
                .phoneNumber(userImportData.getPhoneNumber())
                .imei(userImportData.getImei())
                .equipmentName(userImportData.getEquipmentName())
                .failInfo(failInfo)
                .build();
    }

    private AuUserDO getUserByPhoneNumber(String phoneNumber) {
        return new LambdaQueryChainWrapper<>(userDao)
                .eq(AuUserDO::getCompanyId, companyId)
                .eq(AuUserDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .eq(AuUserDO::getPhoneNumber, phoneNumber)
                .one();
    }

    private String getEquipmentName(Integer userId, String userName, String phoneNumber) {
        Set<String> equipmentNameSet = new LambdaQueryChainWrapper<>(equipmentDao)
                .likeRight(DeviceEquipmentDO::getEquipmentName, userName)
                .eq(DeviceEquipmentDO::getUserId, userId)
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .list()
                .stream().map(DeviceEquipmentDO::getEquipmentName)
                .collect(Collectors.toSet());
        if (CollectionUtils.isNotEmpty(equipmentNameSet)) {
            if (equipmentNameMap.containsKey(phoneNumber)) {
                equipmentNameSet.addAll(equipmentNameMap.get(phoneNumber));
            }
        } else {
            if (equipmentNameMap.containsKey(phoneNumber)) {
                equipmentNameSet = equipmentNameMap.get(phoneNumber);
            }
        }
        return RegisterEquipmentUtil.createEquipmentName(ListUtil.toList(equipmentNameSet), userName);
    }

    private boolean isOverAllowUseNumber() {
        int currentEquipmentCount = (int) (existEquipmentCount + successCount);
        return currentEquipmentCount >= allowUseNumber;
    }

}
