package com.yeejoin.amos.fas.business.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.yeejoin.amos.component.rule.RuleTrigger;
import com.yeejoin.amos.fas.business.action.model.ContingencyRo;
import com.yeejoin.amos.fas.business.action.mq.WebMqttComponent;
import com.yeejoin.amos.fas.business.action.mq.WebMqttSubscribe;
import com.yeejoin.amos.fas.business.bo.SafetyExecuteBo;
import com.yeejoin.amos.fas.business.dao.mapper.*;
import com.yeejoin.amos.fas.business.dao.repository.IContingencyOriginalDataDao;
import com.yeejoin.amos.fas.business.dao.repository.IEvaluationModelDao;
import com.yeejoin.amos.fas.business.dao.repository.IPlanOperationRecordDao;
import com.yeejoin.amos.fas.business.dao.repository.IPreplanPictureDao;
import com.yeejoin.amos.fas.business.feign.RemoteSecurityService;
import com.yeejoin.amos.fas.business.param.AlarmParam;
import com.yeejoin.amos.fas.business.service.intfc.IDataRefreshService;
import com.yeejoin.amos.fas.business.service.intfc.IEquipmentHandlerService;
import com.yeejoin.amos.fas.business.service.intfc.IView3dService;
import com.yeejoin.amos.fas.business.util.JexlUtil;
import com.yeejoin.amos.fas.business.util.RpnUtils;
import com.yeejoin.amos.fas.business.vo.EquipmentSpecificForRiskVo;
import com.yeejoin.amos.fas.business.vo.EquipmentSpecificIndexVo;
import com.yeejoin.amos.fas.business.vo.Toke;
import com.yeejoin.amos.fas.business.vo.TopicEntityVo;
import com.yeejoin.amos.fas.client.invoke.RsDataQueue;
import com.yeejoin.amos.fas.common.enums.EquipmentRiskTypeEnum;
import com.yeejoin.amos.fas.core.util.StringUtil;
import com.yeejoin.amos.fas.dao.entity.*;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.typroject.tyboot.core.foundation.context.RequestContext;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author keyong
 * @title: HandlerMqttMessageImpl
 * <pre>
 * @description: 订阅装备信息系统消息处理类
 * </pre>
 * @date 2020/11/10 18:03
 */
@Service("IEquipmentHandlerService")
public class HandlerMqttMessageImpl implements IEquipmentHandlerService {

    private final Logger log = LoggerFactory.getLogger(HandlerMqttMessageImpl.class);

    @Autowired
    private WebMqttSubscribe webMqttSubscribe;

    @Value("${equipManage.fegin.name}")
    private String serverName;

    @Value("${emqx.defaultTopic}")
    private String defaultTopic;

    @Value("${data.type.fireMonitor}")
    private String fireMonitor;

    @Autowired
    private WebMqttComponent webMqttComponent;

    @Autowired
    private RemoteSecurityService remoteSecurityService;

    @Autowired
    private FireEquipPointMapper fireEquipPointMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ImpAndFireEquipMapper impAndFireEquipMapper;

    @Autowired
    private IPreplanPictureDao iPreplanPictureDao;

    @Autowired
    private RuleTrigger ruleTrigger;

    @Autowired
    IContingencyOriginalDataDao iContingencyOriginalDataDao;

    @Autowired
    private IDataRefreshService iDataRefreshService;

    @Autowired
    private IView3dService view3dService;

    @Autowired
    private FmeaEquipmentPointMapper fmeaEquipmentPointMapper;

    @Autowired
    EquipmentSpecificMapper equipmentSpecificMapper;

    @Autowired
    private FmeaPointInputitemMapper fmeaPointInputitemMapper;

    @Autowired
    private IEvaluationModelDao iEvaluationModelDao;

    @Autowired
    private FmeaMapper fmeaMapper;


    @Value("${station.name}")
    private String stationName;

    @Value("${spring.application.name}")
    private String serviceName;
    @Autowired
    private IPlanOperationRecordDao planOperationRecordDao;

    @Value("${autoSys.alarm.nameKeys}")
    private String nameKeys;

    @Override
    public void handlerMqttMessage(String topic, String data) {

        TopicEntityVo topicEntity = JSON.parseObject(data, TopicEntityVo.class);
        log.info("iotCode 性能指标属于： " + topicEntity.getType());
        EquipmentSpecificIndexVo equipmentSpecificIndex = JSONObject.parseObject(topicEntity.getMessage(), EquipmentSpecificIndexVo.class);
        long eqSpecId = equipmentSpecificIndex.getEquipmentSpecificId();
        String nameKey = equipmentSpecificIndex.getNameKey();
        if(topicEntity.getSimulationDate().equals("false") && !indexStateIsChange(equipmentSpecificIndex)){
            log.info("指标值没有发生变化： " + equipmentSpecificIndex.getIotCode()+"-"+equipmentSpecificIndex.getNameKey()+":"+equipmentSpecificIndex.getValue());
            return;
        }

        EquipmentSpecificForRiskVo equipmentSpecific = equipmentSpecificMapper.getOneById(eqSpecId);
        //  三维屏指标状态推送
        String specificIndexType = equipmentSpecificIndex.getType();
        String typeCode = equipmentSpecificIndex.getTypeCode();
        if (nameKey !=null && nameKeys.contains(nameKey)) {
            if (equipmentSpecific != null) {
                equipmentSpecificIndex.setId(equipmentSpecific.getId());
                equipmentSpecificIndex.setName(equipmentSpecific.getName());
                equipmentSpecificIndex.setCode(equipmentSpecific.getCode());
            }
            equipmentSpecificIndex.setType("equip");
            String title = String.format("/%s/%s", serviceName, "data/refresh/indexStatus");
            webMqttComponent.publish(title, JSON.toJSONString(equipmentSpecificIndex));
        }
        Equipment equipment = topicEntity.getEquipment()==null?impAndFireEquipMapper.queryImpEqumtByFireEquipmt(eqSpecId):topicEntity.getEquipment();
        Toke toke = remoteSecurityService.getServerToken();
        AlarmParam deviceData = new AlarmParam();
        deviceData.setMonitor(equipment != null ? equipment.getName() : "");
        deviceData.setId(String.valueOf(equipmentSpecific.getId()));
        deviceData.setCode(equipmentSpecific.getCode());
        //设备告警处理逻辑
        if (EquipmentRiskTypeEnum.HZGJ.getCode().equals(typeCode)) {
            log.info("(报警)Message typeCode is: " + typeCode);
            /**
             * 推送告警数据
             * 影响区域：消防安全=>火灾告警
             */
            String title = String.format("/%s/%s/%s/%s", serviceName, stationName,"data/refresh","fireSafety");
            List<SafetyExecuteBo> dataList = view3dService.getSafetyExecuteListTop5("fire", equipmentSpecific.getOrgCode());
            Map<String,Object> result = new HashMap<>();
            result.put("type","fire");
            result.put("dataList", dataList);
            webMqttComponent.publish(title, JSON.toJSONString(result));

            // 报警触发调用规则服务
            if(EquipmentRiskTypeEnum.HZGJ.getCode().equals(specificIndexType)&&!ObjectUtils.isEmpty(equipment) && !ObjectUtils.isEmpty(equipment.getReservePlan())){
                executeDynamicPlan(deviceData, equipment, equipmentSpecific, toke, null);
            }
        } else if (EquipmentRiskTypeEnum.GZ.getCode().equals(typeCode)) {
            // 设备故障处理逻辑
            log.info("(故障)Message typeCode is: " +typeCode);
            final String stateTrue = "true";
            final String stateFalse = "false";
            String state = equipmentSpecificIndex.getValue();
            if (stateTrue.equals(state)) {
                //故障
                notifyAlarm(equipmentSpecificIndex, 1);
            } else if (stateFalse.equals(state)) {
                //故障恢复
                notifyAlarm(equipmentSpecificIndex, 0);
            }
            long equipId = 0;
            if(StringUtil.isNotEmpty(equipment)) {
                equipId = equipment.getId();
                // 是否关联风险点
                List<FmeaEquipmentPoint> list = fmeaEquipmentPointMapper.listFmeaByEquipIndexIdAndEquipId(equipmentSpecificIndex.getId(), equipId);
                if(list.size() > 0) {
                    // 关联风险点进行rpn，rpni值的修改
                    list.forEach(fmeaEqPoint -> {
                        long fmeaId = fmeaEqPoint.getFmeaId();
                        updateFmeaRpn(fmeaId);
                    });
                } else {
                    // 没有关联风险点，三维页面消息推送
                    fireEquipRuleMessagePush(deviceData, toke);
                }
            }
        } else {
            // 监测数据逻辑
            log.info("(监测)Message type is: " + specificIndexType);
            Map<String, Object> content = new HashMap<>();
            String location = equipmentSpecific.getLocation()+" "+equipmentSpecific.getDescription();
            content.put("id", equipmentSpecific.getId());
            content.put("label", equipmentSpecific.getName()+" "+equipmentSpecificIndex.getEquipmentIndexName() + (ObjectUtils.isEmpty(location)?("【" +location+"】"):""));
            content.put("code", equipmentSpecific.getCode());
            content.put("changeDate", new Date());
            content.put("orgCode", equipmentSpecific.getOrgCode());
            content.put("indexKey", equipmentSpecificIndex.getNameKey());
            content.put("status", equipmentSpecificIndex.getValue());
            String title = String.format("/%s/%s/%s", serviceName, stationName,"data/refresh/monitorData");
            webMqttComponent.publish(title, JSON.toJSONString(view3dService.getEquipStatusList(equipmentSpecific.getOrgCode())));
        }
    }

    private boolean indexStateIsChange(EquipmentSpecificIndexVo equipmentSpecificIndex){
        if(ObjectUtils.isEmpty(equipmentSpecificIndex.getValue())){
            return false;
        }
        String key = equipmentSpecificIndex.getIotCode()+"-"+equipmentSpecificIndex.getNameKey();
        if(redisTemplate.hasKey(key)){
            EquipmentSpecificIndexVo equipmentSpecificIndexVo = JSON.parseObject(redisTemplate.opsForValue().get(key).toString(), EquipmentSpecificIndexVo.class);
            if(equipmentSpecificIndex.getValue().equals(equipmentSpecificIndexVo.getValue())){
                return false;
            }
        }
        redisTemplate.opsForValue().set(key, JSONObject.toJSONString(equipmentSpecificIndex));
        return true;
    }

    /**
     * <pre>
     * @Description: rpn,rpni值修改
     * </pre>
     *
     * @MethodName:
     * @Param: [fmeaId]
     * @Return: void
     * @Throws
     * @Author keyong
     * @Date 2020/11/16 18:26
     */
   public void updateFmeaRpn(long fmeaId) {
       Fmea fmea = fmeaMapper.getById(fmeaId);
       BigDecimal oidValue = new BigDecimal(fmea.getOidValue());
       BigDecimal sidValue = new BigDecimal(fmea.getSidValue());
       BigDecimal didValue = new BigDecimal(fmea.getDidValue());
       // 计算rpni
       BigDecimal rpni = oidValue.multiply(sidValue).multiply(didValue).setScale(2, BigDecimal.ROUND_HALF_UP);
       BigDecimal rpn;
       if (fmea.getNewEvaluationOid() == null) {
           EvaluationModel evaluationModel = this.getEvaluationModel(fmeaId);
           if (evaluationModel != null) { // 已经绑定点位或者巡检点项且匹配到风险模型，则更新为计算后的结果
               BigDecimal newOidValue = new BigDecimal(evaluationModel.getCoefficient());
               rpn = newOidValue.multiply(sidValue).multiply(didValue).setScale(2, BigDecimal.ROUND_HALF_UP);
               fmea.setNewEvaluationOid(evaluationModel.getId());
           } else { // 未绑定点位或者巡检点项或者匹配不到风险模型，则更新为rpn与rpni一致
               fmea.setNewEvaluationOid(fmea.getEvaluationOid());
               rpn = rpni;
           }
       } else {
           BigDecimal newOidValue = new BigDecimal(fmea.getNewOidValue());
           rpn = newOidValue.multiply(sidValue).multiply(didValue).setScale(2, BigDecimal.ROUND_HALF_UP);
       }
       fmea.setRpni(rpni);
       fmea.setRpn(rpn);
       // 更新fmea
       fmeaMapper.updateRpn(fmea);
   }



    @Override
    public void subscribeTopic() {
        // 若登录系统则订阅装备数据
        webMqttSubscribe.adapter.removeTopic(defaultTopic);
        String topic = String.format("%s.%s%s", serverName, "equipment", "/#");
        String[] strs =  webMqttSubscribe.adapter.getTopic();
        List<String> list = Stream.of(strs).collect(Collectors.toList());
        if(list.size() > 0) {
            list.forEach(x -> {
                if(!(x.equals(topic))) {
                    webMqttSubscribe.adapter.addTopic(topic);
                }
            });
        } else {
            webMqttSubscribe.adapter.addTopic(topic);
        }

    }

    /**
      * <pre>
      * @Description: 设备消息规则推送
      * </pre>
      *
      * @MethodName:
      * @Param: [deviceData, toke]
      * @Return: void
      * @Throws
      * @Author keyong
      * @Date 2020/11/16 18:26
      */
    @Async
    void fireEquipRuleMessagePush(AlarmParam deviceData, Toke toke) {
        RiskSourceServiceImpl.ruleMsgSend(deviceData, toke, log, ruleTrigger);
    }

    /**
      * <pre>
      * @Description: 故障通知
      * </pre>
      *
      * @MethodName:
      * @Param: [fireEquipmentPoint, param]
      * @Return: void
      * @Throws
      * @Author keyong
      * @Date 2020/11/16 15:25
      */
    private void notifyAlarm(EquipmentSpecificIndexVo equipmentSpecificIndex, int state) {
        List<FmeaEquipmentPoint> fmeaEquipmentPoints = fmeaEquipmentPointMapper.listByEquipmentPointId(equipmentSpecificIndex.getEquipmentIndexId());
        Set<Long> fmeaIds = Sets.newHashSet();
        List<Long> ids = Lists.newArrayList();
        fmeaEquipmentPoints.forEach(fmeaEquipmentPoint -> {
            ids.add(fmeaEquipmentPoint.getId());
            fmeaIds.add(fmeaEquipmentPoint.getFmeaId());
        });
        if (ids.size() > 0) {
            fmeaEquipmentPointMapper.updateStateByIds(state, ids);
        }
        final Integer st = state;
        String monitor = equipmentSpecificMapper.findEquipNameById(equipmentSpecificIndex.getEquipmentSpecificId());
        RsDataQueue rsDataQueue = RsDataQueue.getInstance();
        fmeaIds.forEach(fmeaId -> rsDataQueue.addEquipmentMessage(fmeaId, monitor, st));
    }

    private EvaluationModel getEvaluationModel(Long fmeaId) {
        List<FmeaEquipmentPoint> equipmentPoints = fmeaEquipmentPointMapper.listFmeaByFmeaId(fmeaId);
        List<FmeaPointInputitem> pointInputitems = fmeaPointInputitemMapper.listFmeaByFmeaId(fmeaId);
        Double maxRate = RpnUtils.getMaxRate(equipmentPoints, pointInputitems);
        if (maxRate != null) {
            List<EvaluationModel> oModels = iEvaluationModelDao.findAllByType("O");
            EvaluationModel oEvaluationModel = getBetweenModel(maxRate, oModels);
            return oEvaluationModel;
        }
        return null;
    }

    private EvaluationModel getBetweenModel(Double maxRate, List<EvaluationModel> oModels) {
        for (EvaluationModel model : oModels) {
            String str = model.getDescribe().replaceAll("O", maxRate.toString());
            if (JexlUtil.convertToCode(str)) {
                return model;
            }
        }
        return null;
    }

    /**
      * <pre>
      * @Description: 火警执行动态预案
      * </pre>
      *
      * @MethodName:
      * @Param:
      * @Return: null
      * @Throws
      * @Author keyong
      * @Date 2020/11/11 20:46
      */
//    @Async
    @Override
    public String executeDynamicPlan(AlarmParam deviceData, Equipment equipment, EquipmentSpecificForRiskVo equipmentSpecific, Toke toke,Long recordId) {
        String batchNo = UUID.randomUUID().toString();
        RequestContext.setToken(toke.getToke());
        RequestContext.setProduct(toke.getProduct());
        try {
            alarmContingency(batchNo, equipmentSpecific, equipment,recordId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  batchNo;
    }

    public void alarmContingency(String batchNo, EquipmentSpecificForRiskVo equipmentSpecific, Equipment equipment,Long recordId) throws Exception {
//        Object oldContingencyRo = redisTemplate.opsForValue().get("contingencyRo");
        ContingencyRo contingencyRo = new ContingencyRo();
        contingencyRo.setBatchNo(batchNo);
        contingencyRo.setEquipmentId(String.valueOf(equipment.getId()));
        contingencyRo.setEquipmentName(equipment.getName());
        contingencyRo.setFireEquipmentId(String.valueOf(equipmentSpecific.getId()));
        contingencyRo.setFireEquipmentName(equipmentSpecific.getName());
        contingencyRo.setStep("0");
        contingencyRo.setConfirm("NONE");
        contingencyRo.setFireTruckRoute(equipment.getFireTruckRoute());
        contingencyRo.setRunstep(false);
        contingencyRo.setEquipmentPosition3d(equipment.getPosition3d());
        contingencyRo.setEquipmentCode(equipment.getCode());
        contingencyRo.setEquipmentOrgCode(equipment.getOrgCode());

        //查询重点设备关联视频点位
        Map<String,Object> cameraInfo = impAndFireEquipMapper.queryCamera(String.valueOf(equipment.getId()));
        if (cameraInfo != null && !cameraInfo.isEmpty()) {
            contingencyRo.setCameraCodes(String.valueOf(cameraInfo.get("codes")));
            contingencyRo.setCameraIds(String.valueOf(cameraInfo.get("ids")));
        }

        //查询重点设备关联消防炮
        Map fireMonitorInfo = equipmentSpecificMapper.queryFireMonitor(String.valueOf(equipment.getId()), fireMonitor);
        if (fireMonitorInfo != null && !fireMonitorInfo.isEmpty()) {
            contingencyRo.setFireMonitorCodes(String.valueOf(fireMonitorInfo.get("codes")));
            contingencyRo.setFireMonitorIds(String.valueOf(fireMonitorInfo.get("ids")));
        }

        List<PreplanPicture> pictures = iPreplanPictureDao.findByEquipmentId(Long.valueOf(equipment.getId()));
        if (!CollectionUtils.isEmpty(pictures)) {
            for (PreplanPicture picture : pictures) {
                if (1 == picture.getType()) {
                    contingencyRo.setPicture1(picture.getPicture());
                }
                if (2 == picture.getType()) {
                    contingencyRo.setPicture2(picture.getPicture());
                }
                if (3 == picture.getType()) {
                    contingencyRo.setPicture3(picture.getPicture());
                }
                if (4 == picture.getType()) {
                    contingencyRo.setPicture4(picture.getPicture());
                }
            }
        }

        // 获取遥信指标,暂不处理    code = 设备编码iot_code-指标项name_key
        List<Map> points = fireEquipPointMapper.getPointsByEquipmentIdAndType(equipment.getId(), "SWITCH");//物联属性指标 and 为true或false
        Map<String, Integer> telesignallingMap = new HashMap<>();
        for (Map map : points) {
            telesignallingMap.put(map.get("code") + "", (ObjectUtils.isEmpty(map.get("value")) || "false".equals(map.get("value").toString())) ? 0 : 1);
        }
        contingencyRo.setTelesignallingMap(telesignallingMap);
        // 获取遥测指标
        points = fireEquipPointMapper.getPointsByEquipmentIdAndType(equipment.getId(), "ANALOGUE"); //物联指标 非 true false
        Map<String, Double> telemetryMap = new HashMap<>();
        for (Map map : points) {
            telemetryMap.put(map.get("code") + "", Double.valueOf(ObjectUtils.isEmpty(map.get("value")) ? "0" : map.get("value").toString()));
        }
        contingencyRo.setTelemetryMap(telemetryMap);

        log.info("开始调用规则 参数 contingencyRo{},reservePlan{},equipmentNames"+contingencyRo.toString()+","+equipment.getReservePlan()+","+ ArrayUtils.toArray(equipment.getName()));
        Object result = ruleTrigger.publish(contingencyRo, equipment.getReservePlan(), ArrayUtils.toArray(equipment.getName()));
        log.info("规则调用返回==",result);

        ContingencyOriginalData contingencyOriginalData = new ContingencyOriginalData();
        BeanUtils.copyProperties(contingencyRo, contingencyOriginalData);
        iContingencyOriginalDataDao.save(contingencyOriginalData);
    }
}
