package com.sonic.bear.service.impl;

import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.sonic.bear.service.SessionKickOutCacheService;
import com.sonic.bear.utils.RedisKeyUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author coder
 */
@Service
public class SessionKickOutCacheServiceImpl implements SessionKickOutCacheService, InitializingBean {

    /**
     * 默认local kickOut token缓存时间 2小时
     */
    private static volatile Duration DEFAULT_LOCAL_SESSION_CACHE_SECONDS = Duration.ofSeconds(2 * 60 * 60);

    private LoadingCache<String, String> sessionKickOutCache;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private RedisKeyUtils redisKeyUtils;


    @Override
    public Optional<String> get(String token) {
        // 通过get获取缓存，如果没有，则异步执行load从redis加载数据。即，多线程情况，可能会加载到旧值
        return Optional.ofNullable(sessionKickOutCache.get(token));
    }

    @Override
    public void cache(String token) {
        Preconditions.checkArgument(!Strings.isNullOrEmpty(token));
        // 缓存 12 小时（只要缓存的时间 > token在redis中存储的时间即可）
        redisTemplate.opsForValue().set(redisKeyUtils.sessionKickOutKey(token), "1", 12, TimeUnit.HOURS);
        sessionKickOutCache.put(token, "1");
    }

    @Override
    public void invalidate(String token) {
        redisTemplate.delete(redisKeyUtils.sessionKickOutKey(token));
        sessionKickOutCache.invalidate(token);
    }

    @Override
    public void afterPropertiesSet() {
        sessionKickOutCache = Caffeine.newBuilder()
                .expireAfterAccess(DEFAULT_LOCAL_SESSION_CACHE_SECONDS)
                // 目前活跃用户不到1w，这里最大按50倍计算，设置50w缓存数量
                .maximumSize(500_000)
                .initialCapacity(500_000 / 50)
                .recordStats()
                .build(new CacheLoader<String, String>() {
                    @Nullable
                    @Override
                    public String load(@NonNull String key) {
                        return redisTemplate.opsForValue().get(redisKeyUtils.sessionKickOutKey(key));
                    }
                });
    }
}
