package com.yida.data.attendance.service.impl;

import cc.mrbird.febs.common.redis.service.RedisService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yida.data.attendance.dto.DevicePage;
import com.yida.data.attendance.dto.IssuedFaceDTO;
import com.yida.data.attendance.dto.RemoveFaceDTO;
import com.yida.data.attendance.mapper.EduAttendanceDeviceMapper;
import com.yida.data.attendance.service.AttendanceRuleDeviceService;
import com.yida.data.attendance.service.EduAttendanceDeviceService;
import com.yida.data.attendance.utils.MqttUtil;
import com.yida.data.attendance.vo.DeviceNumberVO;
import com.yida.data.attendance.vo.DeviceVO;
import com.yida.data.attendance.vo.OnlineCountVO;
import com.yida.data.common.core.common.ResultBean;
import com.yida.data.common.core.entity.CurrentUser;
import com.yida.data.common.core.entity.agent.EduAgentWxPublicReceiver;
import com.yida.data.common.core.entity.attendance.EduAttendanceDevice;
import com.yida.data.common.core.entity.attendance.attendance.AttendanceRuleDevice;
import com.yida.data.common.core.entity.attendance.enums.DeviceStateEnum;
import com.yida.data.common.core.entity.constant.CachePrefixConstant;
import com.yida.data.common.core.entity.constant.hq.HaiqingConstant;
import com.yida.data.common.core.entity.constant.LockPrefixConstant;
import com.yida.data.common.core.entity.constant.StringConstant;
import com.yida.data.common.core.entity.system.Dept;
import com.yida.data.common.core.entity.system.EduDeptWxPublic;
import com.yida.data.common.core.entity.system.enums.RoleEnum;
import com.yida.data.common.core.entity.user.EduFaceGroupUser;
import com.yida.data.common.core.entity.user.EduUserDevice;
import com.yida.data.common.core.entity.user.EduUserDeviceFactory;
import com.yida.data.common.core.entity.user.EduUserFace;
import com.yida.data.common.core.enums.DeviceType;
import com.yida.data.common.core.enums.IssuedTypeEnum;
import com.yida.data.common.core.exception.FebsException;
import com.yida.data.common.core.utils.EnumUtils;
import com.yida.data.common.core.utils.FebsUtil;
import com.yida.data.common.core.utils.WxPublicUtil;
import com.yida.data.common.service.CommonService;
import com.yida.data.system.feign.RemoteAgentService;
import com.yida.data.system.feign.RemoteDeptService;
import com.yida.data.user.dto.UpdateFaceGroupUserStatusDTO;
import com.yida.data.user.feign.RemoteUserDeviceFactoryService;
import com.yida.data.user.feign.RemoteUserDeviceService;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.yida.data.user.feign.RemoteUserFaceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
@RequiredArgsConstructor
public class EduAttendanceDeviceServiceImpl extends
        ServiceImpl<EduAttendanceDeviceMapper, EduAttendanceDevice> implements EduAttendanceDeviceService {

    private static final ThreadFactory FACE_THREAD_FACTORY = new ThreadFactoryBuilder()
            .setNamePrefix("hq-issue-face-factory-").build();
    private static final ExecutorService FACE_POOL = new ThreadPoolExecutor(10, 10, 1000,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), FACE_THREAD_FACTORY);

    private final AttendanceRuleDeviceService attendanceRuleDeviceService;

    private final RemoteDeptService remoteDeptService;
    private final RemoteAgentService remoteAgentService;
    private final RemoteUserFaceService remoteUserFaceService;
    private final RemoteUserDeviceService remoteUserDeviceService;
    private final RemoteUserDeviceFactoryService remoteUserDeviceFactoryService;

    private final MqttUtil mqttUtil;
    private final RedisService redisService;
    private final WxPublicUtil wxPublicUtil;
    private final CommonService commonService;

    @Override

    public DevicePage<DeviceVO> listDevicePage(DevicePage page, String schoolName, String number,
                                               Integer scope, Integer type, Integer state,
                                               CurrentUser currentUser) {
        List<Long> schoolIds = new ArrayList<>();
        // 查询关联学校信息
        List<Dept> deptList = this.remoteDeptService.getSchoolByArea(null, schoolName).getData();
        if (CollUtil.isNotEmpty(deptList)) {
            schoolIds = deptList.stream().map(Dept::getDeptId).collect(Collectors.toList());
        } else {
            // 添加默认值，防止报错
            schoolIds.add(-1L);
        }

        DevicePage<DeviceVO> res = baseMapper.listDevicePage(page, schoolIds, number, scope, type, state);
        // 在线离线数量
        res.setOnlineNumber((int) count(Wrappers.<EduAttendanceDevice>lambdaQuery()
                .eq(EduAttendanceDevice::getState, DeviceStateEnum.ONLINE_STATUS.getStatus())
                .in(EduAttendanceDevice::getSchoolId, schoolIds)));
        res.setOfflineNumber((int) count(Wrappers.<EduAttendanceDevice>lambdaQuery()
                .eq(EduAttendanceDevice::getState, DeviceStateEnum.OFFLINE_STATUS.getStatus())
                .in(EduAttendanceDevice::getSchoolId, schoolIds)));

        if (CollUtil.isNotEmpty(res.getRecords())) {
            for (DeviceVO record : res.getRecords()) {
                record.setSchoolName(commonService.getDept(record.getSchoolId()).getDeptName());
            }
        }
        return res;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public EduAttendanceDevice saveDevice(EduAttendanceDevice device) {

        // 判断设备编号是否重复
        EduAttendanceDevice exist = baseMapper.checkSn(device);
        if (Objects.nonNull(exist)) {
            throw new FebsException("该设备号已存在！请检查后重新保存");
        }

        if (StrUtil.isNotEmpty(device.getSn()) && DeviceType.HAIQING.getValue()
                .equals(device.getDeviceType())) {
            // 设置删除策略信息
            JSONObject params = JSONUtil.createObj()
                    .set("operator", HaiqingConstant.UP_DOOR_CONFIG)
                    .set("messageId", StrUtil.uuid().replace("-", ""))
                    .set("info", JSONUtil.createObj()
                            // 0：未开启（绑定空策略人员允许通行），1：开启（绑定空策略人员不可通行）
                            .set("IsASCheck", "1"));
            Boolean flag = mqttUtil.publish(params.toString(), "mqtt/face/" + device.getSn());
            if (!flag) {
                throw new FebsException("保存设备失败");
            }

            if (Objects.isNull(device.getId())) {
                mqttUtil.addTopic("$queue/mqtt/face/" + device.getSn() + "/Ack");
                mqttUtil.addTopic("$queue/mqtt/face/" + device.getSn() + "/Rec");
            }
        }
        if (StrUtil.isNotBlank(device.getMacAddress())) {
            device.setMacAddress(device.getMacAddress().toUpperCase());
        }
        saveOrUpdate(device);
        redisService.hdel(CachePrefixConstant.DEVICE_DATA, device.getSn());
        redisService.hdel(CachePrefixConstant.DEVICE_DATA_BY_ID, String.valueOf(device.getId()));
        return device;
    }

    /**
     * 批量openDoor
     *
     * @param list
     */

    @Override
    public void openAllDoor(List<String> list) {
        list.forEach(s -> mqttUtil.publish(spliceOpenDoor(),
                spliceTopic(s)));
    }

    /**
     * 批量设置时间
     *
     * @param list
     */
    @Override
    public void setAllTime(List<String> list) {
        list.forEach(s -> mqttUtil.publish(spliceSetSysTime(), spliceTopic(s)));
    }

    @Override
    public void openDoor(String id) {
        mqttUtil.publish(spliceOpenDoor(), spliceTopic(id));

    }

    @Override
    public void setSysTime(String id) {
        mqttUtil.publish(spliceSetSysTime(), spliceTopic(id));
    }

    @Override
    public void issuedFace(IssuedFaceDTO dto) {
        List<EduUserFace> userFaceList = dto.getUserFaceList();
        List<EduAttendanceDevice> deviceList = listByIds(dto.getDeviceList());
        // 在线设备数量
        long onlineNumber = deviceList.stream().filter(eduAttendanceDevice ->
                        DeviceStateEnum.ONLINE_STATUS.getStatus().equals(eduAttendanceDevice.getState()))
                .count();
        // 判断是否有策略信息
//        boolean hasStrategyFlag = Objects.nonNull(dto.getGroupStrategy());
        // 海清设备
        if (DeviceType.HAIQING.getValue().equals(deviceList.get(0).getDeviceType())) {
            // 每次下发的数量(必须大于每次最大下发数量)
            int size = 100;
            // 下发总数
            int totalSize = userFaceList.size() * deviceList.size();
            redisService.set(CachePrefixConstant.ISSUE_FACE_TOTAL + dto.getGroupId(), totalSize);
            // 进度相关信息
            redisService.set(CachePrefixConstant.ISSUE_FACE_FINISH + dto.getGroupId(), 0);
            redisService.set(CachePrefixConstant.ISSUE_FACE_ERROR + dto.getGroupId(), 0);
            // 错误信息key值
            Set<String> keys = redisService.keys(CachePrefixConstant.ISSUE_FACE_ERROR_MSG + dto.getGroupId());
            redisService.del(keys.toArray(new String[0]));

            // 遍历向每个设备下发信息
            for (EduAttendanceDevice device : deviceList) {
                // 不同接口操作同一台设备时阻塞
                while (redisService.hasKey(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn())
                        || redisService.hasKey(CachePrefixConstant.HQ_BATCH_ADD + device.getSn())
                        || redisService.hasKey(CachePrefixConstant.HQ_BATCH_EDIT + device.getSn())) {
                    try {
                        ThreadUtil.sleep(1000);
                    } catch (Exception e) {
                        log.error("阻塞设备信息失败", e);
                    }
                }

                // 将人脸数据缓存到redis中
                Map<Object, Object> userFaceMap = new HashMap<>();
                for (EduUserFace userFace : userFaceList) {
                    userFace.setDeviceName(device.getName());
                    userFaceMap.put(userFace.getUserId().toString() + userFace.getUserType().toString(), userFace);
                }
                // 离线设备
                if (DeviceStateEnum.OFFLINE_STATUS.getStatus().equals(device.getState())) {
                    // 添加错误数据
                    dealErrorMessage(dto.getGroupId(), device.getSn(), userFaceMap);
                    continue;
                }
                // 保存每个设备要添加的信息
                redisService.hmset(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn(), userFaceMap);
                // 添加人脸组在线设备数量(用于回调判断)
                redisService.set(CachePrefixConstant.ISSUE_FACE_GROUP_DEVICE_NUMBER + device.getSn(), onlineNumber);

                // 判断是否设置当前设备下发的人脸组id
                if (IssuedTypeEnum.FACE_GROUP.equals(dto.getIssuedTypeEnum())) {
                    // 当前设备下发的人脸组
                    redisService.hset(CachePrefixConstant.ISSUE_FACE_GROUP_DEVICE, device.getSn(), dto.getGroupId());
                }
                // 每个设备一个线程
                FACE_POOL.execute(() -> {
                    // 用户与厂商关联信息
                    List<EduUserDeviceFactory> userDeviceFactoryList = new ArrayList<>();
                    while (redisService.hasKey(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn())) {
                        // 添加人脸
                        JSONObject addParams = new JSONObject();
                        addParams.set("operator", HaiqingConstant.ADD_PERSONS_BATCH);
                        addParams.set("DataBegin", "BeginFlag");
                        addParams.set("DataEnd", "EndFlag");
                        JSONArray addUserFaces = new JSONArray();
                        Map<Object, Object> addMap = new HashMap<>();
                        List<String> addKey = new ArrayList<>();
                        // 修改人脸
                        JSONObject editParams = new JSONObject();
                        editParams.set("operator", HaiqingConstant.EDIT_PERSONS_BATCH);
                        editParams.set("DataBegin", "BeginFlag");
                        editParams.set("DataEnd", "EndFlag");
                        JSONArray editUserFaces = new JSONArray();
                        Map<Object, Object> editMap = new HashMap<>();
                        List<String> editKey = new ArrayList<>();
                        // 下发的所有是数据
                        Map<Object, Object> saveData = redisService
                                .hmget(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn());
                        log.info("循环下发");
                        log.info("数据：[{}]", saveData);
                        if (saveData.size() == 0) {
                            break;
                        }
                        Set<Map.Entry<Object, Object>> entries = saveData.entrySet();
                        log.info("处理后数据：[{}]", entries);
                        log.info("锁标识：[{}]",
                                redisService.get(LockPrefixConstant.HQ_DEVICE_USER_EDIT_LOCK + device.getSn()));
                        for (int i = 0; i < entries.size(); i += size) {
                            // 每size个下发一次
                            List<EduUserFace> tempFace;
                            if (i + size <= entries.size()) {
                                tempFace =
                                        CollUtil.sub(entries, i, i + size).stream().map(x -> ((EduUserFace) x.getValue()))
                                                .collect(Collectors.toList());
                            } else {
                                tempFace =
                                        CollUtil.sub(entries, i, i + entries.size()).stream()
                                                .map(x -> ((EduUserFace) x.getValue())).collect(Collectors.toList());
                            }
                            for (EduUserFace userFace : tempFace) {
                                JSONObject oneUserFace = new JSONObject();
                                oneUserFace.set("name", userFace.getName());
                                String customId = userFace.getUserId().toString() + userFace.getUserType().toString();
                                oneUserFace.set("customId", customId);
                                oneUserFace.set("picURI", userFace.getPicPath());
                                // 永久白名单
                                oneUserFace.set("personType", 0);
                                oneUserFace.set("tempCardType", 0);
                                oneUserFace.set("notes", userFace.getNotes());
                                oneUserFace.set("cardValidBegin", userFace.getCardValidBegin());
                                oneUserFace.set("cardValidEnd", userFace.getCardValidEnd());
                                oneUserFace.set("RFIDCard", userFace.getRfidCard());
                                // 判断人脸和设备关系
                                List<EduUserDevice> list = remoteUserDeviceService.listUserDevice(userFace.getUserId(),
                                        userFace.getUserType(), device.getId(), dto.getGroupId()).getData();
                                // 新增
                                if (CollUtil.isEmpty(list)) {
                                    // 厂商关联信息
                                    EduUserDeviceFactory userDeviceFactory = new EduUserDeviceFactory();
                                    userDeviceFactory.setUserId(userFace.getUserId());
                                    userDeviceFactory.setUserType(userFace.getUserType());
                                    userDeviceFactory.setTypeName(DeviceType.HAIQING.getValue());
                                    userDeviceFactory
                                            .setTypeFlag(userFace.getUserId().toString() + userFace.getUserType().toString());
                                    userDeviceFactoryList.add(userDeviceFactory);
                                    // 新增人脸的信息处理
                                    addUserFaces.set(oneUserFace);
                                    addMap.put(userFace.getUserId().toString() + userFace.getUserType().toString(), userFace);
                                    addKey.add(userFace.getUserId().toString() + userFace.getUserType().toString());
                                } else {
                                    // 编辑
                                    editUserFaces.set(oneUserFace);
                                    editMap.put(userFace.getUserId().toString() + userFace.getUserType().toString(), userFace);
                                    editKey.add(userFace.getUserId().toString() + userFace.getUserType().toString());
                                }
                                // 处理人脸策略信息
//                                if (hasStrategyFlag) {
//                                    // key存组id，value存1
//                                    redisService.hset(CachePrefixConstant.USER_ATTENDANCE_STRATEGY
//                                            + device.getSn() + "-" + customId, String.valueOf(dto.getGroupId()), 1);
//                                }
                            }
                            if (CollUtil.isNotEmpty(addUserFaces)) {
                                addParams.set("info", addUserFaces);
                                addParams.set("PersonNum", addUserFaces.size());
                                addParams.set("messageId", StrUtil.uuid().replace("-", ""));
                            }
                            if (CollUtil.isNotEmpty(editUserFaces)) {
                                editParams.set("info", editUserFaces);
                                editParams.set("PersonNum", editUserFaces.size());
                                editParams.set("messageId", StrUtil.uuid().replace("-", ""));
                            }
                            // 获取编辑锁
                            redisService.falseLock(LockPrefixConstant.HQ_DEVICE_USER_EDIT_LOCK + device.getSn());
                            if (CollUtil.isNotEmpty(addUserFaces)) {
                                // 发送下发人脸消息
                                log.info("发送下发新增人脸消息=================sn: {}, params: {}", device.getSn(), addParams);
                                Boolean flag = mqttUtil.publish(addParams.toString(), "mqtt/face/" + device.getSn());
                                if (!flag) {
                                    // 新增未下发成功
                                    redisService.hdel(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn(),
                                            addKey.toArray(new String[0]));
                                    // 添加错误数据
                                    dealErrorMessage(dto.getGroupId(), device.getSn(), addMap);
                                } else {
                                    // 保存海清人脸新增信息缓存
                                    redisService.hmset(CachePrefixConstant.HQ_BATCH_ADD + device.getSn(), addMap);
                                }
                            }
                            if (CollUtil.isNotEmpty(editUserFaces)) {
                                log.info("发送下发编辑人脸消息=================sn: {}, params: {}", device.getSn(), editParams);
                                Boolean flag = mqttUtil.publish(editParams.toString(), "mqtt/face/" + device.getSn());
                                if (!flag) {
                                    // 编辑未下发成功
                                    redisService.hdel(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn(),
                                            editKey.toArray(new String[0]));
                                    // 添加错误数据
                                    dealErrorMessage(dto.getGroupId(), device.getSn(), editMap);
                                } else {
                                    // 保存海清人脸编辑信息缓存
                                    redisService.hmset(CachePrefixConstant.HQ_BATCH_EDIT + device.getSn(), editMap);
                                }
                            }
                            // 判断是否还有下发人脸数据数据
                            if (!redisService.hasKey(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn())) {
                                // 释放批量编辑锁
                                redisService.falseUnLock(LockPrefixConstant.HQ_DEVICE_USER_EDIT_LOCK + device.getSn());
                            }
                            // 等待设备消息释放锁
                            while (redisService.hasKey(LockPrefixConstant.HQ_DEVICE_USER_EDIT_LOCK + device.getSn())) {
                                try {
                                    ThreadUtil.sleep(1000);
                                } catch (Exception e) {
                                    log.error("阻塞下发人脸失败", e);
                                }
                            }
                        }
                    }
                    log.info("下发结束");
                    log.info("--------------------------");
                    if (CollUtil.isNotEmpty(userDeviceFactoryList)) {
                        // 只有添加人脸需要保存厂商唯一标识
                        remoteUserDeviceFactoryService.batchSaveUserDeviceFactory(userDeviceFactoryList);
                    }
                });
            }
        }
    }

    /**
     * 处理人脸下发错误数据
     *
     * @param faceGroupId 人脸组id
     * @param deviceSn    设备编号
     * @param faceList    人脸信息集合
     * @author ZYJ
     * @date 2023/10/11 15:09
     */
    private void dealErrorMessage(Long faceGroupId, String deviceSn, Map<Object, Object> faceList) {
        // 添加失败信息
        redisService.incr(CachePrefixConstant.ISSUE_FACE_FINISH + faceGroupId, (long) faceList.size());
        redisService.incr(CachePrefixConstant.ISSUE_FACE_ERROR + faceGroupId, (long) faceList.size());
        // 设置当前设备错误信息
        redisService.lSet(CachePrefixConstant.ISSUE_FACE_ERROR_MSG + faceGroupId + StringConstant.DOT + deviceSn,
                faceList.values());
        // 修改人脸状态
        if (CollUtil.isNotEmpty(faceList)) {
            // 失败的用户  修改其下发状态  删除下发记录。EduUserFace转EduFaceGroupUser
            UpdateFaceGroupUserStatusDTO dto = new UpdateFaceGroupUserStatusDTO();
            dto.setUserList(faceList.values().stream().map(value -> {
                EduUserFace userFace = (EduUserFace) value;
                EduFaceGroupUser user = new EduFaceGroupUser();
                user.setUserId(userFace.getUserId());
                user.setUserType(userFace.getUserType());
                user.setFaceGroupId(userFace.getFaceGroupUserId());
                return user;
            }).collect(Collectors.toList()));
            dto.setStatus(EduFaceGroupUser.ISSUE_FAIL);
            remoteUserFaceService.updateFaceGroupUserStatus(dto);
        }
    }

    @Override
    public void removeFace(RemoveFaceDTO dto) {
        log.info("移除的人脸数据集合: {}", dto);
        List<EduAttendanceDevice> deviceList = listByIds(dto.getDeviceList());
        int size = 200;
        String deviceType = deviceList.get(0).getDeviceType();
        List<String> deviceFactoryUserIds = dto.getDeviceFactoryUserId();
        if (DeviceType.HAIQING.getValue().equals(deviceType)) {
            for (EduAttendanceDevice device : deviceList) {
                FACE_POOL.execute(() -> {
                    JSONObject params = new JSONObject();
                    params.set("operator", HaiqingConstant.DELETE_PERSONS_BATCH);
                    params.set("DataBegin", "BeginFlag");
                    params.set("DataEnd", "EndFlag");
                    // 每次最多删除200条
                    for (int i = 0; i < deviceFactoryUserIds.size(); i += size) {
                        List<String> tempIds;
                        if (i + size <= deviceFactoryUserIds.size()) {
                            tempIds = deviceFactoryUserIds.subList(i, i + size);
                        } else {
                            tempIds = deviceFactoryUserIds.subList(i, deviceFactoryUserIds.size());
                        }
                        params.set("info", JSONUtil.createObj()
                                .set("customId", JSONUtil.parseArray(tempIds)));
                        params.set("PersonNum", tempIds.size());
                        params.set("messageId", StrUtil.uuid().replace("-", ""));
                        log.info("删除的人脸数据: {}", params);
                        // 获取该台设备的删除锁
//                        redisService.falseLock(LockPrefixConstant.HQ_DEVICE_USER_DELETE_LOCK + device.getSn());
                        // 发送删除人脸消息
                        mqttUtil.publish(params.toString(), "mqtt/face/" + device.getSn());
                        // 等待设备消息释放锁
//                        while (redisService.hasKey(LockPrefixConstant.HQ_DEVICE_USER_DELETE_LOCK + device.getSn())) {
//                            try {
//                                ThreadUtil.sleep(1000);
//                            } catch (Exception e) {
//                                log.error("阻塞删除人脸失败", e);
//                            }
//                        }
                    }
                });
            }
        }
    }

    @Override
    public void updateDeviceStatus() {
        Set<String> aliveDeviceSnKeys = redisService.keys(CachePrefixConstant.ALIVE_DEVICE);
        Set<String> aliveDeviceSn = CollUtil.isNotEmpty(aliveDeviceSnKeys) ?
                aliveDeviceSnKeys.stream().map(x -> x.substring(CachePrefixConstant.ALIVE_DEVICE.length()))
                        .collect(Collectors.toSet()) : new HashSet<String>() {{
            add("000");
        }};
        log.info("alive device list sn:[{}]", aliveDeviceSn);
        // 系统内在线 此刻不在线的设备
        List<EduAttendanceDevice> noticeDeice = list(Wrappers.<EduAttendanceDevice>lambdaQuery()
                .eq(EduAttendanceDevice::getState, 0)
                .notIn(EduAttendanceDevice::getSn, aliveDeviceSn));
        // 系统内不在线  此刻上线的设备
        //noticeDeice.addAll(list(Wrappers.<EduAttendanceDevice>lambdaQuery()
        //        .eq(EduAttendanceDevice::getState, 1)
        //        .in(EduAttendanceDevice::getSn, aliveDeviceSn)));
        if (CollUtil.isNotEmpty(noticeDeice)) {
            EduDeptWxPublic sysWxPublic = commonService.getWxPublic(1L);
            String accessToken = wxPublicUtil.getAccessToken(sysWxPublic.getAppId(), sysWxPublic.getSecret());
            // 本地缓存加速
            Map<Long, List<EduAgentWxPublicReceiver>> receiversCache = new HashMap<>();
            for (EduAttendanceDevice device : noticeDeice) {
                Long schoolId = device.getSchoolId();
                Dept school = commonService.getDept(device.getSchoolId());
                List<EduAgentWxPublicReceiver> receiverList = receiversCache.containsKey(schoolId)
                        ? receiversCache.get(schoolId)
                        : remoteAgentService.listReceiverBySchool(schoolId).getData();
                receiversCache.put(schoolId, receiverList);
                // 如果有通知接收者
                if (CollUtil.isNotEmpty(receiverList)) {
                    for (EduAgentWxPublicReceiver receiver : receiverList) {
                        JSONObject msgData = new JSONObject() {{
                            set("first", new JSONObject() {{
                                set("value", school.getDeptName() + "设备掉线通知");
                                set("color", "#173177");
                            }});
                            set("keyword1", new JSONObject() {{
                                set("value", "人脸面板机-" + device.getName()
                                        + (device.getZoneScope() == 0 ? "进门" : "出门")
                                        + "【" + device.getSn() + "】");
                                set("color", "#173177");
                            }});
                            set("keyword2", new JSONObject() {{
                                set("value", LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy-MM-dd " +
                                        "HH:mm"));
                                set("color", "#173177");
                            }});
                            set("remark", new JSONObject() {{
                                set("value", "请尽快登录后台进行检查！");
                                set("color", "#173177");
                            }});
                        }};
                        wxPublicUtil.publishTemplateMsg(accessToken, receiver.getWxPublicOpenId(),
                                WxPublicUtil.DEVICE_OFFLINE_TEMPLATE_ID, null, msgData);
                    }
                }
            }
        }
        if (CollUtil.isNotEmpty(aliveDeviceSn)) {
            baseMapper.updateDevice(0, DeviceType.HAIQING.getValue(), aliveDeviceSn);
        }
        baseMapper.updateDevice(1, DeviceType.HAIQING.getValue(), aliveDeviceSn);
    }

    private String spliceOpenDoor() {

        //拼接json
        HashMap<String, Object> map = new HashMap<>();
        map.put("messageId", UUID.randomUUID().toString());
        map.put("operator", "Unlock");
        HashMap<String, Object> info = new HashMap<>();
        info.put("openDoor", 1);
        map.put("info", info);

        return JSON.toJSONString(map);
    }

    private String spliceTopic(String deviceId) {
        return "mqtt/face/" + deviceId;

    }

    private String spliceSetSysTime() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("messageId", UUID.randomUUID().toString());
        map.put("operator", "SetSysTime");
        String date = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.UTC_SIMPLE_PATTERN);
        map.put("info", new HashMap<String, String>() {{
            put("SysTime", date);
        }});
        return JSON.toJSONString(map);
    }

    /**
     * 考勤设备数量统计
     *
     * @return com.yida.data.common.core.common.ResultBean<com.yida.data.attendance.vo.DeviceNumberVO>
     * @author ZYJ
     * @date 2021/10/8 17:14
     */
    @Override
    public ResultBean<DeviceNumberVO> findDeviceNumber() throws NoSuchMethodException {
        // 需要查询的学校id集合
        List<Long> schoolIds = new ArrayList<>();
        CurrentUser currentUser = FebsUtil.getCurrentUser();
        // 获取登陆用户角色类型
        List<RoleEnum> roleList = new ArrayList<>();
        if (StringUtils.isNotBlank(Objects.requireNonNull(currentUser).getRolePerms())) {
            for (String rolePerm : Objects.requireNonNull(currentUser).getRolePerms().split(StringConstant.COMMA)) {
                // 获取登陆角色类型
                RoleEnum roleEnum = EnumUtils.valueOf(RoleEnum.class, rolePerm,
                        RoleEnum.class.getMethod("getValue"));
                if (Objects.nonNull(roleEnum)) {
                    roleList.add(roleEnum);
                }
            }
        }

        if (CollectionUtils.isNotEmpty(roleList)) {
            // 排序
            RoleEnum roleEnum = roleList.stream().sorted(Comparator.comparing(RoleEnum::getOrder))
                    .collect(Collectors.toList()).get(0);
            // 代理商相关账号
            if (RoleEnum.ROLE_AGENT_ADMIN.equals(roleEnum) || RoleEnum.ROLE_AGENT_NORMAL.equals(roleEnum)) {
                List<Dept> deptList = remoteDeptService.getSchoolByArea(null, null).getData();
                if (CollUtil.isNotEmpty(deptList)) {
                    schoolIds = deptList.stream().map(Dept::getDeptId).collect(Collectors.toList());
                }
            } else if (RoleEnum.ROLE_SCHOOL_PRINCIPAL.equals(roleEnum)) {
                // 学校负责人账号
                schoolIds.add(currentUser.getDeptId());
            } else {
                return ResultBean.buildSuccess(
                        DeviceNumberVO.builder()
                                .totalNumber(0L)
                                .onlineNumber(0L)
                                .offlineNumber(0L)
                                .build());
            }
        }
        // 查询不同状态考勤设备数量
        Long offlineNumber = this.baseMapper.findDeviceNumber(schoolIds, DeviceStateEnum.OFFLINE_STATUS.getStatus());
        Long onlineNumber = this.baseMapper.findDeviceNumber(schoolIds, DeviceStateEnum.ONLINE_STATUS.getStatus());
        return ResultBean.buildSuccess(
                DeviceNumberVO.builder()
                        .totalNumber(offlineNumber + onlineNumber)
                        .onlineNumber(onlineNumber)
                        .offlineNumber(offlineNumber)
                        .build()
        );
    }

    @Override
    public void timingAllDevice() {
        Set<String> aliveDeviceSnKeys = redisService.keys(CachePrefixConstant.ALIVE_DEVICE);
        List<String> aliveDeviceSn = CollUtil.isNotEmpty(aliveDeviceSnKeys)
                ? aliveDeviceSnKeys.stream().map(x -> x.substring(CachePrefixConstant.ALIVE_DEVICE.length()))
                .collect(Collectors.toList()) : new ArrayList<>();
        if (CollUtil.isNotEmpty(aliveDeviceSn)) {
            setAllTime(aliveDeviceSn);
        }
    }

    @Override
    public OnlineCountVO getOnlineDistrib(List<Long> schoolIds) {
        OnlineCountVO vo = new OnlineCountVO();
        if (CollUtil.isNotEmpty(schoolIds)) {
            List<EduAttendanceDevice> cardList = list(
                    Wrappers.<EduAttendanceDevice>lambdaQuery().in(EduAttendanceDevice::getSchoolId, schoolIds)
                            .select(EduAttendanceDevice::getState));
            Map<Integer, List<EduAttendanceDevice>> onlineGroup = cardList.stream()
                    .collect(Collectors.groupingBy(EduAttendanceDevice::getState));
            vo.setOnlineNum(onlineGroup.getOrDefault(0, Collections.emptyList()).size());
            vo.setOfflineNum(onlineGroup.getOrDefault(1, Collections.emptyList()).size());
        }
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delDevice(List<Long> ids) {
        // 删除设备信息
        removeByIds(ids);
        // 移除规则管理设备
        attendanceRuleDeviceService.remove(Wrappers.lambdaQuery(new AttendanceRuleDevice())
                .in(AttendanceRuleDevice::getDeviceId, ids));
        // TODO: 2023/10/23 后续移除和设备相关信息
    }
}
