package org.dromara.web.service.impl;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.web.service.LearnerIdleSessionService;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Collection;
import java.util.Collections;

/**
 * 学习者超时签退服务实现类
 *
 * @author shihongwei
 * @date 2026/04/09
 */
@Slf4j
@Service
public class LearnerIdleSessionServiceImpl implements LearnerIdleSessionService {

    private static final String LEARNER_IDLE_KEY = "auth:learner:idle:last:";
    private static final long IDLE_TIMEOUT_MS = 60L * 60L * 1000L;
    private static final String IDLE_MSG = "您长时间未进行操作，为保障账号安全，已自动退出登录。";

    @Override
    public void checkAndRefresh() {
        String loginPort = String.valueOf(StpUtil.getExtra(LoginHelper.LOGIN_PORT));
        if (!StringUtils.equals("port-driver", loginPort)) {
            return;
        }
        String tokenValue = StpUtil.getTokenValue();
        if (StringUtils.isBlank(tokenValue)) {
            return;
        }
        String key = LEARNER_IDLE_KEY + tokenValue;
        Long now = System.currentTimeMillis();
        Long lastActive = RedisUtils.getCacheObject(key);
        if (lastActive != null && now - lastActive > IDLE_TIMEOUT_MS) {
            timeoutKick(tokenValue);
            throw NotLoginException.newInstance(StpUtil.getLoginType(), "-100", IDLE_MSG, tokenValue);
        }
        long tokenTimeout = StpUtil.getTokenTimeout();
        long cacheSeconds = tokenTimeout > 0 ? tokenTimeout : 2 * 60 * 60;
        RedisUtils.setCacheObject(key, now, Duration.ofSeconds(cacheSeconds));
    }

    @Override
    public void scanAndKickTimeout() {
        long now = System.currentTimeMillis();
        Collection<String> keys = RedisUtils.keys(LEARNER_IDLE_KEY + "*");
        if (keys == null || keys.isEmpty()) {
            return;
        }
        for (String key : keys) {
            Long lastActive = RedisUtils.getCacheObject(key);
            if (lastActive == null || now - lastActive <= IDLE_TIMEOUT_MS) {
                continue;
            }
            int idx = key.lastIndexOf(LEARNER_IDLE_KEY);
            if (idx < 0) {
                continue;
            }
            String tokenValue = key.substring(idx + LEARNER_IDLE_KEY.length());
            if (StringUtils.isBlank(tokenValue)) {
                continue;
            }
            timeoutKick(tokenValue);
        }
    }

    @Override
    public void clear(String tokenValue) {
        if (StringUtils.isBlank(tokenValue)) {
            return;
        }
        RedisUtils.deleteObject(LEARNER_IDLE_KEY + tokenValue);
    }

    private void timeoutKick(String tokenValue) {
        clear(tokenValue);
        try {
            Object userIdObj = StpUtil.getExtra(tokenValue, LoginHelper.USER_KEY);
            if (userIdObj != null) {
                String payload = "{\"type\":\"FORCE_LOGOUT\",\"targetToken\":\"" + tokenValue + "\",\"reason\":\"IDLE_TIMEOUT\",\"message\":\"" + IDLE_MSG + "\"}";
                SseMessageDto dto = new SseMessageDto();
                dto.setUserIds(Collections.singletonList(Long.valueOf(userIdObj.toString())));
                dto.setMessage(payload);
                SseMessageUtils.publishMessage(dto);
            }
        } catch (Exception e) {
            log.warn("学习者超时SSE推送失败 token={}", tokenValue);
        }
        try {
            StpUtil.kickoutByTokenValue(tokenValue);
        } catch (Exception e) {
            log.warn("学习者超时签退失败 token={}", tokenValue);
        }
    }
}
