package com.bz.rmcp.alarm.service.impl;
import java.time.LocalDateTime;

import cn.hutool.core.collection.CollUtil;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.bz.rmcp.alarm.common.Constants;
import com.bz.rmcp.alarm.service.DeviceAlarmService;
import com.bz.rmcp.alarm.service.DeviceMeasuringPointService;
import com.zcxk.core.common.enums.StatusEnum;
import com.zcxk.core.common.util.SnowflakeIdWorker;
import com.zcxk.core.utils.DateUtil;
import com.zcxk.rmcp.api.dto.alarm.*;
import com.zcxk.rmcp.api.dto.product.MeasureDataDto;
import com.zcxk.rmcp.api.enums.DeviceStatusEnum;
import com.zcxk.rmcp.api.vo.MeasuringPointVo;
import com.zcxk.rmcp.api.vo.ProductMeasuringDictVo;
import com.zcxk.rmcp.core.dao.AlarmTypeMapper;
import com.zcxk.rmcp.core.dao.DeviceAlarmMapper;
import com.zcxk.rmcp.core.dao.DeviceAlarmRuleMapper;
import com.zcxk.rmcp.core.dao.DeviceMapper;

import com.zcxk.rmcp.core.entity.Device;
import com.zcxk.rmcp.core.entity.DeviceAlarm;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

/**
 * @author linqingwei
 * @date 2021-08-06 10:00
 */
@Slf4j
@Service
public class DeviceAlarmServiceImpl implements DeviceAlarmService {

    private static EvaluationContext context = new StandardEvaluationContext();
    private static ExpressionParser parser = new SpelExpressionParser();

    @Resource
    private DeviceMapper deviceMapper;
    @Resource
    private AlarmTypeMapper alarmTypeMapper;
    @Resource
    private DeviceAlarmRuleMapper deviceAlarmRuleMapper;
    @Autowired
    private DeviceMeasuringPointService deviceMeasuringPointService;
    @Resource
    private DeviceAlarmMapper deviceAlarmMapper;
    @Autowired
    private SnowflakeIdWorker idWorker;

    @Override
    public void handle(MeasureDataDto measureDataDto) {
        Long deviceId = measureDataDto.getDeviceId();
        Device device = deviceMapper.findById(deviceId);
        // 1,判断生成告警
        List<DeviceAlarm> deviceAlarmList = checkDeviceAlarm(device,measureDataDto);

        // 2,处理告警
        Boolean isAlarm = handleDeviceAlarm(device,deviceAlarmList);

        // 3,更新设备
        updateDevice(device,isAlarm);
    }

    /**
     * @description 判断设备告警
     * @param device
     * @param measureDataDto
     * @return
     * @author linqingwei
     **/
    private List<DeviceAlarm>  checkDeviceAlarm(Device device,MeasureDataDto measureDataDto){

        List<DeviceAlarm> deviceAlarmList = new ArrayList<>();

        // 1,查找产品对应的告警类型，
        AlarmTypePageDto alarmTypeDto = new AlarmTypePageDto();
        alarmTypeDto.setTenantId(device.getTenantId());
        alarmTypeDto.setProductCategroyId(device.getCategoryId());
        alarmTypeDto.setDeviceTypeId(device.getProductId());
        List<AlarmTypeDto> alarmTypeList = alarmTypeMapper.selectList(alarmTypeDto);
        for (AlarmTypeDto alarmType : alarmTypeList) {

            // 2,获取规则对应的测点以及表达式进行报警判断
            boolean isOk = true;
            StringBuilder measuringData = new StringBuilder();
            AlarmRulePageDto rulePageDto = new AlarmRulePageDto();
            rulePageDto.setAlarmTypeId(alarmType.getId());
            List<AlarmRuleDto> ruleList = deviceAlarmRuleMapper.selectList(rulePageDto);
            if(ruleList.isEmpty()){
                break;
            }
            for (AlarmRuleDto rule : ruleList) {
                String measuringCode = rule.getMeasuringCode();

                Object measuringValue = measureDataDto.getMeasureMap().get(measuringCode);
                if(measuringValue == null) {
                    isOk = false;
                    break;
                }
                Boolean isAlarm = checkIsAlarm(rule,measuringValue);
                isOk = isOk && isAlarm;
                if(isOk){
                    List<MeasuringPointVo> pointVoList = deviceMeasuringPointService.getMeasuringPoint(device.getProductId());
                    measuringData.append(getMeasuringData(String.valueOf(measuringValue), measuringCode, pointVoList));
                }
            }
            if (isOk) {
                DeviceAlarm deviceAlarm = new DeviceAlarm();
                deviceAlarm.setId(idWorker.nextId());
                deviceAlarm.setTenantId(device.getTenantId());
                deviceAlarm.setDeviceId(device.getId());
                deviceAlarm.setMeasuringId(0);
                deviceAlarm.setAlarmTypeId(alarmType.getId());
                deviceAlarm.setAlarmTime(DateUtil.asLocalDateTime(measureDataDto.getReceiveDate()));
                deviceAlarm.setLastAlarmTime(DateUtil.asLocalDateTime(measureDataDto.getReceiveDate()));
                deviceAlarm.setAlarmCount(1);
                deviceAlarm.setHandleStatus(0);
                deviceAlarm.setMeasuringData(CharSequenceUtil.sub(measuringData.toString(), 0, -1));
                deviceAlarm.setStatus(StatusEnum.OK.getCode());
                deviceAlarm.setCreateBy(Constants.SYS_FLAG);
                deviceAlarm.setUpdateBy(Constants.SYS_FLAG);
                deviceAlarm.setCreateDate(LocalDateTime.now());
                deviceAlarm.setUpdateDate(LocalDateTime.now());
                deviceAlarm.setAlarmTypeName(alarmType.getName());
                deviceAlarmList.add(deviceAlarm);
            }

        }
        return deviceAlarmList;
    }

    /**
     * @description 更新设备
     * @param device
     * @param isAlarm
     * @return
     * @author linqingwei
     **/
    private void updateDevice(Device device, Boolean isAlarm) {
        Device update = new Device();
        update.setId(device.getId());
        update.setDeviceStatus(Boolean.TRUE.equals(isAlarm)?DeviceStatusEnum.ALARM.getCode():DeviceStatusEnum.NORMAL.getCode());
        update.setLastAlarmDate(Boolean.TRUE.equals(isAlarm)?LocalDateTime.now():null);
        update.setUpdateDate(LocalDateTime.now());
        deviceMapper.updateByAlarm(update);
    }

    /**
     * @description 查看最后一次上报的告警信息
     * @param device
     * @return
     * @author linqingwei
     **/
    protected List<DeviceAlarm> getDeviceLastErrorList(Device device) {
        return deviceAlarmMapper.getLastDeviceErrorList(device.getId());
    }
    /**无最新告警记录,将告警记录直接入库
     * @param rtnList
     * @description
     * @author hym
     * @updateTime 2021/8/20 17:45
     * @return void
     * @throws
     */
   private boolean generateAlarmRecordsDirectly(List<DeviceAlarm> rtnList,List<DeviceAlarm> lastErrorList,
                                      List<DeviceAlarm> deviceAlarmList,Device device){
        //不能直接生成告警记录标识
        boolean flag=true;
        if(CollUtil.isEmpty(lastErrorList)) {
            flag=false;
            // 无最新告警记录,将告警记录直接入库
            for(DeviceAlarm  de :deviceAlarmList ) {
                rtnList.add(saveNewDeviceAlarm(device,de));
            }
        }
        return flag;
    }
   private void generateAlarmRecordsAfterComparison(List<DeviceAlarm> rtnList,List<DeviceAlarm> lastErrorList,
                                             List<DeviceAlarm> deviceAlarmList,Device device){
        for(DeviceAlarm de : deviceAlarmList) {
            boolean isSame = false ;
            DeviceAlarm originalError = null ;
            for( DeviceAlarm lde : lastErrorList) {
                if(lde.getAlarmTypeId().equals(de.getAlarmTypeId())) {
                    isSame = true ;
                    originalError = lde;
                    break ;
                }
            }
            if(!isSame) {
                // 告警类型与上次告警类型不同,则直接新增记录
                rtnList.add(saveNewDeviceAlarm(device,de));
            }
            else {
                // 告警类型与上次告警类型相同
                if(device.getLastAlarmDate()!=null){
                    //上次有告警则更新
                    rtnList.add(updateExistDeviceAlarm(de, originalError));
                }else {
                    //上次无告警则新增
                    rtnList.add(saveNewDeviceAlarm(device,de));
                }

            }
        }
    }
    /**
     * @description 处理告警
     * @param device
     * @param deviceAlarmList
     * @return
     * @author linqingwei
     **/
    private Boolean handleDeviceAlarm(Device device,List<DeviceAlarm> deviceAlarmList){
        log.info("begin handleDeviceAlram deviceAlarmList : {}",JSON.toJSONString(deviceAlarmList));
        List<DeviceAlarm> rtnList = new ArrayList<>();
        // 1,查询最新告警记录
        List<DeviceAlarm> lastErrorList = getDeviceLastErrorList(device);

            if(CollUtil.isNotEmpty(deviceAlarmList)&&
                    generateAlarmRecordsDirectly(rtnList,lastErrorList,deviceAlarmList,device)) {
                // 2,有最新告警记录,对比最新告警的告警类型是否同新生成告警记录的告警类型
                generateAlarmRecordsAfterComparison(rtnList,lastErrorList,deviceAlarmList,device);
            }

        log.info("end handleDeviceAlarm deviceAlarmList : {}",JSON.toJSONString(deviceAlarmList));
        return !rtnList.isEmpty();
    }


    /**
     * @description 更新告警
     * @param newDeviceAlarm
     * @param oldDeviceAlarm
     * @return
     * @author linqingwei
     **/
    private DeviceAlarm updateExistDeviceAlarm(DeviceAlarm newDeviceAlarm, DeviceAlarm oldDeviceAlarm) {
        DeviceAlarm param = new DeviceAlarm();
        param.setId(oldDeviceAlarm.getId());
        param.setAlarmCount(oldDeviceAlarm.getAlarmCount()+1);
        param.setLastAlarmTime(newDeviceAlarm.getAlarmTime());
        param.setMeasuringData(newDeviceAlarm.getMeasuringData());
        param.setUpdateDate(LocalDateTime.now());
        deviceAlarmMapper.updateByPrimaryKeySelective(param);
        return newDeviceAlarm;
    }

    private DeviceAlarm saveNewDeviceAlarm(Device device,DeviceAlarm deviceAlarm) {
        deviceAlarmMapper.insertSelective(deviceAlarm);

        String url = "http://10.0.0.62:8081/operation-manager/order/workOrderManage/saveByAlarms";

        log.info("begin create WorkOrder");
        WorkOrderManageByAlarmDto dto = new WorkOrderManageByAlarmDto();
        dto.setId(idWorker.nextId());
        dto.setDeviceName(device.getDeviceNo());
        dto.setTenantId(device.getTenantId());
        dto.setTaskDesc(deviceAlarm.getAlarmTypeName());
        dto.setTaskNo(String.valueOf(idWorker.nextId()));
        dto.setAddress(device.getAddress());
        dto.setOrderTime(LocalDateTimeUtil.format(deviceAlarm.getAlarmTime(), DatePattern.NORM_DATETIME_FORMATTER));
        dto.setCompanyOrgId(device.getCompanyOrgId().toString());
        dto.setDepartmentOrgId(device.getDeptOrgId().toString());
        dto.setOrderStatus(0);

        List<WorkOrderManageByAlarmDto> list = ListUtil.toList(dto);

        String json = JSON.toJSONString(list);

        String result = HttpUtil.post(url,json);
        log.info("end create WorkOrder json = {}, result={}",json,result);

        return deviceAlarm;
    }

    /**
     * @description 判断是否告警
     * @param rule
     * @param measuringValue
     * @return
     * @author linqingwei
     **/
    private Boolean checkIsAlarm(AlarmRuleDto rule,Object measuringValue) {
        if(2 == rule.getMeasuringDataType()) {
            // 连续型
            context.setVariable("value", Double.parseDouble(String.valueOf(measuringValue)));
        }
        else{
            context.setVariable("value", measuringValue);
        }
        return parser.parseExpression(rule.getExpression()).getValue(context, Boolean.class);
    }

    /**
     * @description 获取测点数据
     * @param value
     * @param code
     * @param measuringList
     * @return
     * @author linqingwei
     **/
    private String getMeasuringData(String value, String code, List<MeasuringPointVo> measuringList) {
        if (measuringList != null && !measuringList.isEmpty()) {
            Optional<MeasuringPointVo> optional = measuringList.stream().filter(dto -> StringUtils.equals(code,dto.getMeasuringCode())).findFirst();
            if(optional.isPresent()){
                MeasuringPointVo measuringDto = optional.get();
                Optional<ProductMeasuringDictVo> measuringOptional = measuringDto.getDictList().stream().filter(dto -> StringUtils.equals(value,dto.getDataValue())).findFirst();
                if (measuringOptional.isPresent() && StringUtils.equals("1",measuringDto.getMeasuringDataType())) {
                    return measuringDto.getMeasuringName() + ":"+ measuringOptional.get().getDataName() + ",";
                }
                return measuringDto.getMeasuringName() + ":"+ value + ",";
            }
            return code + ":"+value + ",";

        }
        return null;
    }

}
