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

import cn.hutool.crypto.digest.DigestUtil;
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.api.system.service.DepartmentService;
import com.skr.mdm.bean.dto.UserImportFailDTO;
import com.skr.mdm.constant.MinIoMessageConstants;
import com.skr.mdm.constant.RegexConstants;
import com.skr.mdm.dao.AuDepartmentDao;
import com.skr.mdm.dao.AuUserDao;
import com.skr.mdm.entity.AuDepartmentDO;
import com.skr.mdm.entity.AuUserDO;
import com.skr.mdm.enums.DeleteStateEnum;
import com.skr.mdm.manager.oss.MinIoManager;
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.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.crypto.password.PasswordEncoder;

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 UserImportDataListener extends AnalysisEventListener<UserImportData> {

    private final AuUserDao userDao;
    private final DepartmentService departmentService;
    private Integer companyId;
    private AuDepartmentDao departmentDao;

    @Setter
    private MinIoManager minIoManager;

    @Setter
    private PasswordEncoder passwordEncoder;

    /**
     * 是否自动创建部门（0：否 1：是）
     */
    @Setter
    private Integer isAutoCreateDept;

    /**
     * 成功导入的部门ID集合
     */
    @Getter
    private final Set<Integer> successDeptIdSet = Sets.newHashSetWithExpectedSize(64);

    private final List<UserImportFailDTO> userImportFailDTOList = Lists.newArrayListWithExpectedSize(256);

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

    private final List<AuUserDO> userDOList = Lists.newArrayListWithExpectedSize(256);

    private Map<String, Integer> deptPathMap;

    private final Map<String, String> passwordMap = Maps.newHashMapWithExpectedSize(16);

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

    @Getter
    private String failFileUrl;

    private int headRowNumber = 1;


    /**
     * @param userDao
     */
    public UserImportDataListener(AuUserDao userDao, DepartmentService departmentService, AuDepartmentDao departmentDao, Integer companyId) {
        this.userDao = userDao;
        this.departmentService = departmentService;
        this.departmentDao = departmentDao;
        this.companyId = companyId;
        initData();
    }

    private void initData() {
        deptPathMap = departmentService.getDeptPathMap(companyId);
        phoneNumberSet = new LambdaQueryChainWrapper<>(userDao)
                .eq(AuUserDO::getCompanyId, companyId)
                .eq(AuUserDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .list()
                .stream()
                .map(AuUserDO::getPhoneNumber)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toSet());
    }

    private static boolean checkExcelHead(Map<Integer, String> headMap) {
        return !headMap.containsKey(0) || !headMap.containsKey(1) || !headMap.containsKey(2) || !headMap.containsKey(3)
                || !headMap.containsKey(4) || !headMap.get(0).equals("用户名") || !headMap.get(1).equals("密码")
                || !headMap.get(2).equals("手机号") || !headMap.get(3).equals("部门") || !headMap.get(4).equals("备注");
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        // 第二行表头校验包括失败信息
        if (headRowNumber == 2) {
            // 表头数量校验
            if (headMap.size() != 5 && headMap.size() != 6) {
                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(UserImportData data, AnalysisContext context) {
        log.info("用户导入解析到一条数据:{}", JSONUtil.toJsonStr(data));
        allCount++;
        // 判空
        if (checkBlank(data)) {
            failCount++;
            return;
        }
        // 格式校验
        if (checkFormat(data)) {
            failCount++;
            return;
        }
        // 手机号重复校验
        if (checkPhoneNumberDuplicate(data)) {
            failCount++;
            return;
        }
        // 查询部门是否存在
        String department = StringUtils.deleteWhitespace(data.getDepartment());
        String[] deptNames = StringUtils.split(department, "/");
        if (deptPathMap.containsKey(department)) {
            addUserDO(data, deptPathMap.get(department), deptNames[deptNames.length - 1]);
            successCount++;
            return;
        }
        if (deptNames != null && deptNames.length > 0) {
            Integer parentId = 0;
            Integer deptId = 0;
            String departmentName = "";
            String deptPath = "";
            for (String deptName : deptNames) {
                if (StringUtils.isBlank(deptPath)) {
                    deptPath = deptName;
                } else {
                    deptPath = deptPath.concat("/").concat(deptName);
                }
                // 部门不存在
                if (!deptPathMap.containsKey(deptPath)) {
                    // 是否自动创建部门 否直接返回失败信息
                    if (isAutoCreateDept.equals(0)) {
                        userImportFailDTOList.add(covertUserImportFailDTO(data, "部门不存在"));
                        failCount++;
                        return;
                    }
                    // 部门名称格式校验
                    if (!Pattern.matches(RegexConstants.DEPT_NAME_REGEX, deptName)) {
                        userImportFailDTOList.add(covertUserImportFailDTO(data, "部门格式错误"));
                        failCount++;
                        return;
                    }
                    // 创建部门
                    AuDepartmentDO auDepartmentDO = saveDepartmentDO(parentId, deptName);
                    parentId = auDepartmentDO.getId();
                    deptId = auDepartmentDO.getId();
                    departmentName = deptName;
                    deptPathMap.put(deptPath, deptId);
                    continue;
                }
                // 存在部门
                parentId = deptPathMap.get(deptPath);
                deptId = deptPathMap.get(deptPath);
                departmentName = deptName;
            }
            // 添加
            addUserDO(data, deptId, departmentName);
            successCount++;
        }
    }

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


    private void uploadErrorReport() {
        if (CollectionUtils.isNotEmpty(userImportFailDTOList)) {
            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(userImportFailDTOList);
            } catch (IOException e) {
                log.error("生成用户导入错误报告异常", e);
            }
            if (file.exists()) {
                String minioObject = MinIoMessageConstants.IMPORT_ERROR + fileName;
                failFileUrl = minIoManager.putObjectMinIo(companyId, minioObject, file, false);
                log.info("用户导入错误报告上传成功");
                // 清除缓存数据
                userImportFailDTOList.clear();
                boolean isDelete = file.delete();
                if (isDelete) {
                    log.info("用户导入错误报告本地文件缓存清除成功");
                }
            }
        }
    }

    private boolean checkPhoneNumberDuplicate(UserImportData data) {
        // 首次查询公司下所有手机号放入缓存
        if (CollectionUtils.isNotEmpty(phoneNumberSet) && phoneNumberSet.contains(data.getPhoneNumber())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "手机号与系统中重复"));
            return true;
        }
        phoneNumberSet.add(data.getPhoneNumber());
        return false;
    }

    private boolean checkFormat(UserImportData data) {
        if (!Pattern.matches(RegexConstants.USER_NAME_REGEX, data.getUserName())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "用户名格式错误"));
            return true;
        }
        if (!Pattern.matches(RegexConstants.PASSWORD_REGEX, data.getPassword())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "密码格式错误"));
            return true;
        }
        if (!Pattern.matches(RegexConstants.PHONE_NUMBER_REGEX, data.getPhoneNumber())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "手机号格式错误"));
            return true;
        }
        if (StringUtils.isNotBlank(data.getRemark()) && data.getRemark().length() > 500) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "备注最多填写500字"));
            return true;
        }
        return false;
    }

    private boolean checkBlank(UserImportData data) {
        if (StringUtils.isBlank(data.getUserName())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "用户名不能为空"));
            return true;
        }
        if (StringUtils.isBlank(data.getPassword())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "密码不能为空"));
            return true;
        }
        if (StringUtils.isBlank(data.getPhoneNumber())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "手机号不能为空"));
            return true;
        }
        if (StringUtils.isBlank(data.getDepartment())) {
            userImportFailDTOList.add(covertUserImportFailDTO(data, "部门不能为空"));
            return true;
        }
        return false;
    }

    private void addUserDO(UserImportData data, Integer deptId, String departmentName) {
        AuUserDO userDO = new AuUserDO();
        userDO.setUserName(data.getUserName());
        // 密码加密
        if (passwordMap.containsKey(data.getPassword())) {
            userDO.setPassword(passwordMap.get(data.getPassword()));
        } else {
            String encode = passwordEncoder.encode(DigestUtil.md5Hex(data.getPassword()));
            passwordMap.put(data.getPassword(), encode);
            userDO.setPassword(encode);
        }
        userDO.setPhoneNumber(data.getPhoneNumber());
        userDO.setDepartmentId(deptId);
        userDO.setDepartmentName(departmentName);
        userDO.setCompanyId(companyId);
        userDO.setRemark(data.getRemark());
        userDOList.add(userDO);
    }


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

    private UserImportFailDTO covertUserImportFailDTO(UserImportData userImportData, String failInfo) {
        return UserImportFailDTO.builder()
                .userName(userImportData.getUserName())
                .password(userImportData.getPassword())
                .phoneNumber(userImportData.getPhoneNumber())
                .department(userImportData.getDepartment())
                .remark(userImportData.getRemark())
                .failInfo(failInfo)
                .build();
    }


    private AuDepartmentDO saveDepartmentDO(Integer parentId, String deptName) {
        AuDepartmentDO departmentDO = AuDepartmentDO.builder()
                .parentId(parentId)
                .departmentName(deptName)
                .companyId(companyId)
                .isDefault(0)
                .weight(0)
                .build();
        departmentDao.insert(departmentDO);
        // 保存部门数据权限
        successDeptIdSet.add(departmentDO.getId());
        return departmentDO;
    }

}
