
package com.tmerclub.cloud.auth.manager;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.tmerclub.cloud.api.auth.bo.LoginInfoBO;
import com.tmerclub.cloud.api.auth.bo.UidInfoBO;
import com.tmerclub.cloud.api.auth.constant.SocialType;
import com.tmerclub.cloud.api.auth.constant.SysTypeEnum;
import com.tmerclub.cloud.api.auth.dto.ThirdApiAbutmentUserDto;
import com.tmerclub.cloud.api.auth.vo.TokenInfoVO;
import com.tmerclub.cloud.auth.service.AuthAccountService;
import com.tmerclub.cloud.common.cache.constant.OauthCacheNames;
import com.tmerclub.cloud.common.cache.util.RedisUtil;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.security.AuthTokenContext;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.security.bo.TokenInfoBO;
import com.tmerclub.cloud.common.util.Json;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * token管理 1. 登陆返回token 2. 刷新token 3. 清除用户过去token 4. 校验token
 *
 * @author tmerclub
 * @date 2020/7/2
 */
@Component
@RefreshScope
public class TokenStore {

    private static final Logger logger = LoggerFactory.getLogger(TokenStore.class);

    private final RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private AuthAccountService authAccountService;

    public TokenStore(RedisTemplate<Object, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 根据accessToken 获取用户信息
     * @param accessToken accessToken
     * @return 用户信息
     */
    public UidInfoBO getUidInfoByAccessToken(String accessToken) {
        if (StrUtil.isBlank(accessToken)) {
            throw new LuckException("accessToken is blank");
        }
        String uid = this.getUidByToken(accessToken);
        if (Objects.isNull(uid)) {
            return null;
        }
        return AuthUserContext.getUidInfo(uid);
    }

    /**
     * 根据accessToken 获取用户信息
     * @param accessToken accessToken
     * @return 用户信息
     */
    public UidInfoBO getUidInfoByCache(String accessToken) {
        String uid = this.getUidByToken(accessToken);
        if (Objects.isNull(uid)) {
            return null;
        }
        return AuthUserContext.getUidInfo(uid);
    }

    /**
     * 直接从上下文中获取用户信息，适用于登陆后的操作
     * @return
     */
    public UidInfoBO getUidInfoInToken() {
        UidInfoBO uidInfoBO = AuthUserContext.get();
        // 如果用户信息为空，则退出登录
        if (Objects.isNull(uidInfoBO)) {
            String uid = AuthUserContext.getUid(uidInfoBO);
            StpUtil.logout(uid);
            throw new LuckException(ResponseEnum.UNAUTHORIZED, "登录过期，请重新登录");
        }
        return this.getUidInfoInTokenByCache(uidInfoBO);
    }

    /**
     * 获取登录信息
     * @return 用户信息
     */
    public LoginInfoBO getLoginInfoByCache() {
        String accessToken = AuthTokenContext.getToken();
        if (accessToken == null) {
            return new LoginInfoBO();
        }
        // 获取登录的信息
        Object loginInfoCache = RedisUtil.get(OauthCacheNames.LOGIN_INFO + accessToken);
        if (Objects.isNull(loginInfoCache)) {
            return new LoginInfoBO();
        }
        return JSONObject.parseObject(loginInfoCache.toString(), LoginInfoBO.class);
    }

    public TokenInfoVO storeAndGetVo(UidInfoBO uidInfoBO) {
        TokenInfoBO tokenInfoBO = this.storeAccessSaToken(uidInfoBO);
        TokenInfoVO tokenInfoVO = new TokenInfoVO();
        tokenInfoVO.setAccessToken(tokenInfoBO.getAccessToken());
        tokenInfoVO.setExpiresIn(tokenInfoBO.getExpiresIn());
        return tokenInfoVO;
    }

    /**
     * 计算过期时间（单位:秒）
     * @param sysType
     * @return
     */
    public int getExpiresIn(int sysType) {
        // 3600秒
        int expiresIn = 3600;
        // 普通用户token过期时间 1小时
        if (Objects.equals(sysType, SysTypeEnum.ORDINARY.value())) {
            expiresIn = expiresIn * 24 * 30;
        }
        // 系统管理员的token过期时间 2小时
        if (Objects.equals(sysType, SysTypeEnum.MULTISHOP.value()) || Objects.equals(sysType, SysTypeEnum.SUPPLIER.value()) || Objects.equals(sysType, SysTypeEnum.PLATFORM.value())) {
            expiresIn = expiresIn * 24 * 30;
        }
        return expiresIn;
    }

    /**
     * 删除当前用户的token
     */
    public void deleteUserToken() {
        String uid = getUidByToken(AuthTokenContext.getToken());
        // 删除用户缓存
        redisTemplate.delete(OauthCacheNames.USER_INFO + uid);
        // 移除该账号的全部token
        StpUtil.logout(uid);
    }


    /**
     * 根据uid信息删除token
     * @param uidInfoBO
     */
    public void deleteTokenByUidInfo(UidInfoBO uidInfoBO) {
        String uid = AuthUserContext.getUid(uidInfoBO);
        // 删除用户缓存
        redisTemplate.delete(OauthCacheNames.USER_INFO + uid);
        // 移除该账号的全部token
        StpUtil.logout(uid);
    }


    /**
     * 更新uidinfo数据
     * @param uidInfoBO
     */
    public void updateUidInfo(UidInfoBO uidInfoBO) {
        if (uidInfoBO == null) {
            return;
        }
        // 更新userInfoToken
        redisTemplate.opsForValue().set(
                OauthCacheNames.USER_INFO + this.getUidByToken(AuthTokenContext.getToken()),
                JSON.toJSONString(uidInfoBO)
        );
    }

    /**
     * 根据uid更新uidinfo数据
     * @param uid
     * @param uidInfoBO
     */
    public void updateUidInfoByUid(String uid, UidInfoBO uidInfoBO) {
        if (uid == null) {
            return;
        }
        // 更新userInfoToken
        redisTemplate.opsForValue().set(
                OauthCacheNames.USER_INFO + uid,
                JSON.toJSONString(uidInfoBO)
        );
    }



    /**
     * 以Sa-Token技术生成token，并返回token信息
     * @param uidInfoBO
     * @return
     */
    private TokenInfoBO storeAccessSaToken(UidInfoBO uidInfoBO) {
        logger.info("storeAccessSaToken-请求参数：{}", Json.toJsonString(uidInfoBO));
        String uid = AuthUserContext.getUid(uidInfoBO);
        // token生成
        int timeoutSecond = getExpiresIn(uidInfoBO.getSysType());
        StpUtil.login(uid, timeoutSecond);

        String token = StpUtil.getTokenValue();
        // 用户信息存入缓存
        String keyName = OauthCacheNames.USER_INFO + uid;
        redisTemplate.opsForValue().set(keyName, JSON.toJSONString(uidInfoBO));
        // 数据封装返回(token不用加密)
        TokenInfoBO tokenInfoBO = new TokenInfoBO();
        tokenInfoBO.setUidInfoBO(uidInfoBO);
        tokenInfoBO.setExpiresIn(timeoutSecond);
        tokenInfoBO.setAccessToken(token);
        return tokenInfoBO;
    }


    private UidInfoBO getUidInfoInTokenByCache(UidInfoBO uidInfoBO) {
        String uid = AuthUserContext.getUid(uidInfoBO);
        String keyName = OauthCacheNames.USER_INFO + uid;
        Object redisCache = redisTemplate.opsForValue().get(keyName);
        // 如果用户信息为空，则退出登录
        if (Objects.isNull(redisCache)) {
            StpUtil.logout(uid);
            // 登录过期，请重新登录
            throw new LuckException(ResponseEnum.UNAUTHORIZED, "登录过期，请重新登录");
        }
        return JSON.parseObject(redisCache.toString(), UidInfoBO.class);
    }

    /**
     * 获取登录用户的uid
     * @return
     */
    private String getUidByToken(String token) {
        Object uid;
        try {
            uid = StpUtil.getLoginIdByToken(token);
        } catch (NotLoginException e) {
            // 登录过期，请重新登录 -- 如果是sa-token未登录异常，需要替换成商城的未登录异常
            throw new LuckException(ResponseEnum.UNAUTHORIZED, "登录过期，请重新登录");
        }
        if (Objects.isNull(uid)) {
            return null;
        }
        return uid.toString();
    }

    /**
     * 获取登录用户的uid
     * @return
     */
    private String getUid() {
        Object uid;
        try {
            uid = StpUtil.getLoginId();
        } catch (NotLoginException e) {
            // 登录过期，请重新登录 -- 如果是sa-token未登录异常，需要替换成商城的未登录异常
            throw new LuckException(ResponseEnum.UNAUTHORIZED, "登录过期，请重新登录");
        }
        return uid.toString();
    }


    /**
     * 将第三方token同步到商城
     * @param thirdApiAbutmentUserDto
     */
    public void userTokenSync(ThirdApiAbutmentUserDto thirdApiAbutmentUserDto) {

        UidInfoBO uidInfoBO = new UidInfoBO(SysTypeEnum.ORDINARY.value(), thirdApiAbutmentUserDto.getUid(), thirdApiAbutmentUserDto.getUserId());
        String uid = AuthUserContext.getUid(uidInfoBO);
        // 将token名称和过期时间设置为第三方的
        SaLoginModel saLoginModel = new SaLoginModel();
        saLoginModel.setToken(thirdApiAbutmentUserDto.getAccessToken());
        saLoginModel.setTimeout(thirdApiAbutmentUserDto.getExpiresIn());
        StpUtil.login(uid, saLoginModel);
        // 小程序或者公众号登录
        if (Objects.equals(thirdApiAbutmentUserDto.getSocialType(), SocialType.MA.value())
                || Objects.equals(thirdApiAbutmentUserDto.getSocialType(), SocialType.MP.value()) ) {
            // 缓存当前登录的openId
            LoginInfoBO loginInfoBO = new LoginInfoBO();
            loginInfoBO.setSocialType(thirdApiAbutmentUserDto.getSocialType());
            loginInfoBO.setBizUserId(thirdApiAbutmentUserDto.getOpenId());
            RedisUtil.set(OauthCacheNames.LOGIN_INFO + thirdApiAbutmentUserDto.getAccessToken(),
                    JSONObject.toJSONString(loginInfoBO),
                    thirdApiAbutmentUserDto.getExpiresIn());
        }
    }
}
