package com.skr.mdm.api.user.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Lists;
import com.skr.mdm.api.equipment.service.EquipmentService;
import com.skr.mdm.api.system.service.DepartmentService;
import com.skr.mdm.api.user.excel.UserImportData;
import com.skr.mdm.api.user.excel.UserImportDataListener;
import com.skr.mdm.api.user.service.UserService;
import com.skr.mdm.bean.bo.EquipmentNameBO;
import com.skr.mdm.bean.dto.UserExportDTO;
import com.skr.mdm.bean.qo.user.UserAddUpdateQO;
import com.skr.mdm.bean.qo.user.UserDeptQO;
import com.skr.mdm.bean.qo.user.UserQO;
import com.skr.mdm.bean.vo.department.DepartmentHierarchyVO;
import com.skr.mdm.bean.vo.user.UserVO;
import com.skr.mdm.constant.BasicNumberConstants;
import com.skr.mdm.dao.AuDepartmentDao;
import com.skr.mdm.dao.AuUserDao;
import com.skr.mdm.dao.DeviceEquipmentDao;
import com.skr.mdm.dao.MdmImportHistoryDao;
import com.skr.mdm.dto.LoginAdminDTO;
import com.skr.mdm.entity.AuDepartmentDO;
import com.skr.mdm.entity.AuUserDO;
import com.skr.mdm.entity.DeviceEquipmentDO;
import com.skr.mdm.entity.MdmImportHistoryDO;
import com.skr.mdm.enums.DeleteStateEnum;
import com.skr.mdm.enums.EquipmentStateEnum;
import com.skr.mdm.enums.LogAdminTypeEnum;
import com.skr.mdm.enums.SendCmdConfigEnum;
import com.skr.mdm.exception.ServiceException;
import com.skr.mdm.handle.IssuedUserHandler;
import com.skr.mdm.handle.LogAdminHandler;
import com.skr.mdm.manager.oss.MinIoManager;
import com.skr.mdm.publish.MdmEventPublisher;
import com.skr.mdm.result.ResponseResult;
import com.skr.mdm.util.MdmThreadPoolUtil;
import com.skr.mdm.util.PageUtil;
import com.skr.mdm.util.ServerSystemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;

/**
 * @author nfq
 * @date 2022-11-15 18:21
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    private final String MODULE = "用户管理";

    /**
     * 部门全路径缓存
     */
    private Map<String, String> fullDeptMap = new HashMap<>(64);

    @Autowired
    private AuUserDao userDao;
    @Autowired
    private DeviceEquipmentDao equipmentDao;
    @Autowired
    private AuDepartmentDao departmentDao;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private LogAdminHandler logAdminHandler;
    @Autowired
    private IssuedUserHandler issuedUserHandler;
    @Autowired
    private MdmEventPublisher mdmEventPublisher;
    @Autowired
    private MdmImportHistoryDao importHistoryDao;
    @Autowired
    private MinIoManager minIoManager;
    @Autowired
    private DepartmentService departmentService;
    @Autowired
    private EquipmentService equipmentService;

    @Override
    public ResponseResult listDeptUser(UserDeptQO userDeptQO, LoginAdminDTO loginAdminDTO) {
        if (userDeptQO.getPageNum().equals(0)) {
            return ResponseResult.success(userDao.listUserDeptVO(loginAdminDTO.getCompanyId(), loginAdminDTO.getId(),
                    userDeptQO.getDepartmentIdList(), userDeptQO.getSearchType(), userDeptQO.getSearchValue()));
        }
        PageHelper.startPage(userDeptQO.getPageNum(), userDeptQO.getPageSize());
        return ResponseResult.success(PageUtil.getPageResult(userDao.listUserDeptVO(loginAdminDTO.getCompanyId(),
                loginAdminDTO.getId(), userDeptQO.getDepartmentIdList(), userDeptQO.getSearchType(), userDeptQO.getSearchValue())));
    }

    @Override
    public ResponseResult listUser(UserQO userQO, LoginAdminDTO loginAdminDTO) {
        userQO.setAdminId(loginAdminDTO.getId());
        userQO.setCompanyId(loginAdminDTO.getCompanyId());
        PageHelper.startPage(userQO.getPageNum(), userQO.getPageSize());
        List<UserVO> userVOList = userDao.listUser(userQO);
        if (CollectionUtils.isNotEmpty(userVOList)) {
            userVOList.forEach(userVO -> {
                LambdaQueryWrapper<DeviceEquipmentDO> queryWrapper = new LambdaQueryWrapper<DeviceEquipmentDO>()
                        .eq(DeviceEquipmentDO::getUserId, userVO.getId())
                        .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState());
                userVO.setDeviceEquipmentCount(equipmentDao.selectCount(queryWrapper));
                Integer state = equipmentDao.selectCount(queryWrapper.ne(DeviceEquipmentDO::getEquipmentState, EquipmentStateEnum.INACTIVATED.getState())) > 0 ? 1 : 0;
                userVO.setState(state);
                DepartmentHierarchyVO departmentHierarchyVo = departmentDao.selectParentId(userVO.getDepartmentId());
                Integer parentId = departmentHierarchyVo.getParentId();
                String departmentStr = recursionGetDepartment(departmentHierarchyVo.getName(), parentId);
                userVO.setDepartmentStr(departmentStr);
            });
        }
        return ResponseResult.success(PageUtil.getPageResult(userVOList));
    }

    @Override
    public ResponseResult listUserEquipmentVO(Integer userId) {
        return ResponseResult.success(equipmentDao.listUserEquipmentVO(userId));
    }

    private String recursionGetDepartment(String departmentName, Integer parentId) {
        if (!parentId.equals(BasicNumberConstants.ZERO)) {
            DepartmentHierarchyVO departmentHierarchyVo = departmentDao.selectParentId(parentId);
            departmentName = departmentHierarchyVo.getName().concat("-").concat(departmentName);
            departmentName = recursionGetDepartment(departmentName, departmentHierarchyVo.getParentId());
        }
        return departmentName;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult addUser(UserAddUpdateQO userAddUpdateQO, LoginAdminDTO loginAdminDTO) {
        // 用户名 手机号查重
        checkExist(userAddUpdateQO, loginAdminDTO);
        AuUserDO userDO = new AuUserDO();
        BeanUtils.copyProperties(userAddUpdateQO, userDO);
        // 加密登录密码
        userDO.setPassword(passwordEncoder.encode(userAddUpdateQO.getPassword()));
        userDO.setDepartmentName(departmentDao.selectById(userDO.getDepartmentId()).getDepartmentName());
        userDO.setCompanyId(loginAdminDTO.getCompanyId());
        userDao.insert(userDO);
        // log
        String content = "添加用户:" + userAddUpdateQO.getUserName();
        logAdminHandler.log(loginAdminDTO, ListUtil.toList(userDO.getId()), LogAdminTypeEnum.USER_TYPE.getLogType(), content, MODULE);
        return ResponseResult.success();
    }

    private void checkExist(UserAddUpdateQO userAddUpdateQO, LoginAdminDTO loginAdminDTO) {
        if (StringUtils.isNotBlank(userAddUpdateQO.getPhoneNumber())) {
            Long countPhoneNumber = new LambdaQueryChainWrapper<>(userDao)
                    .eq(AuUserDO::getCompanyId, loginAdminDTO.getCompanyId())
                    .eq(AuUserDO::getPhoneNumber, userAddUpdateQO.getPhoneNumber())
                    .eq(AuUserDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                    .ne(userAddUpdateQO.getId() != null, AuUserDO::getId, userAddUpdateQO.getId())
                    .count();
            if (countPhoneNumber > 0) {
                throw new ServiceException("手机号已存在");
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult updateUser(UserAddUpdateQO userAddUpdateQO, LoginAdminDTO loginAdminDTO) {
        // 用户名 手机号查重
        checkExist(userAddUpdateQO, loginAdminDTO);
        AuUserDO userDO = userDao.selectById(userAddUpdateQO.getId());
        AuDepartmentDO departmentDO = departmentDao.selectById(userAddUpdateQO.getDepartmentId());
        // 更新
        String password = userAddUpdateQO.getPassword();
        if (StringUtils.isNotBlank(userAddUpdateQO.getPassword())) {
            password = passwordEncoder.encode(userAddUpdateQO.getPassword());
        }
        new LambdaUpdateChainWrapper<>(userDao)
                .eq(AuUserDO::getId, userAddUpdateQO.getId())
                .set(AuUserDO::getUserName, userAddUpdateQO.getUserName())
                .set(AuUserDO::getPhoneNumber, userAddUpdateQO.getPhoneNumber())
                .set(AuUserDO::getDepartmentId, userAddUpdateQO.getDepartmentId())
                .set(AuUserDO::getDepartmentName, departmentDO.getDepartmentName())
                .set(AuUserDO::getRemark, userAddUpdateQO.getRemark())
                .set(StringUtils.isNotBlank(userAddUpdateQO.getPassword()), AuUserDO::getPassword, password)
                .update();
        // 修改设备表用户信息
        new LambdaUpdateChainWrapper<>(equipmentDao)
                .set(StringUtils.isNotBlank(userAddUpdateQO.getUserName()), DeviceEquipmentDO::getUserName, userAddUpdateQO.getUserName())
                .set(userAddUpdateQO.getDepartmentId() != null, DeviceEquipmentDO::getDepartmentId, userAddUpdateQO.getDepartmentId())
                .set(DeviceEquipmentDO::getDepartmentName, departmentDO.getDepartmentName())
                .eq(DeviceEquipmentDO::getUserId, userAddUpdateQO.getId())
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .update();
        // 下发
        List<Integer> deviceIdList = equipmentDao.selectDeviceEquipmentIdByUserId(ListUtil.toList(userAddUpdateQO.getId()));
        issuedUserHandler.issuedUser(deviceIdList, loginAdminDTO.getCompanyId(), SendCmdConfigEnum.UPDATE_USER_INFO.getCmd(), null);
        // log
        String content = "编辑用户:" + userAddUpdateQO.getUserName();
        logAdminHandler.log(loginAdminDTO, userDO.getUserName(), LogAdminTypeEnum.USER_TYPE.getLogType(), content, MODULE);
        return ResponseResult.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult deleteUser(List<Integer> userIdList, LoginAdminDTO loginAdminDTO) {
        // log
        logAdminHandler.log(loginAdminDTO, userIdList, LogAdminTypeEnum.USER_TYPE.getLogType(), "删除用户", MODULE);
        // 修改删除标识
        new LambdaUpdateChainWrapper<>(userDao)
                .set(AuUserDO::getIsDelete, DeleteStateEnum.DELETED.getState())
                .in(AuUserDO::getId, userIdList)
                .update();
        // 删除用户相关数据
        mdmEventPublisher.publishUserDeleteEvent(userIdList);
        // 删除设备
        List<Integer> deviceIdList = equipmentDao.selectDeviceEquipmentIdByUserId(userIdList);
        if (CollectionUtils.isNotEmpty(deviceIdList)) {
            return equipmentService.deleteEquipment(deviceIdList, loginAdminDTO);
        }
        return ResponseResult.success();
    }

    @Override
    public ResponseResult importUser(MultipartFile file, Integer isAutoCreateDept, LoginAdminDTO loginAdminDTO) throws Exception {
        Date date = new Date();
        UserImportDataListener userImportDataListener = new UserImportDataListener(userDao, departmentService,
                departmentDao, loginAdminDTO.getCompanyId());
        userImportDataListener.setMinIoManager(minIoManager);
        userImportDataListener.setPasswordEncoder(passwordEncoder);
        userImportDataListener.setIsAutoCreateDept(isAutoCreateDept);
        EasyExcel.read(file.getInputStream(), UserImportData.class, userImportDataListener)
                .sheet()
                .headRowNumber(2)
                .doRead();
        // 保存部门数据权限
        if (CollectionUtils.isNotEmpty(userImportDataListener.getSuccessDeptIdSet())) {
            departmentService.saveAdminDept(ListUtil.toList(userImportDataListener.getSuccessDeptIdSet()), loginAdminDTO);
            userImportDataListener.getSuccessDeptIdSet().clear();
        }
        MdmImportHistoryDO mdmImportHistoryDO = MdmImportHistoryDO.builder()
                .importType(2)
                .operator(loginAdminDTO.getAdminName())
                .allCount(userImportDataListener.getAllCount())
                .successCount(userImportDataListener.getSuccessCount())
                .failCount(userImportDataListener.getFailCount())
                .finishTime(new Date())
                .failFileUrl(userImportDataListener.getFailFileUrl())
                .companyId(loginAdminDTO.getCompanyId())
                .createTime(date)
                .build();
        importHistoryDao.insert(mdmImportHistoryDO);
        // log
        logAdminHandler.log(loginAdminDTO, "", LogAdminTypeEnum.USER_TYPE.getLogType(), "用户导入", MODULE);
        return ResponseResult.success(mdmImportHistoryDO);
    }

    @Override
    public void exportUser(List<Integer> deptIdList, LoginAdminDTO loginAdminDTO, HttpServletResponse response) throws IOException {
        UserQO userQO = new UserQO();
        userQO.setAdminId(loginAdminDTO.getId());
        userQO.setCompanyId(loginAdminDTO.getCompanyId());
        userQO.setDepartmentIdList(deptIdList);
        List<UserVO> userVOList = userDao.listUser(userQO);
        List<UserExportDTO> userExportDTOList = partitionBuild(userVOList);
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码
        String fileName = URLEncoder.encode("用户导出", "UTF-8");
        response.setHeader("content-disposition", "attachment;filename=" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), UserExportDTO.class)
                .sheet()
                .doWrite(userExportDTOList);
        // log
        String content = "用户导出-全部";
        if (CollectionUtils.isNotEmpty(deptIdList)) {
            content = "用户导出-部分";
        }
        logAdminHandler.log(loginAdminDTO, LogAdminTypeEnum.USER_TYPE.getLogType(), content, deptIdList, MODULE);
    }

    private List<UserExportDTO> partitionBuild(List<UserVO> userVOList) {
        if (CollectionUtils.isNotEmpty(userVOList)) {
            List<UserExportDTO> userExportDTOList = Lists.newArrayListWithCapacity(userVOList.size());
            List<List<UserVO>> partition = Lists.partition(userVOList, 1000);
            CountDownLatch countDownLatch = new CountDownLatch(partition.size());
            for (List<UserVO> userVOS : partition) {
                MdmThreadPoolUtil.execute(() -> {
                    List<Integer> userIdList = userVOS.stream().map(UserVO::getId).collect(Collectors.toList());
                    List<EquipmentNameBO> equipmentNameBOList = equipmentDao.listEquipmentNameBO(userIdList);
                    List<UserExportDTO> userExportDTOS = userVOS.stream()
                            .map(userVO -> getUserExportDTO(equipmentNameBOList, userVO))
                            .collect(Collectors.toList());
                    userExportDTOList.addAll(userExportDTOS);
                    countDownLatch.countDown();
                });
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                log.error("用户分区导出异常:", e);
            }
            fullDeptMap.clear();
            return userExportDTOList;
        }
        return Collections.emptyList();
    }

    private UserExportDTO getUserExportDTO(List<EquipmentNameBO> equipmentNameBOList, UserVO userVO) {
        String department;
        if (fullDeptMap.containsKey(userVO.getDepartmentId().toString())) {
            department = fullDeptMap.get(userVO.getDepartmentId().toString());
        } else {
            department = departmentService.getFullDept(userVO.getDepartmentId());
            fullDeptMap.put(userVO.getDepartmentId().toString(), department);
        }
        UserExportDTO userExportDTO = UserExportDTO.builder()
                .userName(userVO.getUserName())
                .phoneNumber(userVO.getPhoneNumber())
                .department(department)
                .userDeviceCount(0)
                .build();
        if (CollectionUtils.isNotEmpty(equipmentNameBOList)) {
            equipmentNameBOList.stream().filter(equipmentNameBO -> userVO.getId().equals(equipmentNameBO.getUserId()))
                    .findFirst()
                    .ifPresent(equipmentNameBO -> {
                        userExportDTO.setUserDeviceCount(equipmentNameBO.getEquipmentCount());
                        userExportDTO.setEquipmentName(equipmentNameBO.getEquipmentName());
                    });
        }
        return userExportDTO;
    }

    @Override
    public void exportUserQrCode(List<Integer> deptIdList, LoginAdminDTO loginAdminDTO, HttpServletResponse response) {
        UserQO userQO = new UserQO();
        userQO.setAdminId(loginAdminDTO.getId());
        userQO.setCompanyId(loginAdminDTO.getCompanyId());
        userQO.setDepartmentIdList(deptIdList);
        List<UserVO> userVOList = userDao.listUser(userQO);
        if (CollectionUtils.isNotEmpty(userVOList)) {
            // 创建二维码保存目录
            String qrCodePath = ServerSystemUtil.userDir().concat(File.separator).concat("qrCode");
            File qrCodeDir = FileUtil.mkdir(qrCodePath);
            partitionProcess(userVOList, qrCodeDir);
            // 打包压缩文件
            File zip = ZipUtil.zip(qrCodeDir);
            //设值文件大小属性，浏览器用于判断文件是否写完
            response.addHeader("Content-Length", "" + zip.length());
            // 定义输出类型为二进制流输出
            response.setContentType("application/octet-stream");
            try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(zip));
                 ServletOutputStream outputStream = response.getOutputStream()) {
                // 这里URLEncoder.encode可以防止中文乱码
                String fileName = URLEncoder.encode(zip.getName(), "UTF-8");
                response.setHeader("content-disposition", fileName);
                IoUtil.copy(bufferedInputStream, outputStream);
            } catch (IOException e) {
                log.error("IOException", e);
            }
            // 清理缓存
            fullDeptMap.clear();
            FileUtil.del(qrCodePath);
            FileUtil.del(zip);
        }
        // log
        String content = "用户二维码导出-全部";
        if (CollectionUtils.isNotEmpty(deptIdList)) {
            content = "用户二维码导出-部分";
        }
        logAdminHandler.log(loginAdminDTO, LogAdminTypeEnum.USER_TYPE.getLogType(), content, deptIdList, MODULE);
    }

    private void partitionProcess(List<UserVO> userVOList, File qrCodeDir) {
        List<List<UserVO>> partition = Lists.partition(userVOList, 1000);
        CountDownLatch countDownLatch = new CountDownLatch(partition.size());
        for (List<UserVO> userVOS : partition) {
            MdmThreadPoolUtil.execute(() -> {
                for (UserVO userVO : userVOS) {
                    createQrCode(qrCodeDir, userVO);
                }
                countDownLatch.countDown();
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            log.error("用户二维码分区导出异常:", e);
        }
    }

    private void createQrCode(File qrCodeDir, UserVO userVO) {
        String department;
        JSONObject content = JSONUtil.createObj();
        content.put("companyId", userVO.getCompanyId());
        content.put("name", userVO.getUserName());
        content.put("phone", userVO.getPhoneNumber());
        if (fullDeptMap.containsKey(userVO.getDepartmentId().toString())) {
            department = fullDeptMap.get(userVO.getDepartmentId().toString());
        } else {
            department = departmentService.getFullDept(userVO.getDepartmentId());
            fullDeptMap.put(userVO.getDepartmentId().toString(), department);
        }
        department = StringUtils.replace(department, "/", "-");
        content.put("department", department);
        String fileName = new StringBuilder()
                .append(department).append("_")
                .append(userVO.getUserName()).append("_")
                .append(userVO.getPhoneNumber()).append(".png")
                .toString();
        QrCodeUtil.generate(JSONUtil.toJsonStr(content), 300, 300, FileUtil.file(qrCodeDir, fileName));
    }
}
