package com.njcn.product.auth.service;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JWSObject;
import com.njcn.common.pojo.constant.SecurityConstants;
import com.njcn.common.pojo.dto.UserTokenInfo;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.redis.utils.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author hongawen
 * @version 1.0.0
 * @date 2022年03月11日 10:34
 */
@Slf4j
@Service
@AllArgsConstructor
public class UserTokenService {

    private final RedisUtil redisUtil;

    /**
     * 记录用户token信息，并经过处理后达到最新登录的使用者，将之前的token信息置为黑名单，过期状态
     * 1、从在线名单中获取该用户的token信息，key为:TOKEN_ONLINE_PREFIX+userid，value为userTokenInfo的json对象
     * 1.1 有，则表示有人使用该账户登录过
     * 1.1.1 将在线名单的用户信息添加到黑名单，并清除黑名单中已经过期的token信息
     * ，重新赋值黑名单信息，key为：TOKEN_BLACKLIST_PREFIX+userid,value为userTokenInfo的集合
     * 1.2 没有，该账号当前只有本人在登录，将当前token等信息保存到白名单
     *
     * @param oAuth2AccessToken 认证后的最新token信息
     */
    @Async("asyncExecutor")
    public void recordUserInfo(OAuth2AccessToken oAuth2AccessToken, String ip) {
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        String accessTokenValue = oAuth2AccessToken.getValue();
        JWSObject accessJwsObject;
        try {
            accessJwsObject = JWSObject.parse(accessTokenValue);
        } catch (ParseException e) {
            throw new BusinessException(CommonResponseEnum.PARSE_TOKEN_ERROR);
        }
        JSONObject accessJson = JSONUtil.parseObj(accessJwsObject.getPayload().toString());
        String userIndex = accessJson.getStr(SecurityConstants.USER_INDEX_KEY);
        // String nickName = accessJson.getStr(SecurityConstants.USER_NICKNAME_KEY);
        // String loginName = accessJson.getStr(SecurityConstants.USER_NAME_KEY);
        //查询是否有在线的当前用户
        String onlineUserKey = SecurityConstants.TOKEN_ONLINE_PREFIX + userIndex;
        Object onlineTokenInfoOld = redisUtil.getObjectByKey(onlineUserKey);
        if (!Objects.isNull(onlineTokenInfoOld)) {
            //存在在线用户，将在线用户添加到黑名单列表
            String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex;
            List<UserTokenInfo> blackUsers = (List<UserTokenInfo>) redisUtil.getObjectByKey(blackUserKey);
            if (CollectionUtils.isEmpty(blackUsers)) {
                blackUsers = new ArrayList<>();
            }
            blackUsers.add((UserTokenInfo) onlineTokenInfoOld);
            //筛选黑名单中是否存在过期的token信息
            blackUsers.removeIf(userTokenInfoTemp -> userTokenInfoTemp.getRefreshTokenExpire().isBefore(LocalDateTime.now()));
            //将黑名单集合重新缓存，此处根据最新的黑名单计算当前这个key的生命周期，在时间差的基础上增加5分钟的延迟时间
            LocalDateTime refreshTokenExpire = ((UserTokenInfo) onlineTokenInfoOld).getRefreshTokenExpire();
            long lifeTime = Math.abs(refreshTokenExpire.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));
            redisUtil.saveByKeyWithExpire(blackUserKey, blackUsers, lifeTime);
        }
        String accessJti = accessJson.getStr(SecurityConstants.JWT_JTI);
        OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
        JWSObject refreshJwsObject;
        try {
            refreshJwsObject = JWSObject.parse(refreshToken.getValue());
        } catch (ParseException e) {
            throw new BusinessException(CommonResponseEnum.PARSE_TOKEN_ERROR);
        }
        JSONObject refreshJson = JSONUtil.parseObj(refreshJwsObject.getPayload().toString());
        // String refreshJti = refreshJson.getStr(SecurityConstants.JWT_JTI);
        Long refreshExpireTime = refreshJson.getLong(SecurityConstants.JWT_EXP);
        userTokenInfo.setAccessTokenJti(accessJti);
        userTokenInfo.setRefreshToken(refreshToken.getValue());
        LocalDateTime refreshLifeTime = LocalDateTime.ofEpochSecond(refreshExpireTime, 0, ZoneOffset.of("+8"));
        userTokenInfo.setRefreshTokenExpire(refreshLifeTime);
        //生命周期在refreshToken的基础上，延迟5分钟
        redisUtil.saveByKeyWithExpire(onlineUserKey, userTokenInfo, refreshLifeTime.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));

        //记录成功登录后的信息
        //LogInfoDTO logInfoDTO = new LogInfoDTO(loginName, nickName, ip, "登录认证", OperateType.AUTHENTICATE, 1, "", 0, 1, generalInfo.getMicroServiceName(), userIndex,LocalDateTime.now());
        //publisher.send("/userLog", PubUtils.obj2json(logInfoDTO), 2, false);
    }

    /**
     * 校验刷新token是否被加入黑名单
     *
     * @param refreshToken 刷新token
     */
    public void judgeRefreshToken(String refreshToken) {
        JWSObject refreshJwsObject;
        try {
            refreshJwsObject = JWSObject.parse(refreshToken);
        } catch (ParseException e) {
            throw new BusinessException();
        }
        JSONObject refreshJson = JSONUtil.parseObj(refreshJwsObject.getPayload().toString());
        String userIndex = refreshJson.getStr(SecurityConstants.USER_INDEX_KEY);
        String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex;
        List<UserTokenInfo> blackUsers = (List<UserTokenInfo>) redisUtil.getObjectByKey(blackUserKey);
        if (CollectionUtils.isNotEmpty(blackUsers)) {
            blackUsers.forEach(temp -> {
                //存在当前的刷新token，则抛出业务异常
                if (temp.getRefreshToken().equalsIgnoreCase(refreshToken)) {
                    throw new BusinessException(CommonResponseEnum.TOKEN_EXPIRE_JWT);
                }
            });
        }
    }
}
