package cn.iocoder.yudao.module.iot.mq.consumer.device;

import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.NewIotDeviceDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Objects;

/**
 * 针对 {@link IotDeviceMessage} 的业务处理器：调用 method 对应的逻辑。例如说：
 * 1. {@link IotDeviceMessageMethodEnum#PROPERTY_POST} 属性上报时，记录设备属性
 *
 * @author alwayssuper
 */
@Component
@Slf4j
public class IotDeviceMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {

    @Resource
    private IotDeviceService deviceService;
    @Resource
    private IotDevicePropertyService devicePropertyService;
    @Resource
    private IotDeviceMessageService deviceMessageService;

    @Resource
    private IotMessageBus messageBus;

    @PostConstruct
    public void init() {
        messageBus.register(this);
    }

    @Override
    public String getTopic() {
        return IotDeviceMessage.MESSAGE_BUS_DEVICE_MESSAGE_TOPIC;
    }

    @Override
    public String getGroup() {
        return "iot_device_message_consumer";
    }

    @Override
    public void onMessage(IotDeviceMessage message) {
        if (!IotDeviceMessageUtils.isUpstreamMessage(message)) {
            log.error("[onMessage][message({}) 非上行消息，不进行处理]", message);
            return;
        }

        TenantUtils.execute(message.getTenantId(), () -> {
            // 1.1 更新设备的最后时间
            NewIotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
            devicePropertyService.updateDeviceReportTimeAsync(device.getId(), LocalDateTime.now());
            // 1.2 更新设备的连接 server
            devicePropertyService.updateDeviceServerIdAsync(device.getId(), message.getServerId());

            // 2. 未上线的设备，强制上线
            forceDeviceOnline(message, device);

            // 3. 核心：处理消息
            deviceMessageService.handleUpstreamDeviceMessage(message, device);
        });
    }

    private void forceDeviceOnline(IotDeviceMessage message, NewIotDeviceDO device) {
        // 已经在线，无需处理
        if (ObjectUtil.equal(device.getState(), IotDeviceStateEnum.ONLINE.getState())) {
            return;
        }
        // 如果是 STATE 相关的消息，无需处理，不然就重复处理状态了
        if (Objects.equals(message.getMethod(), IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod())) {
            return;
        }

        // 特殊：设备非在线时，主动标记设备为在线
        // 为什么不直接更新状态呢？因为通过 IotDeviceMessage 可以经过一系列的处理，例如说记录日志、规则引擎等等
        try {
            deviceMessageService.sendDeviceMessage(IotDeviceMessage.buildStateUpdateOnline().setDeviceId(device.getId()));
        } catch (Exception e) {
            // 注意：即使执行失败，也不影响主流程
            log.error("[forceDeviceOnline][message({}) device({}) 强制设备上线失败]", message, device, e);
        }
    }

}
