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.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.mapper.EduAttendanceDeviceMapper;
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.enums.DeviceStateEnum;
import com.yida.data.common.core.entity.constant.CachePrefixConstant;
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.telephone.EduTelephoneCard;
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.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.attendance.dto.DevicePage;
import com.yida.data.attendance.dto.IssuedFaceDTO;
import com.yida.data.attendance.dto.RemoveFaceDTO;
import com.yida.data.system.feign.RemoteAgentService;
import com.yida.data.system.feign.RemoteDeptService;
import com.yida.data.user.feign.RemoteUserDeviceFactoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

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;

@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 RemoteDeptService remoteDeptService;
    private final RemoteAgentService remoteAgentService;
    private final RemoteUserDeviceFactoryService remoteUserDeviceFactoryService;
    private final CommonService commonService;
    private final MqttUtil mqttUtil;
    private final RedisService redisService;
    private final WxPublicUtil wxPublicUtil;

    @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
    public EduAttendanceDevice saveDevice(EduAttendanceDevice device) {
        if (device.getId() == null && StrUtil.isNotEmpty(device.getSn()) && DeviceType.HAIQING.getValue()
            .equals(device.getDeviceType())) {
            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());
        return device;
    }

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

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

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

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

    }

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

    @Override
    public void issuedFace(IssuedFaceDTO dto) {
        List<EduUserFace> userFaceList = dto.getUserFaceList();
        List<EduAttendanceDevice> deviceList = listByIds(dto.getDeviceList());
        // 用户与厂商关联信息
        List<EduUserDeviceFactory> userDeviceFactoryList = new ArrayList<>();
        if (DeviceType.HAIQING.getValue().equals(deviceList.get(0).getDeviceType())) {
            // 海清设备
            JSONObject params = new JSONObject();
            params.set("operator", dto.getType() == 0 ? "AddPersons" : "EditPersons");
            params.set("DataBegin", "BeginFlag");
            params.set("DataEnd", "EndFlag");
            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);
            redisService.del(CachePrefixConstant.ISSUE_FACE_ERROR_MSG + dto.getGroupId());
            log.info("错误数据:[{}],groupId:[{}]",
                redisService.lGet(CachePrefixConstant.ISSUE_FACE_ERROR_MSG + dto.getGroupId(), 0L, -1L),
                dto.getGroupId());
            // 将人脸数据缓存到redis中
            Map<Object, Object> userFaceMap = new HashMap<>();
            for (EduUserFace userFace : userFaceList) {
                userFaceMap.put(userFace.getUserId().toString() + userFace.getUserType().toString(), userFace);
                // 厂商关联信息
                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);
            }
            // 遍历向每个设备下发信息
            for (EduAttendanceDevice device : deviceList) {
                //保存每个设备要添加的信息
                redisService.hmset(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn(), userFaceMap);
                // 当前设备下发的人脸组
                redisService.hset(CachePrefixConstant.ISSUE_FACE_GROUP_DEVICE, device.getSn(), dto.getGroupId());
                // 每个设备一个线程
                FACE_POOL.execute(() -> {
                    while (redisService.hasKey(CachePrefixConstant.HQ_EDIT_BATCH + device.getSn())) {
                        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());
                            }
                            JSONArray userFaces = new JSONArray();
                            for (EduUserFace userFace : tempFace) {
                                JSONObject oneUserFace = new JSONObject();
                                oneUserFace.set("name", userFace.getName());
                                oneUserFace.set("customId",
                                    userFace.getUserId().toString() + userFace.getUserType().toString());
                                oneUserFace.set("picURI", userFace.getPicPath());
                                // 永久白名单
                                oneUserFace.set("personType", 0);
                                oneUserFace.set("tempCardType", 0);
                                userFaces.set(oneUserFace);
                            }
                            params.set("info", userFaces);
                            params.set("PersonNum", userFaces.size());
                            params.set("messageId", StrUtil.uuid().replace("-", ""));
                            // 获取编辑锁
                            redisService.falseLock(LockPrefixConstant.HQ_DEVICE_USER_EDIT_LOCK + device.getSn());
                            // 发送消息
                            mqttUtil.pulish(params.toString(), "mqtt/face/" + device.getSn());
                            // 等待设备消息释放锁
                            while (redisService.hasKey(LockPrefixConstant.HQ_DEVICE_USER_EDIT_LOCK + device.getSn())) {
                                try {
                                    Thread.sleep(1000);
                                } catch (Exception e) {
                                    log.error("阻塞下发人脸失败", e);
                                }
                            }
                        }
                    }
                    log.info("下发结束");
                });
            }
        }
        log.info("--------------------------");
        if (dto.getType() == 0) {
            // 只有添加人脸需要保存厂商唯一标识
            remoteUserDeviceFactoryService.batchSaveUserDeviceFactory(userDeviceFactoryList);
        }
    }

    @Override
    public void removeFace(RemoveFaceDTO 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", "DeletePersons");
                    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", new JSONObject() {{
                            set("customId", JSONUtil.parseArray(tempIds));
                        }});
                        params.set("PersonNum", tempIds.size());
                        params.set("messageId", StrUtil.uuid().replace("-", ""));
                        // 获取该台设备的删除锁
                        redisService.falseLock(LockPrefixConstant.HQ_DEVICE_USER_DELETE_LOCK + device.getSn());
                        // 发送消息
                        mqttUtil.pulish(params.toString(), "mqtt/face/" + device.getSn());
                        // 等待设备消息释放锁
                        while (redisService.hasKey(LockPrefixConstant.HQ_DEVICE_USER_DELETE_LOCK + device.getSn())) {
                            try {
                                Thread.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()) : null;
        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;
    }
}
