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

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.alibaba.excel.EasyExcel;
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.excel.EquipmentImportData;
import com.skr.mdm.api.equipment.excel.EquipmentImportDataListener;
import com.skr.mdm.api.equipment.service.EquipmentService;
import com.skr.mdm.api.license.MdmLicenseVerify;
import com.skr.mdm.api.system.service.DepartmentService;
import com.skr.mdm.bean.dto.EquipmentExportDTO;
import com.skr.mdm.bean.qo.BaseQueryQO;
import com.skr.mdm.bean.qo.GaodeTerminalQO;
import com.skr.mdm.bean.qo.equipment.EquipmentAddUpdateQO;
import com.skr.mdm.bean.qo.equipment.EquipmentPageQO;
import com.skr.mdm.bean.vo.equipment.AppFlowStatisticsVO;
import com.skr.mdm.bean.vo.equipment.DeviceEquipmentVO;
import com.skr.mdm.bean.vo.equipment.DeviceSimRecordVO;
import com.skr.mdm.bean.vo.equipment.EquipmentAppUseStatisticsVO;
import com.skr.mdm.bean.vo.statistics.AppUseDurationVO;
import com.skr.mdm.constant.RegexConstants;
import com.skr.mdm.dao.*;
import com.skr.mdm.dto.LoginAdminDTO;
import com.skr.mdm.entity.AuCompanyDO;
import com.skr.mdm.entity.AuUserDO;
import com.skr.mdm.entity.DeviceEquipmentDO;
import com.skr.mdm.entity.MdmImportHistoryDO;
import com.skr.mdm.enums.*;
import com.skr.mdm.exception.ServiceException;
import com.skr.mdm.handle.IssuedUserHandler;
import com.skr.mdm.handle.LogAdminHandler;
import com.skr.mdm.manager.DeviceManager;
import com.skr.mdm.manager.oss.MinIoManager;
import com.skr.mdm.result.ResponseResult;
import com.skr.mdm.util.*;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author nfq
 * @date 2023-02-01 14:22
 */
@Slf4j
@Service
public class EquipmentServiceImpl implements EquipmentService {

    private final String MODULE = "设备管理";

    private Map<String, String> fullDeptMap = new HashMap<>(64);

    @Value("${mdm.license.company}")
    private Boolean companyLicense;

    @Autowired
    private DeviceEquipmentDao equipmentDao;
    @Autowired
    private AuUserDao userDao;
    @Autowired
    private AuCompanyDao companyDao;
    @Autowired
    private LogAdminHandler logAdminHandler;
    @Autowired
    private IssuedUserHandler issuedUserHandler;
    @Autowired
    private DeviceManager deviceManager;
    @Autowired
    private MinIoManager minIoManager;
    @Autowired
    private MdmImportHistoryDao importHistoryDao;
    @Autowired
    private DepartmentService departmentService;

    private static void registerGaodeTerminal(AuCompanyDO companyDO, DeviceEquipmentDO equipmentDO, String phoneNumber) {
        if (companyDO.getSid() != null) {
            GaodeTerminalQO gaodeTerminalQO = GaodeTerminalQO.builder()
                    .sid(companyDO.getSid())
                    .phoneNumber(phoneNumber)
                    .equipmentName(equipmentDO.getEquipmentName())
                    .userName(equipmentDO.getUserName())
                    .departmentName(equipmentDO.getDepartmentName())
                    .companyName(companyDO.getCompanyName())
                    .imei(equipmentDO.getImei())
                    .oaid(equipmentDO.getOaid())
                    .uuid(equipmentDO.getUuid())
                    .build();
            RegisterEquipmentUtil.registerGaodeTerminal(gaodeTerminalQO, equipmentDO);
        }
    }

    private static void checkEquipmentNameFormat(EquipmentAddUpdateQO equipmentAddUpdateQO) {
        if (!Validator.isMatchRegex(RegexConstants.EQUIPMENT_NAME_REGEX, equipmentAddUpdateQO.getEquipmentName())) {
            throw new ServiceException(ResultCodeEnum.EQUIPMENT_VERIFY);
        }
    }

    @Override
    public ResponseResult listEquipment(EquipmentPageQO equipmentPageQO, LoginAdminDTO loginAdminDTO) {
        equipmentPageQO.setCompanyId(loginAdminDTO.getCompanyId());
        equipmentPageQO.setAdminId(loginAdminDTO.getId());
        PageHelper.startPage(equipmentPageQO.getPageNum(), equipmentPageQO.getPageSize());
        List<DeviceEquipmentVO> equipmentVOList = equipmentDao.listEquipment(equipmentPageQO);
        if (CollectionUtils.isNotEmpty(equipmentVOList)) {
            equipmentVOList.forEach(deviceEquipmentVO -> {
                String offlineUnlockCode = deviceEquipmentVO.getOfflineUnlockCode();
                deviceEquipmentVO.setOfflineUnlockCode(HmacShaUtils.getCode(offlineUnlockCode));
            });
        }
        return ResponseResult.success(PageUtil.getPageResult(equipmentVOList));
    }

    @Override
    public ResponseResult addEquipment(EquipmentAddUpdateQO equipmentAddUpdateQO, LoginAdminDTO loginAdminDTO) {
        // 检查设备名称格式
        checkEquipmentNameFormat(equipmentAddUpdateQO);
        // 设备查重
        checkDuplication(equipmentAddUpdateQO, loginAdminDTO.getCompanyId());
        // 检查设备授权数量限制
        AuCompanyDO companyDO = companyDao.selectById(loginAdminDTO.getCompanyId());
        checkLicenseLimit(loginAdminDTO.getCompanyId(), companyDO.getAllowUseNumber());
        // 保存
        DeviceEquipmentDO equipmentDO = new DeviceEquipmentDO();
        AuUserDO userDO = userDao.selectById(equipmentAddUpdateQO.getUserId());
        equipmentDO.setUserName(userDO.getUserName());
        equipmentDO.setCompanyId(loginAdminDTO.getCompanyId());
        equipmentDO.setDepartmentId(userDO.getDepartmentId());
        equipmentDO.setDepartmentName(userDO.getDepartmentName());
        equipmentDO.setState(EquipmentStateEnum.INACTIVATED.getState());
        BeanUtils.copyProperties(equipmentAddUpdateQO, equipmentDO);
        // 创建猎鹰终端
        registerGaodeTerminal(companyDO, equipmentDO, userDO.getPhoneNumber());
        equipmentDao.insert(equipmentDO);
        // log
        logAdminHandler.log(loginAdminDTO, equipmentAddUpdateQO.getEquipmentName(), LogAdminTypeEnum.DEVICE_TYPE.getLogType(), "添加设备", MODULE);
        return ResponseResult.success();
    }

    private void checkLicenseLimit(Integer companyId, String allowUseNumber) {
        Long countAllEquipment = getAllEquipmentCount(companyId);
        if (companyLicense) {
            SM2 sm2 = SmUtils.sm2();
            String authorizationNumber = StrUtil.utf8Str(sm2.decryptFromBcd(allowUseNumber, KeyType.PrivateKey));
            if (countAllEquipment >= Integer.parseInt(authorizationNumber)) {
                throw new ServiceException(ResultCodeEnum.NUMBER_OVER);
            }
        } else {
            if (MdmLicenseVerify.isOverAllowUseNumber(countAllEquipment + 1)) {
                throw new ServiceException(ResultCodeEnum.NUMBER_OVER);
            }
        }
    }

    private Long getAllEquipmentCount(Integer companyId) {
        return new LambdaQueryChainWrapper<>(equipmentDao)
                .eq(DeviceEquipmentDO::getCompanyId, companyId)
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .count();
    }

    private void checkDuplication(EquipmentAddUpdateQO equipmentAddUpdateQO, Integer companyId) {
        Long countEquipmentName = new LambdaQueryChainWrapper<>(equipmentDao)
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .eq(DeviceEquipmentDO::getUserId, equipmentAddUpdateQO.getUserId())
                .eq(DeviceEquipmentDO::getEquipmentName, equipmentAddUpdateQO.getEquipmentName())
                .ne(equipmentAddUpdateQO.getId() != null, DeviceEquipmentDO::getId, equipmentAddUpdateQO.getId())
                .count();
        if (countEquipmentName > 0) {
            throw new ServiceException("该用户设备名称已存在！");
        }
        if (StringUtils.isNotBlank(equipmentAddUpdateQO.getImei())) {
            if (!Pattern.matches(RegexConstants.IMEI_REGEX, equipmentAddUpdateQO.getImei()) &&
                    !Pattern.matches(RegexConstants.IMEI_NUMBER_REGEX, equipmentAddUpdateQO.getImei())) {
                throw new ServiceException("IMEI格式错误！");
            }
            Long countPhoneNumber = new LambdaQueryChainWrapper<>(equipmentDao)
                    .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                    .eq(DeviceEquipmentDO::getCompanyId, companyId)
                    .eq(DeviceEquipmentDO::getImei, equipmentAddUpdateQO.getImei())
                    .ne(equipmentAddUpdateQO.getId() != null, DeviceEquipmentDO::getId, equipmentAddUpdateQO.getId())
                    .count();
            if (countPhoneNumber > 0) {
                throw new ServiceException("设备IMEI已存在！");
            }
        }
        if (StringUtils.isNotBlank(equipmentAddUpdateQO.getOaid())) {
            if (equipmentAddUpdateQO.getOaid().length() > 32) {
                throw new ServiceException("OAID格式错误！");
            }
            Long countPhoneNumber = new LambdaQueryChainWrapper<>(equipmentDao)
                    .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                    .eq(DeviceEquipmentDO::getCompanyId, companyId)
                    .eq(DeviceEquipmentDO::getOaid, equipmentAddUpdateQO.getOaid())
                    .ne(equipmentAddUpdateQO.getId() != null, DeviceEquipmentDO::getId, equipmentAddUpdateQO.getId())
                    .count();
            if (countPhoneNumber > 0) {
                throw new ServiceException("设备OAID已存在！");
            }
        }
        if (StringUtils.isNotBlank(equipmentAddUpdateQO.getUuid())) {
            if (equipmentAddUpdateQO.getUuid().length() > 32) {
                throw new ServiceException("UUID格式错误！");
            }
            Long countPhoneNumber = new LambdaQueryChainWrapper<>(equipmentDao)
                    .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                    .eq(DeviceEquipmentDO::getCompanyId, companyId)
                    .eq(DeviceEquipmentDO::getUuid, equipmentAddUpdateQO.getUuid())
                    .ne(equipmentAddUpdateQO.getId() != null, DeviceEquipmentDO::getId, equipmentAddUpdateQO.getId())
                    .count();
            if (countPhoneNumber > 0) {
                throw new ServiceException("设备UUID已存在！");
            }
        }
    }

    @Override
    public ResponseResult updateEquipment(EquipmentAddUpdateQO equipmentAddUpdateQO, LoginAdminDTO loginAdminDTO) {
        // 检查设备名称格式
        checkEquipmentNameFormat(equipmentAddUpdateQO);
        // 设备查重
        checkDuplication(equipmentAddUpdateQO, loginAdminDTO.getCompanyId());
        // 更新
        DeviceEquipmentDO equipmentDO = equipmentDao.selectById(equipmentAddUpdateQO.getId());
        AuUserDO userDO = userDao.selectById(equipmentAddUpdateQO.getUserId());
        AuCompanyDO companyDO = companyDao.selectById(loginAdminDTO.getCompanyId());
        new LambdaUpdateChainWrapper<>(equipmentDao)
                .set(DeviceEquipmentDO::getEquipmentName, equipmentAddUpdateQO.getEquipmentName())
                .set(DeviceEquipmentDO::getEquipmentType, equipmentAddUpdateQO.getEquipmentType())
                .set(StringUtils.isNotBlank(equipmentAddUpdateQO.getImei()), DeviceEquipmentDO::getImei, equipmentAddUpdateQO.getImei())
                .set(StringUtils.isNotBlank(equipmentAddUpdateQO.getOaid()), DeviceEquipmentDO::getOaid, equipmentAddUpdateQO.getOaid())
                .set(StringUtils.isNotBlank(equipmentAddUpdateQO.getUuid()), DeviceEquipmentDO::getUuid, equipmentAddUpdateQO.getUuid())
                .set(DeviceEquipmentDO::getUserId, equipmentAddUpdateQO.getUserId())
                .set(DeviceEquipmentDO::getUserName, userDO.getUserName())
                .set(DeviceEquipmentDO::getDepartmentId, userDO.getDepartmentId())
                .set(DeviceEquipmentDO::getDepartmentName, userDO.getDepartmentName())
                .eq(DeviceEquipmentDO::getId, equipmentAddUpdateQO.getId())
                .update();
        //更新高德终端信息
        if (companyDO.getSid() != null && equipmentDO.getTid() != null) {
            GaodeTerminalQO gaodeTerminalQO = GaodeTerminalQO.builder()
                    .sid(companyDO.getSid())
                    .phoneNumber(userDO.getPhoneNumber())
                    .equipmentName(equipmentDO.getEquipmentName())
                    .userName(equipmentDO.getUserName())
                    .departmentName(equipmentDO.getDepartmentName())
                    .companyName(companyDO.getCompanyName())
                    .imei(equipmentDO.getImei())
                    .oaid(equipmentDO.getOaid())
                    .uuid(equipmentDO.getUuid())
                    .build();
            GaodeMapAPIUtil.updateTerminal(gaodeTerminalQO, equipmentDO.getTid());
        }
        issuedUserHandler.issuedUser(ListUtil.toList(equipmentAddUpdateQO.getId()), loginAdminDTO.getCompanyId(), "updateEquipment", "");
        // log
        String content = "编辑设备：" + equipmentAddUpdateQO.getEquipmentName();
        logAdminHandler.log(loginAdminDTO, equipmentDO.getEquipmentName(), LogAdminTypeEnum.DEVICE_TYPE.getLogType(), content, MODULE);
        return ResponseResult.success();
    }

    @Override
    public ResponseResult deleteEquipment(List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO) {
        // log
        List<String> equipmentNameList = equipmentDao.selectBatchIds(equipmentIdList)
                .stream().map(DeviceEquipmentDO::getEquipmentName)
                .collect(Collectors.toList());
        logAdminHandler.log(loginAdminDTO, StringUtils.join(equipmentNameList, ","),
                LogAdminTypeEnum.DEVICE_TYPE.getLogType(), "删除设备", MODULE);
        // 删除高德猎鹰终端
        List<Integer> deviceIdList = deviceManager.deleteGaodeTerminal(equipmentIdList, loginAdminDTO.getCompanyId());
        // 下发删除指令
        if (CollectionUtils.isNotEmpty(deviceIdList)) {
            equipmentIdList.removeAll(deviceIdList);
            issuedUserHandler.issuedUser(deviceIdList, loginAdminDTO.getCompanyId(), SendCmdConfigEnum.DELETE_USER_EQUIPMENT.getCmd(), "");
            // 发布设备删除事件
            deviceManager.deleteDevice(deviceIdList);
        }
        // 高德终端删除失败
        if (CollectionUtils.isNotEmpty(equipmentIdList)) {
            // 无法删除设备名称
            List<String> userNameList = equipmentDao.listEquipmentNameByIdList(deviceIdList);
            return ResponseResult.failure(ResultCodeEnum.GAO_DEL_MSG_FAILURE, StringUtils.join(userNameList, "、"));
        }
        return ResponseResult.success();
    }

    @Override
    public ResponseResult importEquipment(MultipartFile file, LoginAdminDTO loginAdminDTO) throws Exception {
        Date date = new Date();
        EquipmentImportDataListener equipmentImportDataListener = new EquipmentImportDataListener(userDao, equipmentDao,
                loginAdminDTO.getCompanyId());
        equipmentImportDataListener.setMinIoManager(minIoManager);
        AuCompanyDO companyDO = companyDao.selectById(loginAdminDTO.getCompanyId());
        equipmentImportDataListener.setSid(companyDO.getSid());
        equipmentImportDataListener.setCompanyName(companyDO.getCompanyName());
        // 获取当前公司设备总数
        equipmentImportDataListener.setExistEquipmentCount(getAllEquipmentCount(loginAdminDTO.getCompanyId()));
        // 获取授权数量
        equipmentImportDataListener.setAllowUseNumber(getAllowUseNumber(companyDO));
        EasyExcel.read(file.getInputStream(), EquipmentImportData.class, equipmentImportDataListener)
                .sheet()
                .headRowNumber(2)
                .doRead();
        MdmImportHistoryDO mdmImportHistoryDO = MdmImportHistoryDO.builder()
                .importType(3)
                .operator(loginAdminDTO.getAdminName())
                .allCount(equipmentImportDataListener.getAllCount())
                .successCount(equipmentImportDataListener.getSuccessCount())
                .failCount(equipmentImportDataListener.getFailCount())
                .finishTime(new Date())
                .failFileUrl(equipmentImportDataListener.getFailFileUrl())
                .companyId(loginAdminDTO.getCompanyId())
                .createTime(date)
                .build();
        importHistoryDao.insert(mdmImportHistoryDO);
        // log
        logAdminHandler.log(loginAdminDTO, "", LogAdminTypeEnum.DEVICE_TYPE.getLogType(), "设备导入", MODULE);
        return ResponseResult.success(mdmImportHistoryDO);
    }

    @Override
    public void exportEquipment(List<Integer> deptIdList, LoginAdminDTO loginAdminDTO, HttpServletResponse response) throws IOException {
        List<EquipmentExportDTO> equipmentExportDTOList = equipmentDao.listEquipmentExportDTO(loginAdminDTO.getId(),
                loginAdminDTO.getCompanyId(), deptIdList);
        if (CollectionUtils.isNotEmpty(equipmentExportDTOList)) {
            partitionProcess(equipmentExportDTOList);
        }
        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(), EquipmentExportDTO.class)
                .sheet()
                .doWrite(equipmentExportDTOList);
        // log
        String content = "设备导出-全部";
        if (CollectionUtils.isNotEmpty(deptIdList)) {
            content = "设备导出-部分";
        }
        logAdminHandler.log(loginAdminDTO, LogAdminTypeEnum.DEVICE_TYPE.getLogType(), content, deptIdList, MODULE);
    }

    private void partitionProcess(List<EquipmentExportDTO> equipmentExportDTOList) {
        List<List<EquipmentExportDTO>> partition = Lists.partition(equipmentExportDTOList, 10000);
        CountDownLatch countDownLatch = new CountDownLatch(partition.size());
        for (List<EquipmentExportDTO> equipmentExportDTOS : partition) {
            MdmThreadPoolUtil.execute(() -> {
                equipmentExportDTOS.forEach(equipmentExportDTO -> {
                    String department;
                    if (fullDeptMap.containsKey(equipmentExportDTO.getDepartmentId().toString())) {
                        department = fullDeptMap.get(equipmentExportDTO.getDepartmentId().toString());
                    } else {
                        department = departmentService.getFullDept(equipmentExportDTO.getDepartmentId());
                        fullDeptMap.put(equipmentExportDTO.getDepartmentId().toString(), department);
                    }
                    equipmentExportDTO.setDepartment(department);
                    if (EquipmentStateEnum.WS_ONLINE.getState().toString().equals(equipmentExportDTO.getEquipmentState()) ||
                            EquipmentStateEnum.HTTP_ONLINE.getState().toString().equals(equipmentExportDTO.getEquipmentState())) {
                        equipmentExportDTO.setEquipmentState("在线");
                        equipmentExportDTO.setOfflineTime(null);
                    } else if (EquipmentStateEnum.OFFLINE.getState().toString().equals(equipmentExportDTO.getEquipmentState())) {
                        equipmentExportDTO.setEquipmentState("离线");
                    } else if (EquipmentStateEnum.INACTIVATED.getState().toString().equals(equipmentExportDTO.getEquipmentState())) {
                        equipmentExportDTO.setEquipmentState("未激活");
                        equipmentExportDTO.setOfflineTime(null);
                    }
                });
                countDownLatch.countDown();
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            log.error("设备分批导出异常：", e);
        }
        fullDeptMap.clear();
    }

    private Integer getAllowUseNumber(AuCompanyDO companyDO) throws Exception {
        Integer allowUseNumber = 0;
        if (companyLicense) {
            SM2 sm2 = SmUtils.sm2();
            String authorizationNumber = StrUtil.utf8Str(sm2.decryptFromBcd(companyDO.getAllowUseNumber(), KeyType.PrivateKey));
            allowUseNumber = Integer.parseInt(authorizationNumber);
        } else {
            allowUseNumber = MdmLicenseVerify.getAllowUseNumber();
        }
        return allowUseNumber;
    }

    @Override
    public ResponseResult updateBoundState(Integer equipmentId, Integer state, LoginAdminDTO loginAdminDTO) {
        // TODO  绑定逻辑:下发时间段全局数据 解绑逻辑:设备为管控状态
        new LambdaUpdateChainWrapper<>(equipmentDao)
                .set(DeviceEquipmentDO::getBoundState, state)
                .eq(DeviceEquipmentDO::getCompanyId, loginAdminDTO.getCompanyId())
                .eq(DeviceEquipmentDO::getId, equipmentId)
                .update();
        // 但设备进行绑定时 重新下发全局信息
        String content = "设备解绑";
        if (0 == state) {
            content = "设备绑定";
            // 下发 新数据
//            operationDeviceToolAddHandle.insertEquipmentInfo(Collections.singletonList(equipmentId), admin.getCompanyId());
        }
        // log
        DeviceEquipmentDO equipmentDO = equipmentDao.selectById(equipmentId);
        logAdminHandler.log(loginAdminDTO, equipmentDO.getEquipmentName(), LogAdminTypeEnum.USER_TYPE.getLogType(), content, "用户管理");
        return ResponseResult.success();
    }

    @Override
    public ResponseResult cardUnbind(Integer equipmentId, LoginAdminDTO loginAdminDTO) {
        issuedUserHandler.issuedUser(ListUtil.toList(equipmentId), loginAdminDTO.getCompanyId(), "cardUnbind", "");
        return ResponseResult.success();
    }

    @Override
    public ResponseResult offLineUnlockCode(String offLineUnlockCode, LoginAdminDTO loginAdminDTO) {
        logAdminHandler.log(loginAdminDTO, offLineUnlockCode, LogAdminTypeEnum.DEVICE_TYPE.getLogType(),
                "生成离线解锁码", MODULE);
        return ResponseResult.success(HmacShaUtils.getCode(offLineUnlockCode));
    }

}