package com.ruoyi.device.mqtt.handler;

import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.device.mqtt.enums.DeviceLineStatusEnum;
import com.ruoyi.device.mqtt.vo.DeviceOnlineInfo;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Date;

/**
 * 调试宝设备 MQTT 登录在线注册表（deviceSn → 设备信息/用户信息，Redis String 维护）
 *
 * @author lwm
 */
@Component
public class DeviceOnlineManager
{
    private static final Logger log = LoggerFactory.getLogger(DeviceOnlineManager.class);

    /** 默认心跳超时（毫秒），超过该时间未收到心跳则判定离线 */
    public static final long DEFAULT_HEARTBEAT_TIMEOUT_MS = 90_000L;

    @Resource
    private RedisCache redisCache;

    /**
     * 登录在线：写入/覆盖设备登录信息，并标记在线、刷新心跳时间
     *
     * @param info 设备登录信息
     */
    public void loginOnline(DeviceOnlineInfo info)
    {
        if (info == null || info.getDeviceSn() == null)
        {
            return;
        }
        info.setLineStatus(DeviceLineStatusEnum.ON_LINE.getStatus());
        info.setHeartbeatTime(new Date());
        saveDeviceLoginInfo(info);
        log.info("设备登录在线, deviceSn={}, userId={}", info.getDeviceSn(), info.getUserId());
    }

    /**
     * 登出离线：保留登录信息，仅标记离线
     *
     * @param deviceSn 设备SN码
     */
    public void logoutOffline(Long deviceSn)
    {
        if (deviceSn == null)
        {
            return;
        }
        DeviceOnlineInfo info = getDeviceLoginInfo(deviceSn);
        if (info == null)
        {
            log.info("设备登出离线，登录信息不存在, deviceSn={}", deviceSn);
            return;
        }
        info.setLineStatus(DeviceLineStatusEnum.OFF_LINE.getStatus());
        saveDeviceLoginInfo(info);
        log.info("设备登出离线, deviceSn={}, userId={}", deviceSn, info.getUserId());
    }

    /**
     * 心跳在线：刷新心跳时间，恢复/保持在线状态
     *
     * @param deviceSn 设备SN码
     */
    public void heartbeatOnline(Long deviceSn)
    {
        if (deviceSn == null)
        {
            return;
        }
        DeviceOnlineInfo info = getDeviceLoginInfo(deviceSn);
        if (info == null)
        {
            log.info("设备心跳，登录信息不存在, deviceSn={}", deviceSn);
            return;
        }
        info.setLineStatus(DeviceLineStatusEnum.ON_LINE.getStatus());
        info.setHeartbeatTime(new Date());
        saveDeviceLoginInfo(info);
        log.info("设备心跳在线, deviceSn={}", deviceSn);
    }

    /**
     * 长时间未心跳离线：标记离线（由定时扫描或外部检测调用）
     *
     * @param deviceSn 设备SN码
     */
    public void offlineByHeartbeatTimeout(Long deviceSn)
    {
        if (deviceSn == null)
        {
            return;
        }
        DeviceOnlineInfo info = getDeviceLoginInfo(deviceSn);
        if (info == null || DeviceLineStatusEnum.isOffLine(info.getLineStatus()))
        {
            return;
        }
        info.setLineStatus(DeviceLineStatusEnum.OFF_LINE.getStatus());
        saveDeviceLoginInfo(info);
        log.info("设备心跳超时离线, deviceSn={}, lastHeartbeat={}", deviceSn, info.getHeartbeatTime());
    }

    /**
     * 扫描所有在线设备，将心跳超时的设备标记为离线
     *
     */
    public void scanOfflineByHeartbeatTimeout()
    {
        Collection<String> keys = redisCache.keys(CacheConstants.TSB_DEVICE_ONLINE_KEY + "*");
        if (keys == null || keys.isEmpty())
        {
            return;
        }
        Date now = new Date();
        for (String key : keys)
        {
            DeviceOnlineInfo info = redisCache.getCacheObject(key);
            if (info == null || DeviceLineStatusEnum.isOffLine(info.getLineStatus()))
            {
                continue;
            }
            Date lastHeartbeat = info.getHeartbeatTime();
            if (lastHeartbeat == null || now.getTime() - lastHeartbeat.getTime() > DEFAULT_HEARTBEAT_TIMEOUT_MS)
            {
                offlineByHeartbeatTimeout(info.getDeviceSn());
            }
        }
    }

    /**
     * 获取设备登录信息（含在线状态与心跳时间）
     *
     * @param deviceSn 设备SN码
     * @return 设备登录信息；未登录过则返回 null
     */
    public DeviceOnlineInfo getDeviceLoginInfo(Long deviceSn)
    {
        if (deviceSn == null)
        {
            return null;
        }
        return redisCache.getCacheObject(getDeviceOnlineKey(deviceSn));
    }

    /**
     * 设备是否在线
     *
     * @param deviceSn 设备SN码
     */
    public boolean isOnline(Long deviceSn)
    {
        DeviceOnlineInfo info = getDeviceLoginInfo(deviceSn);
        return info != null && DeviceLineStatusEnum.isOnLine(info.getLineStatus());
    }

    private void saveDeviceLoginInfo(DeviceOnlineInfo info)
    {
        redisCache.setCacheObject(getDeviceOnlineKey(info.getDeviceSn()), info);
    }

    private String getDeviceOnlineKey(Long deviceSn)
    {
        return CacheConstants.TSB_DEVICE_ONLINE_KEY + deviceSn;
    }
}
