package com.yuchen.saas.park.service.fault.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.thinkunion.park.service.common.cache.CacheNames;
import com.yuchen.saas.park.api.entity.fault.*;
import com.yuchen.saas.park.api.entity.park.PmgBatDeviceData;
import com.yuchen.saas.park.api.entity.park.PmgBatParkInfo;
import com.yuchen.saas.park.api.vo.fault.EquipmentPictureRuleVo;
import com.yuchen.saas.park.api.vo.fault.EquipmentStatusVo;
import com.yuchen.saas.park.service.fault.mapper.EquipmentBreakdownAnalyseHistoryMapper;
import com.yuchen.saas.park.service.fault.mapper.EquipmentPictureRuleMapper;
import com.yuchen.saas.park.service.fault.service.EquipmentBreakdownTypeService;
import com.yuchen.saas.park.service.service.ParkDeviceDataService;
import com.yuchen.saas.park.service.service.PmgBatParkInfoService;
import com.yuchen.saas.park.service.fault.service.EquipmentPictureAnalyseService;
import com.yuchen.saas.park.service.fault.service.EquipmentPictureRuleService;
import com.yuchen.saas.park.service.fault.service.EquipmentStatusService;
import org.nest.springwrap.core.mp.base.BaseServiceImpl;
import org.nest.springwrap.core.mp.support.Condition;
import org.nest.springwrap.core.redis.cache.NestRedis;
import org.nest.springwrap.core.tool.api.R;
import org.nest.springwrap.core.tool.utils.BeanUtil;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @Author: 肖润飞
 * @CreateTime: 2022/10/18 15:46
 * @Description: 设备画像规则
 * @Version: 1.0
 */
@Service
public class EquipmentPictureRuleServiceImpl extends BaseServiceImpl<EquipmentPictureRuleMapper, EquipmentPictureRule> implements EquipmentPictureRuleService {

    @Resource
    private NestRedis nestRedis;

    @Resource
    private EquipmentBreakdownTypeService breakdownTypeService;

    @Resource
    @Lazy
    private EquipmentPictureAnalyseService analyseService;

    @Resource
    private ParkDeviceDataService parkDeviceDataService;

    @Resource
    @Lazy
    private EquipmentStatusService statusService;

    @Resource
    private PmgBatParkInfoService pmgBatParkInfoService;

    @Resource
    private EquipmentBreakdownAnalyseHistoryMapper analyseHistoryMapper;

    /**
     * 检测该异常状态是否触发设备画像分析
     *
     * @param equipmentStatus 设备异常状态信息
     * @return
     */
    @Override
    public EquipmentPictureRule checkEquipmentPictureRule(EquipmentStatus equipmentStatus) {

        // 1.根据breakdownCode获得pictureCode
        String pictureCode = this.getPictureCode(equipmentStatus.getBreakdownCode());
        if (StrUtil.isBlank(pictureCode)) {
            return null;
        }
        // 2.根据pictureCode获得画像规则
        EquipmentPictureRule devicePictureRule = this.getDevicePictureRule(pictureCode);
        // 3.画像规则不为空,表示该故障属于画像规则定义中的故障
        if (devicePictureRule != null) {
            return devicePictureRule;
        }
        return null;
    }


    /**
     * 根据设备异常进行,指定规则统计
     *
     * @param pictureRule     指定画像规则
     * @param equipmentStatus 设备异常信息
     */
    @Override
    public EquipmentPictureAnalyse pictureCount(EquipmentPictureRule pictureRule, EquipmentStatus equipmentStatus, EquipmentBreakdownHistory history) {
        if (pictureRule == null) {
            return null;
        }
        String pictureCode = pictureRule.getPictureCode();
        if (StrUtil.isBlank(pictureCode)) {
            return null;
        }
        switch (pictureCode) {
            case "often_breakdown": //频繁故障
            case "often_off-online": //频繁离线
                return this.oftenBreakdown(pictureRule, equipmentStatus, history);
            case "low_battery": //低电量
                return this.lowBattery(pictureRule, equipmentStatus, history);
            case "often_poor_signal": //频繁信号弱
                return this.oftenPoorSignal(pictureRule, equipmentStatus, history);
        }
        return null;
    }

    /**
     * 批量更改显示的rule
     *
     * @param rule 需要显示的rule
     * @return
     */
    @Override
    public R updateBatchShow(List<EquipmentPictureRule> rule) {
        StringBuilder sb = new StringBuilder();
        rule.forEach(r -> {
            sb.append(r.getId()).append(",");
        });
        String ids = sb.toString();
        // 查询全部的
        List<EquipmentPictureRule> list = this.list();
        for (EquipmentPictureRule pictureRule : list) {
            if (ids.contains(pictureRule.getId().toString())) {
                pictureRule.setIsShow(1);
            } else {
                pictureRule.setIsShow(0);
            }
        }
        this.updateBatchById(list);
        return R.status(true);
    }

    @Override
    public R updateRule(EquipmentPictureRule rule) {
        if (rule.getId() == null) {
            return R.error("Id不能为空");
        }
        EquipmentPictureRule oldRule = this.getById(rule.getId());
        if (oldRule == null) {
            return R.error("需要更新的分析规则不存在");
        }
        String pictureCode = oldRule.getPictureCode();
        switch (pictureCode) {
            case "often_breakdown": //频繁故障
            case "often_off-online": //频繁离线
            case "often_poor_signal": //频繁信号弱
                Integer cycleTime = rule.getCycleTime();
                if (cycleTime < 1) {
                    rule.setCycleTime(1);
                }
                if (cycleTime > 60) {
                    rule.setCycleTime(60);
                }
                int pictureNumber = rule.getPictureNumber();
                if (pictureNumber < 1) {
                    rule.setPictureNumber(1);
                }
                if (pictureNumber > 99) {
                    rule.setPictureNumber(99);
                }
                break;
            case "low_battery": //低电量
                Integer pictureValue = rule.getPictureValue();
                if (pictureValue < 0) {
                    rule.setPictureValue(0);
                }
                if (pictureValue > 100) {
                    rule.setPictureValue(100);
                }
                break;
            default:
                return R.error("画像规则:pictureCode不存在");
        }
        // 更新规则数值
        this.updateById(rule);
        // 更新画像规则规则缓存
        nestRedis.hSet(CacheNames.EQ_PICTURE_RULE, pictureCode, rule);
        return R.data(true);
    }


    /**
     * 列表统计vo
     *
     * @return
     */
    @Override
    public R listVo() {
        List<EquipmentPictureRule> list = this.list();
        List<EquipmentPictureRuleVo> resultList = new ArrayList<>(list.size());
        LambdaQueryWrapper<EquipmentPictureAnalyse> query = Wrappers.lambdaQuery();
        list.forEach(rule -> {
            EquipmentPictureRuleVo vo = new EquipmentPictureRuleVo();
            BeanUtil.copyProperties(rule, vo);
            query.clear();
            resultList.add(vo);
            // 获得所有的园区
            List<PmgBatParkInfo> allPark = pmgBatParkInfoService.getAllPark();
            AtomicLong total = new AtomicLong();
            allPark.forEach(park -> {
                EquipmentStatusVo statusVo = new EquipmentStatusVo();
                statusVo.setParkId(park.getId());
                statusVo.setStatus(1);
                statusVo.setPictureCode(rule.getPictureCode());
                R result = statusService.pageStatus(new Page(), statusVo);
                IPage<EquipmentStatusVo> data = (IPage<EquipmentStatusVo>) result.getData();
                total.addAndGet(data.getTotal());
            });
            vo.setTotalNumber(total.longValue());
        });
        return R.data(resultList);
    }

    /**
     * 频繁信号弱画像统计:  近30天内, 信号弱次数占总上报次数>=80%时，判定为频繁信号弱
     *
     * @param pictureRule
     * @param equipmentStatus
     * @param history
     * @return
     */
    private EquipmentPictureAnalyse oftenPoorSignal(EquipmentPictureRule pictureRule, EquipmentStatus equipmentStatus, EquipmentBreakdownHistory history) {
        // 当前设备查询发送的信号信息次数
        long max = DateUtil.current();//当前时间戳
        //当前时间
        Date date = DateUtil.date(max);
        Integer cycleTime = pictureRule.getCycleTime();//判定周期
        //当前时间前面 30天
        Date dateTime = DateUtil.offsetDay(date, -cycleTime);
        LambdaQueryWrapper<PmgBatDeviceData> query = Wrappers.lambdaQuery();
        query.eq(PmgBatDeviceData::getProductKey, equipmentStatus.getProductKey())
                .eq(PmgBatDeviceData::getDeviceName, equipmentStatus.getDeviceName())
                .between(PmgBatDeviceData::getDeviceTime, dateTime, date)
                .eq(PmgBatDeviceData::getIdentify, "signalStrength");
        // 获得全部的信号统计次数
        int total = parkDeviceDataService.count(query);
        if (total < 10) {
            //产生的信号总数小于10,不进行信号强弱画像分析
            //total = 10;
            return null;
        }
        // 查询信号弱总数
        // 1.获得当前时间的时间戳
        // 当前时间的前30天的时间戳
        long min = max - (60 * 60 * 24) * cycleTime;
        String zSetKey = new StringBuilder(CacheNames.EQ_PICTURE_CODE)
                .append(":").append(pictureRule.getPictureCode())
                .append(":").append(equipmentStatus.getProductKey())
                .append(":").append(equipmentStatus.getDeviceName())
                .toString();
        //将当前 信号弱信息放入zset中
        nestRedis.getZSetOps().add(zSetKey, history.getId(), max);
        // 获得指定时间段内的信号弱次数
        Long count = nestRedis.getZSetOps().count(zSetKey, min, max);
        // 计算出的信号弱比例
        Double rate = (float) count / total * 100D;
        Integer pictureValue = pictureRule.getPictureNumber();
        //true表示 产生信号弱画像分析 ,信号弱比例大于设定的阈值
        boolean countFlag = rate > pictureValue;
        if (countFlag) {//生成信号弱画像分析
            // 获得指定ids
            Set<Object> historyIdSet = nestRedis.getZSetOps().rangeByScore(zSetKey, min, max);
            // 3.新建画像分析,并保存
            LambdaQueryWrapper<EquipmentPictureAnalyse> analyseQuery = Wrappers.lambdaQuery();
            analyseQuery.eq(EquipmentPictureAnalyse::getPictureCode, pictureRule.getPictureCode())
                    .eq(EquipmentPictureAnalyse::getEquipmentStatusId, equipmentStatus.getId())
                    .between(EquipmentPictureAnalyse::getCreateTime, dateTime, date).last(" limit 1");
            EquipmentPictureAnalyse analyse = analyseService.getOne(analyseQuery);
            if (analyse == null) {
                analyse = new EquipmentPictureAnalyse();
            }
            analyse.setEquipmentStatusId(equipmentStatus.getId());
            analyse.setPictureCode(pictureRule.getPictureCode());
            analyse.setPictureNumber(count.intValue());
            analyse.setPictureName(pictureRule.getPictureName());
            // 3.2保存画像设备数据
            analyseService.saveOrUpdate(analyse);

            for (Object id : historyIdSet) {
                EquipmentBreakdownAnalyseHistory analyseHistory = new EquipmentBreakdownAnalyseHistory();
                analyseHistory.setAnalyseId(analyse.getId());
                analyseHistory.setHistoryId(Long.valueOf(id + ""));
                Integer integer = analyseHistoryMapper.selectCount(Condition.getQueryWrapper(analyseHistory));
                if (integer == null || integer == 0) {
                    analyseHistoryMapper.insert(analyseHistory);
                }
            }

            // 4.保存之后清除zSet中的指定时间段里边的数据
            nestRedis.getZSetOps().removeRangeByScore(zSetKey, 0, min);
            return analyse;
        }
        return null;
    }

    /**
     * 低电量画像统计 当前规则： 电量低于设定值 pictureValue
     *
     * @param pictureRule
     * @param equipmentStatus
     * @param history
     * @return
     */
    private EquipmentPictureAnalyse lowBattery(EquipmentPictureRule pictureRule, EquipmentStatus equipmentStatus, EquipmentBreakdownHistory history) {
        Long pictureValue = Long.valueOf(pictureRule.getPictureValue());
        Float value = Float.valueOf(history.getBreakdownValue() + "");
        if (value < pictureValue) {
            // 3.新建画像分析,并保存
            LambdaQueryWrapper<EquipmentPictureAnalyse> query = Wrappers.lambdaQuery();
            query.eq(EquipmentPictureAnalyse::getPictureCode, pictureRule.getPictureCode())
                    .eq(EquipmentPictureAnalyse::getEquipmentStatusId, equipmentStatus.getId())
                    .last(" limit 1");
            EquipmentPictureAnalyse analyse = analyseService.getOne(query);
            if (analyse == null) {
                analyse = new EquipmentPictureAnalyse();
            }
            analyse.setEquipmentStatusId(equipmentStatus.getId());
            analyse.setPictureCode(pictureRule.getPictureCode());
            analyse.setPictureNumber(analyse.getPictureNumber() + 1);

            analyse.setPictureName(pictureRule.getPictureName());
            // 4.更新画像分析数据
            analyseService.saveOrUpdate(analyse);

            EquipmentBreakdownAnalyseHistory analyseHistory = new EquipmentBreakdownAnalyseHistory();
            analyseHistory.setAnalyseId(analyse.getId());
            analyseHistory.setHistoryId(history.getId());
            Integer integer = analyseHistoryMapper.selectCount(Condition.getQueryWrapper(analyseHistory));
            if (integer == null || integer == 0) {
                // 5.插入画像_历史中间表数据
                analyseHistoryMapper.insert(analyseHistory);
            }
            return analyse;
        }
        return null;
    }


    /**
     * 频繁故障画像统计 当前规则： 近30天内，故障数>=5次时，判定为频繁故障
     *
     * @param equipmentStatus
     */
    private EquipmentPictureAnalyse oftenBreakdown(EquipmentPictureRule pictureRule, EquipmentStatus equipmentStatus, EquipmentBreakdownHistory history) {
        //当前规则： 近30天内，故障数>=5次时，判定为频繁故障.
        // 1.获得当前时间的时间戳
        long max = DateUtil.current();
        Date date = DateUtil.date(max);
        Integer cycleTime = pictureRule.getCycleTime();//判定周期
        //当前时间前面 30天
        Date dateTime = DateUtil.offsetDay(date, -cycleTime);
        // 当前时间的前30天的时间戳
        long min = max - (60 * 60 * 24) * cycleTime;
        String zSetKey = new StringBuilder(CacheNames.EQ_PICTURE_CODE)
                .append(":").append(pictureRule.getPictureCode())
                .append(":").append(equipmentStatus.getProductKey())
                .append(":").append(equipmentStatus.getDeviceName())
                .toString();
        //将当前故障信息放入zset中 以
        nestRedis.getZSetOps().add(zSetKey, history.getId(), max);
        Long count = nestRedis.getZSetOps().count(zSetKey, min, max);
        if (count != null) {
            Long pictureNumber = Long.valueOf(pictureRule.getPictureNumber());
            String pictureStandard = pictureRule.getPictureStandard();
            boolean countFlag = false;
            switch (pictureStandard) {
                case "lt":
                    countFlag = count < pictureNumber;
                    break;
                case "gt":
                    countFlag = count > pictureNumber;
                    break;
                case "eq":
                    countFlag = count == pictureNumber;
                    break;
            }
            // 2.达到画像统计标准,进行画像统计
            if (countFlag) {
                // 获得指定ids
                Set<Object> historyIdSet = nestRedis.getZSetOps().rangeByScore(zSetKey, min, max);
                // 3.新建画像分析,并保存
                // 3.1 先查询该画像分析是不是存在
                LambdaQueryWrapper<EquipmentPictureAnalyse> query = Wrappers.lambdaQuery();
                query.eq(EquipmentPictureAnalyse::getPictureCode, pictureRule.getPictureCode())
                        .eq(EquipmentPictureAnalyse::getEquipmentStatusId, equipmentStatus.getId())
                        .between(EquipmentPictureAnalyse::getCreateTime, dateTime, date).last(" limit 1");
                EquipmentPictureAnalyse analyse = analyseService.getOne(query);
                if (analyse == null) {
                    analyse = new EquipmentPictureAnalyse();
                }
                analyse.setEquipmentStatusId(equipmentStatus.getId());
                analyse.setPictureCode(pictureRule.getPictureCode());
                analyse.setPictureNumber(count.intValue());
                analyse.setPictureName(pictureRule.getPictureName());
                // 3.2保存画像分析数据
                analyseService.saveOrUpdate(analyse);
                for (Object id : historyIdSet) {
                    EquipmentBreakdownAnalyseHistory analyseHistory = new EquipmentBreakdownAnalyseHistory();
                    analyseHistory.setAnalyseId(analyse.getId());
                    analyseHistory.setHistoryId(Long.valueOf(id + ""));
                    Integer integer = analyseHistoryMapper.selectCount(Condition.getQueryWrapper(analyseHistory));
                    if (integer == null || integer == 0) {
                        analyseHistoryMapper.insert(analyseHistory);
                    }
                }
                // 4.保存之后清除zSet中的指定时间段,之前的数据
                nestRedis.getZSetOps().removeRangeByScore(zSetKey, 0, min);
                return analyse;
            }

        }
        return null;
    }

    /**
     * 根据breakdownCode获得pictureCode
     *
     * @param breakdownCode
     * @return
     */
    private String getPictureCode(String breakdownCode) {
        EquipmentBreakdownType pictureType = nestRedis.hGet(CacheNames.EQ_BREAKDOWN_TYPE, breakdownCode);
        if (pictureType != null) {
            return pictureType.getPictureCode();
        }
        LambdaQueryWrapper<EquipmentBreakdownType> query = Wrappers.lambdaQuery();
        query.eq(EquipmentBreakdownType::getBreakdownCode, breakdownCode);
        EquipmentBreakdownType type = breakdownTypeService.getOne(query);
        if (type != null) {
            nestRedis.hSet(CacheNames.EQ_BREAKDOWN_TYPE, breakdownCode, type);
            return type.getPictureCode();
        }
        return null;
    }


    /**
     * 根据画像规则code获得画像规则
     *
     * @param pictureCode
     * @return
     */
    private EquipmentPictureRule getDevicePictureRule(String pictureCode) {
        EquipmentPictureRule pictureRule = nestRedis.hGet(CacheNames.EQ_PICTURE_RULE, pictureCode);
        if (pictureRule != null) {
            return pictureRule;
        }
        LambdaQueryWrapper<EquipmentPictureRule> query = Wrappers.lambdaQuery();
        query.eq(EquipmentPictureRule::getPictureCode, pictureCode);
        EquipmentPictureRule rule = this.getOne(query);
        nestRedis.hSet(CacheNames.EQ_PICTURE_RULE, pictureCode, rule);
        return rule;

    }


}