package org.dromara.system.controller.monitor;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ThreadUtils;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.redis.annotation.RepeatSubmit;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.api.domain.UserOnlineDTO;
import org.dromara.system.domain.SysUserOnline;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

/**
 * 在线用户监控
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@RestController
@RequestMapping("/monitor/online")
public class SysUserOnlineController extends BaseController {

    /**
     * 获取在线用户监控列表，并按 IP 或用户名条件过滤当前有效会话。
     *
     * @param ipaddr   IP地址
     * @param userName 用户名
     * @return 在线用户分页数据
     */
    @SaCheckPermission("monitor:online:list")
    @GetMapping("/list")
    public R<PageResult<SysUserOnline>> list(String ipaddr, String userName) {
        // 获取所有未过期的 token
        Collection<String> keys = RedisUtils.keys(CacheNames.ONLINE_TOKEN_KEY + "*");
        List<Supplier<UserOnlineDTO>> suppliers = keys.stream().map(key -> (Supplier<UserOnlineDTO>) () -> {
            String token = StringUtils.substringAfterLast(key, ":");
            // 如果已经过期则跳过
            if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
                return null;
            }
            return RedisUtils.getCacheObject(CacheNames.ONLINE_TOKEN_KEY + token);
        }).toList();
        List<UserOnlineDTO> userOnlineDTOList = ThreadUtils.virtualSubmitAll(suppliers);
        userOnlineDTOList.removeAll(Collections.singleton(null));
        if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) {
            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
                StringUtils.equals(ipaddr, userOnline.getIpaddr()) &&
                    StringUtils.equals(userName, userOnline.getUserName())
            );
        } else if (StringUtils.isNotEmpty(ipaddr)) {
            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
                StringUtils.equals(ipaddr, userOnline.getIpaddr())
            );
        } else if (StringUtils.isNotEmpty(userName)) {
            userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
                StringUtils.equals(userName, userOnline.getUserName())
            );
        }
        Collections.reverse(userOnlineDTOList);
        List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
        return R.ok(PageResult.build(userOnlineList));
    }

    /**
     * 按 token 强制用户下线，适用于管理员踢除异常会话。
     *
     * @param tokenId token值
     * @return 操作结果
     */
    @SaCheckPermission("monitor:online:forceLogout")
    @Log(title = "在线用户", businessType = BusinessType.FORCE)
    @RepeatSubmit()
    @DeleteMapping("/{tokenId}")
    public R<Void> forceLogout(@PathVariable String tokenId) {
        try {
            StpUtil.kickoutByTokenValue(tokenId);
        } catch (NotLoginException ignored) {
        }
        return R.ok();
    }

    /**
     * 获取当前登录用户的在线设备列表，仅返回当前账号仍有效的 token 会话。
     *
     * @return 当前用户在线设备列表
     */
    @GetMapping()
    public R<PageResult<SysUserOnline>> getInfo() {
        // 获取指定账号 id 的 token 集合
        List<String> tokenIds = StpUtil.getTokenValueListByLoginId(StpUtil.getLoginIdAsString());
        List<Supplier<UserOnlineDTO>> suppliers = tokenIds.stream().map(token -> (Supplier<UserOnlineDTO>) () -> {
            if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
                return null;
            }
            return RedisUtils.getCacheObject(CacheNames.ONLINE_TOKEN_KEY + token);
        }).toList();
        List<UserOnlineDTO> userOnlineDTOList = ThreadUtils.virtualSubmitAll(suppliers);
        //复制和处理 SysUserOnline 对象列表
        Collections.reverse(userOnlineDTOList);
        userOnlineDTOList.removeAll(Collections.singleton(null));
        List<SysUserOnline> userOnlineList = BeanUtil.copyToList(userOnlineDTOList, SysUserOnline.class);
        return R.ok(PageResult.build(userOnlineList));
    }

    /**
     * 强退当前账号下指定在线设备，避免误踢其他账号的会话。
     *
     * @param tokenId token值
     * @return 操作结果
     */
    @Log(title = "在线设备", businessType = BusinessType.FORCE)
    @RepeatSubmit()
    @DeleteMapping("/myself/{tokenId}")
    public R<Void> remove(@PathVariable("tokenId") String tokenId) {
        try {
            // 获取指定账号 id 的 token 集合
            List<String> keys = StpUtil.getTokenValueListByLoginId(StpUtil.getLoginIdAsString());
            keys.stream()
                .filter(key -> key.equals(tokenId))
                .findFirst()
                .ifPresent(key -> StpUtil.kickoutByTokenValue(tokenId));
        } catch (NotLoginException ignored) {
        }
        return R.ok();
    }

}
