package openjoe.smart.sso.server.manager.redis;

import openjoe.smart.sso.base.util.JsonUtils;
import openjoe.smart.sso.server.entity.TokenContent;
import openjoe.smart.sso.server.manager.AbstractTokenManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 分布式调用凭证管理
 *
 * @author Joe
 */
public class RedisTokenManager extends AbstractTokenManager {

    private final Logger logger = LoggerFactory.getLogger(RedisTokenManager.class);
    private static final String ACCESS_TOKEN_KEY = "server_token_at_";
    private static final String REFRESH_TOKEN_KEY = "server_token_rt_";
    private static final String TGT_REFRESH_TOKEN_KEY = "server_token_tgt_rt_";

    private StringRedisTemplate redisTemplate;

    public RedisTokenManager(int accessTokenTimeout, int refreshTokenTimeout, int threadPoolSize, StringRedisTemplate redisTemplate) {
        super(accessTokenTimeout, refreshTokenTimeout, threadPoolSize);
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void create(String refreshToken, TokenContent tokenContent) {
        redisTemplate.opsForValue().set(ACCESS_TOKEN_KEY + tokenContent.getAccessToken(), refreshToken, getAccessTokenTimeout(),
                TimeUnit.SECONDS);

        redisTemplate.opsForValue().set(REFRESH_TOKEN_KEY + refreshToken, JsonUtils.toString(tokenContent), getRefreshTokenTimeout(),
                TimeUnit.SECONDS);

        redisTemplate.opsForSet().add(TGT_REFRESH_TOKEN_KEY + tokenContent.getTgt(), refreshToken);
        // 创建任意的Token，都为TGT和Token映射更新失效时间
        redisTemplate.expire(TGT_REFRESH_TOKEN_KEY + tokenContent.getTgt(), getRefreshTokenTimeout(),
                TimeUnit.SECONDS);
        logger.debug("Redis调用凭证创建成功, accessToken:{}, refreshToken:{}", tokenContent.getAccessToken(), refreshToken);
    }

    @Override
    public TokenContent get(String refreshToken) {
        String tc = redisTemplate.opsForValue().get(REFRESH_TOKEN_KEY + refreshToken);
        if (!StringUtils.hasLength(tc)) {
            return null;
        }
        return JsonUtils.parseObject(tc, TokenContent.class);
    }

    @Override
    public TokenContent getByAccessToken(String accessToken) {
        String refreshToken = redisTemplate.opsForValue().get(ACCESS_TOKEN_KEY + accessToken);
        if (!StringUtils.hasLength(refreshToken)) {
            return null;
        }
        return get(refreshToken);
    }

    @Override
    public void remove(String refreshToken) {
        String tc = redisTemplate.opsForValue().get(REFRESH_TOKEN_KEY + refreshToken);
        if (!StringUtils.hasLength(tc)) {
            return;
        }
        // 删除refreshToken
        redisTemplate.delete(REFRESH_TOKEN_KEY + refreshToken);

        TokenContent tokenContent = JsonUtils.parseObject(tc, TokenContent.class);
        if (tokenContent == null) {
            return;
        }
        // 删除accessToken
        redisTemplate.delete(ACCESS_TOKEN_KEY + tokenContent.getAccessToken());

        // 删除tgt映射中的refreshToken
        redisTemplate.opsForSet().remove(TGT_REFRESH_TOKEN_KEY + tokenContent.getTgt(), refreshToken);
    }

    @Override
    public void removeByTgt(String tgt) {
        Set<String> refreshTokenSet = redisTemplate.opsForSet().members(TGT_REFRESH_TOKEN_KEY + tgt);
        if (CollectionUtils.isEmpty(refreshTokenSet)) {
            return;
        }
        // 删除tgt映射中的refreshToken集合
        redisTemplate.delete(TGT_REFRESH_TOKEN_KEY + tgt);

        submitRemoveToken(refreshTokenSet);
    }

    @Override
    public void processRemoveToken(String refreshToken) {
        String tc = redisTemplate.opsForValue().get(REFRESH_TOKEN_KEY + refreshToken);
        if (!StringUtils.hasLength(tc)) {
            return;
        }
        // 删除refreshToken
        redisTemplate.delete(REFRESH_TOKEN_KEY + refreshToken);

        TokenContent tokenContent = JsonUtils.parseObject(tc, TokenContent.class);
        if (tokenContent == null) {
            return;
        }
        // 删除accessToken
        redisTemplate.delete(ACCESS_TOKEN_KEY + tokenContent.getAccessToken());

        // 发起客户端退出请求
        logger.debug("发起客户端退出请求, accessToken:{}, refreshToken:{}, logoutUri:{}", tokenContent.getAccessToken(), refreshToken, tokenContent.getLogoutUri());
        sendLogoutRequest(tokenContent.getLogoutUri(), tokenContent.getAccessToken());
    }

    @Override
    public Map<String, Set<String>> getClientIdMapByTgt(Set<String> tgtSet) {
        Map<String, Set<String>> clientIdMap = new HashMap<>();
        tgtSet.forEach(tgt -> {
            Set<String> refreshTokenSet = redisTemplate.opsForSet().members(TGT_REFRESH_TOKEN_KEY + tgt);
            if (CollectionUtils.isEmpty(refreshTokenSet)) {
                return;
            }
            Set<String> clientIdSet = new HashSet<>();
            refreshTokenSet.forEach(refreshToken -> {
                String tc = redisTemplate.opsForValue().get(REFRESH_TOKEN_KEY + refreshToken);
                if (!StringUtils.hasLength(tc)) {
                    return;
                }

                TokenContent tokenContent = JsonUtils.parseObject(tc, TokenContent.class);
                if (tokenContent == null) {
                    return;
                }
                clientIdSet.add(tokenContent.getClientId());
            });
            clientIdMap.put(tgt, clientIdSet);
        });
        return clientIdMap;
    }

}
