/*
 * 文 件 名:  GeekOpenService
 * 版    权:  华设设计集团股份有限公司
 * 描    述:  <描述>
 * 修 改 人:  lvwenbin
 * 修改时间:  2025/2/27
 * 跟踪单号:  <跟踪单号>
 * 修改单号:  <修改单号>
 * 修改内容:  <修改内容>
 */
package com.ruoyi.ems.handle;

import com.alibaba.fastjson2.JSONObject;
import com.huashe.common.domain.JsonEntity;
import com.huashe.common.exception.Assert;
import com.huashe.common.exception.BusinessException;
import com.huashe.common.utils.DateUtils;
import com.huashe.common.utils.ThreadUtils;
import com.ruoyi.ems.config.EmsConfig;
import com.ruoyi.ems.core.EmsApiTemplate;
import com.ruoyi.ems.core.MqttTemplate;
import com.ruoyi.ems.core.ObjectCache;
import com.ruoyi.ems.domain.EmsDevice;
import com.ruoyi.ems.domain.EmsObjAbilityCallLog;
import com.ruoyi.ems.domain.EmsObjAttrValue;
import com.ruoyi.ems.domain.MeterDevice;
import com.ruoyi.ems.enums.DevOnlineStatus;
import com.ruoyi.ems.model.AbilityPayload;
import com.ruoyi.ems.model.CallResponse;
import com.ruoyi.ems.model.MqttCacheMsg;
import com.ruoyi.ems.model.QueryDevice;
import com.ruoyi.ems.util.IdUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;

/**
 * GeekOpen 断路器服务层
 * <功能详细描述>
 *
 * @author lvwenbin
 * @version [版本号, 2025/2/27]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Service
public class GeekOpenCbHandler extends BaseMeterDevHandler {
    private static final Logger log = LoggerFactory.getLogger(GeekOpenCbHandler.class);

    @Autowired
    private ObjectCache messageCache;

    @Resource
    @Qualifier("mqttTemplate")
    protected MqttTemplate mqttTemplate;

    // 主题前缀
    private static final String TOPIC_PREFIX = "/device/dlq/";

    // 设备模型代码
    private static final String MODE_CODE = "M_W2_QF_GEEKOPEN";

    @Autowired
    private EmsConfig emsConfig;

    /**
     * 能力执行
     *
     * @param abilityParam 执行参数
     * @return 响应
     */
    @Override
    public CallResponse<Void> call(AbilityPayload abilityParam) {
        CallResponse<Void> callResponse = null;

        JSONObject msgBody = buildSendMsg(abilityParam);
        String messageId = msgBody.getString("messageId");
        String deviceCode = abilityParam.getObjCode();

        // 发送消息到MQTT服务器
        long sendTime = System.currentTimeMillis();
        String topic = TOPIC_PREFIX + deviceCode;
        sendMqttMsg(topic, msgBody.toString(), 2, false);

        // 需要监测回包的流程
        if (StringUtils.startsWith(messageId, "CALL-")) {
            // 写入日志
            EmsObjAbilityCallLog logItem = saveCallLog(abilityParam, msgBody.toString(), sendTime, 1);

            while (true) {
                MqttCacheMsg cacheMsg = messageCache.readDevResponse(messageId);

                if (null != cacheMsg) {
                    String receiveParam = cacheMsg.getPayload();
                    JSONObject receiveObject = JSONObject.parseObject(receiveParam);
                    callResponse = checkResult(msgBody, receiveObject);
                    updateCallLog(logItem, cacheMsg, callResponse.getCode() == 0 ? 0 : 2);

                    break;
                }

                ThreadUtils.sleep(100);

                if (System.currentTimeMillis() - sendTime > 30000) {
                    logItem.setResPayload("响应超时");
                    logItem.setCallStatus(2);
                    objAbilityCallLogService.updateLog(logItem);
                    callResponse = new CallResponse<>(-1, "响应超时!");
                    break;
                }
            }
        }
        else {
            saveCallLog(abilityParam, msgBody.toString(), sendTime, 0);
            callResponse = new CallResponse<>(0, "执行成功!");
        }

        return callResponse;
    }

    @Async("msgHandleExecutor")
    public void msgHandle(String deviceCode, String payload) {
        try {
            EmsDevice device = deviceService.selectByCode(deviceCode);

            if (null != device) {
                JSONObject msgBody = JSONObject.parseObject(payload);
                String messageId = msgBody.getString("messageId");

                // 自动上报数据: 1.更新属性值，2.更新电量计量数据
                if (StringUtils.equals("auto", messageId)) {
                    updateAutoAttr(device, msgBody);
                    saveCallLog(deviceCode, device.getDeviceModel(), "triggerSync", 0, null, payload);
                }
                // 前序调用的响应消息：1.写入消息队列，2.更新属性值
                else if (StringUtils.isNotEmpty(messageId) && StringUtils.startsWith(messageId, "CALL-")) {
                    MqttCacheMsg mqttCacheMsg = new MqttCacheMsg(messageId, deviceCode, new Date(), payload);
                    messageCache.setDevResCache(messageId, mqttCacheMsg);
                    updateBaseAttr(device, msgBody);
                }
                // 设备同步数据（INFO类，协议类）：更新基础属性值
                else {
                    updateBaseAttr(device, msgBody);
                    saveCallLog(deviceCode, device.getDeviceModel(), "triggerSync", 0, null, payload);
                }

                // 设备消息抵达，将数据库离线状态改为在线
                refreshStatus(device, DevOnlineStatus.ONLINE);
                // 设备消息抵达，更新最后消息时间
                updateMsgTime(device, new Date());
            }
            else {
                log.warn("接收消息，设备未注册, deviceCode:{}\nmessageBody:{}", deviceCode, payload);
            }
        }
        catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 获取设备列表
     *
     * @return 设备列表
     */
    @Override
    public List<EmsDevice> getDeviceList() {
        QueryDevice queryDevice = new QueryDevice();
        queryDevice.setDeviceModel(MODE_CODE);
        return deviceService.selectList(queryDevice);
    }

    @Override
    public List<MeterDevice> getMeterDeviceList() {
        return meterDeviceService.selectByModel("M_W2_QF_GEEKOPEN");
    }

    /**
     * 更新基础属性值
     *
     * @param device   设备信息
     * @param jsonBody jsonBody 消息体
     */
    private void updateBaseAttr(EmsDevice device, JSONObject jsonBody) {
        updateAttr(device, jsonBody, "key");
        updateAttr(device, jsonBody, "iccid");
        updateAttr(device, jsonBody, "imei");
        updateAttr(device, jsonBody, "signal");
        updateAttr(device, jsonBody, "version");
        updateAttr(device, jsonBody, "timerEnable");
        updateAttr(device, jsonBody, "timerInterval");
        updateAttr(device, jsonBody, "keyLock");
        updateAttr(device, jsonBody, "onState");
        updateAttr(device, jsonBody, "resetLock");

        // 协议信息
        updateAttr(device, jsonBody, "protocol");
        updateAttr(device, jsonBody, "clientId");
        updateAttr(device, jsonBody, "server");
        updateAttr(device, jsonBody, "port");
        updateAttr(device, jsonBody, "username");
        updateAttr(device, jsonBody, "publish");
        updateAttr(device, jsonBody, "subcribe");
    }

    /**
     * 更新自动上报属性值
     *
     * @param device   设备信息
     * @param jsonBody jsonBody 消息体
     */
    private void updateAutoAttr(EmsDevice device, JSONObject jsonBody) {
        updateAttr(device, jsonBody, "voltage");
        updateAttr(device, jsonBody, "current");
        updateAttr(device, jsonBody, "power");
        updateAttr(device, jsonBody, "energy", NEW_HOUR_READING);
    }

    public boolean updateAttr(EmsDevice device, JSONObject jsonBody, String sourceKey) {
        return updateAttr(device, jsonBody, sourceKey, sourceKey);
    }

    /**
     * 校验是否需要更新属性值
     *
     * @param jsonBody  jsonBody 消息体
     * @param sourceKey 源属性key
     * @param targetKey 目标缓存属性key
     * @return true 更新，false 不更新
     */
    public boolean updateAttr(EmsDevice device, JSONObject jsonBody, String sourceKey, String targetKey) {
        boolean flag = false;

        try {
            // 校验消息体是否包含属性key
            if (!jsonBody.containsKey(sourceKey)) {
                return flag;
            }

            // 获取属性值
            String currentValue = jsonBody.getString(sourceKey);

            // 获取缓存
            String cacheValue = redisService.getCacheMapValue(device.getDeviceCode(), targetKey);

            if (null == cacheValue) {
                EmsObjAttrValue param = new EmsObjAttrValue();
                param.setObjCode(device.getDeviceCode());
                param.setModelCode(device.getDeviceModel());
                param.setAttrKey(targetKey);

                List<EmsObjAttrValue> attrList = objAttrValueService.selectObjAttrValueList(param);
                EmsObjAttrValue attrValue = CollectionUtils.isNotEmpty(attrList) ? attrList.get(0) : null;
                cacheValue = null != attrValue ? attrValue.getAttrValue() : null;
            }

            // 比较属性值是否发生变化
            if (!StringUtils.equals(currentValue, cacheValue)) {
                EmsObjAttrValue attrValue = new EmsObjAttrValue(device.getDeviceCode(), device.getDeviceModel(),
                    targetKey, currentValue);
                // 更新数据库
                objAttrValueService.mergeObjAttrValue(attrValue);
                // 更新缓存
                updateCacheAfterSuccess(device.getDeviceCode(), targetKey, currentValue);
                new EmsApiTemplate(emsConfig).notifyAttrValueChangedAsync(device.getDeviceCode(), targetKey, cacheValue,
                    currentValue);
                flag = true;
            }
        }
        catch (Exception e) {
            log.error("检查属性并更新失败!", e);
        }

        return flag;
    }

    private CallResponse<Void> checkResult(JSONObject sendObject, JSONObject receiveObject) {
        CallResponse<Void> response = new CallResponse<>();

        try {
            String type = sendObject.getString("type");

            if (StringUtils.equals(type, "event")) {
                String sendKeyValue = sendObject.getString("key");
                String receiveKeyValue = receiveObject.getString("key");
                Assert.isTrue(StringUtils.equals(sendKeyValue, receiveKeyValue), -1, "响应参数key校验失败!");
            }
            else if (StringUtils.equals(type, "setting")) {
                if (sendObject.containsKey("keyLock")) {
                    String sendKeyLockValue = sendObject.getString("keyLock");
                    String receiveKeyLockValue = receiveObject.getString("keyLock");
                    Assert.isTrue(StringUtils.equals(sendKeyLockValue, receiveKeyLockValue), -1,
                        "响应参数keyLock校验失败!");
                }
                else if (sendObject.containsKey("timerEnable") && sendObject.containsKey("timerInterval")) {
                    String sendTimerEnableValue = sendObject.getString("timerEnable");
                    String receiveTimerEnableValue = receiveObject.getString("timerEnable");
                    Assert.isTrue(StringUtils.equals(sendTimerEnableValue, receiveTimerEnableValue), -1,
                        "响应参数timerEnable校验失败!");
                    String sendTimerIntervalValue = sendObject.getString("timerInterval");
                    String receiveTimerIntervalValue = receiveObject.getString("timerInterval");
                    Assert.isTrue(StringUtils.equals(sendTimerIntervalValue, receiveTimerIntervalValue), -1,
                        "响应参数timerInterval校验失败!");
                }
                else if (sendObject.containsKey("onState")) {
                    String sendOnStateValue = sendObject.getString("onState");
                    String receiveOnStateValue = receiveObject.getString("onState");
                    Assert.isTrue(StringUtils.equals(sendOnStateValue, receiveOnStateValue), -1,
                        "响应参数onState校验失败!");
                }
            }

            response.setCode(0);
            response.setMessage("执行成功!");
        }
        catch (BusinessException e) {
            log.warn(e.getMessage());
            response.setCode(e.getCode());
            response.setMessage(e.getMessage());
        }
        catch (Exception e) {
            log.error(e.getMessage(), e);
            response.setCode(-1);
            response.setMessage("内部错误!");
        }

        return response;
    }

    /**
     * 定时刷新在线状态
     * <br/>每小时执行一次，扫描2个小时无消息的设备，标记为离线状态
     */
    @Override
    public void refreshOnline() {
        long threshold = 2 * 60 * 60 * 1000;
        long currentTime = new Date().getTime();

        List<EmsDevice> deviceList = getDeviceList();

        if (CollectionUtils.isNotEmpty(deviceList)) {
            for (EmsDevice device : deviceList) {
                String lastMsgTime = redisService.getCacheMapValue(device.getDeviceCode(), MQTT_LAST_TIME);

                if (null != lastMsgTime) {
                    // 计算最后一次消息至今的时间差
                    long time =
                        currentTime - DateUtils.stringToDate(lastMsgTime, DateUtils.YYYY_MM_DD_HH_MM_SS).getTime();

                    if (time > threshold) {
                        refreshStatus(device, DevOnlineStatus.OFFLINE);
                    }
                }
            }
        }
    }

    /**
     * 构造下发指令
     *
     * @param abilityParam 消息
     * @return msgBody
     */
    public JSONObject buildSendMsg(AbilityPayload abilityParam) {
        String abilityKey = abilityParam.getAbilityKey();
        JSONObject jsonBody = null;

        if (StringUtils.equals("OnOffCtl", abilityKey)) {
            jsonBody = JsonEntity.objBuilder().putKv("type", "event")
                .putKv("key", Integer.parseInt(abilityParam.getAbilityParam()))
                .putKv("messageId", "CALL-" + IdUtils.generateMessageId()).build().getJsonObj();
        }
        else if (StringUtils.equals("KeyLockCtl", abilityKey)) {
            jsonBody = JsonEntity.objBuilder().putKv("type", "setting")
                .putKv("keyLock", Integer.parseInt(abilityParam.getAbilityParam()))
                .putKv("messageId", "CALL-" + IdUtils.generateMessageId()).build().getJsonObj();
        }
        else if (StringUtils.equals("settingCtl", abilityKey)) {
            jsonBody = JsonEntity.objBuilder().putKv("type", "setting").putKv("system", abilityParam.getAbilityParam())
                .putKv("messageId", "auto").build().getJsonObj();
        }
        else if (StringUtils.equals("settAutoReport", abilityKey)) {
            JsonEntity.ObjBuilder onOffJson = JsonEntity.objBuilder();
            onOffJson.putKv("type", "setting").putKv("messageId", "auto");
            Integer timerInterval = Integer.parseInt(abilityParam.getAbilityParam());

            if (0 == timerInterval) {
                onOffJson.putKv("timerEnable", 0);
            }
            else {
                onOffJson.putKv("timerEnable", 1);
                onOffJson.putKv("timerInterval", timerInterval);
            }

            jsonBody = onOffJson.build().getJsonObj();
        }
        else if (StringUtils.equals("setOnState", abilityKey)) {
            jsonBody = JsonEntity.objBuilder().putKv("type", "setting")
                .putKv("onState", Integer.parseInt(abilityParam.getAbilityParam()))
                .putKv("messageId", "CALL-" + IdUtils.generateMessageId()).build().getJsonObj();
        }
        else if (StringUtils.equals("triggerSync", abilityKey)) {
            jsonBody = JsonEntity.objBuilder().putKv("type", abilityParam.getAbilityParam()).putKv("messageId", "auto")
                .build().getJsonObj();
        }

        return jsonBody;
    }

    /**
     * 发送MQTT消息
     *
     * @param topic    主题
     * @param payload  负载数据
     * @param qos      质量服务等级
     * @param retained 是否保留消息
     */
    public void sendMqttMsg(String topic, String payload, int qos, boolean retained) {
        log.info("[Send] Topic:{}, message:{}, qos:{}, retained:{}", topic, payload, qos, retained);
        mqttTemplate.sendString(topic, payload, 2, false);
    }

}