package cn.lihongjie.coal.ratelimit;

import static cn.lihongjie.coal.common.Constants.*;

import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class RateLimiterService {

    @Autowired RedissonClient redissonClient;

    @Autowired SysConfigService sysConfigService;

    public void initAnonymousIpRL(String ip) {

        if (StringUtils.isEmpty(ip)) {
            throw new RuntimeException("ip is empty");
        }

        RRateLimiter rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX + ip);
        rateLimiter.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_MIN)),
                1,
                RateIntervalUnit.MINUTES);



        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX + ip);
        rateLimiter.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_HOUR)),
                1,
                RateIntervalUnit.HOURS);



        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX + ip);
        rateLimiter.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_DAY)),
                1,
                RateIntervalUnit.DAYS);
    }

    public RateLimitAcquireResult acquireIpRL(String ip) {

        if (StringUtils.isEmpty(ip)) {
            throw new RuntimeException("ip is empty");
        }

        try{

        RRateLimiter rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX + ip);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.MIN;
        }

        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX + ip);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.HOUR;
        }

        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX + ip);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.DAY;
        }

        return RateLimitAcquireResult.SUCCESS;
        }catch (Exception e){
            log.warn("获取IP限流失败", e);
            return RateLimitAcquireResult.ERROR;
        }

    }

    public RateLimitAcquireResult acquireSessionRL(String session) {

        if (StringUtils.isEmpty(session)) {
            throw new RuntimeException("session is empty");
        }

        RRateLimiter rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + session);

        try{

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.MIN;
        }

        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + session);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.HOUR;
        }

        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + session);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.DAY;
        }
        return RateLimitAcquireResult.SUCCESS;

        }catch (Exception e){
            log.warn("获取会话限流失败", e);
            return RateLimitAcquireResult.ERROR;
        }

    }

    public RateLimitAcquireResult acquireUserRL(String userId) {

        if (StringUtils.isEmpty(userId)) {
            throw new RuntimeException("userId is empty");
        }

        try{


        RRateLimiter rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.MIN;
        }

        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.HOUR;
        }

        rateLimiter =
                redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId);

        if (!rateLimiter.tryAcquire(1)) {
            return RateLimitAcquireResult.DAY;
        }

        return RateLimitAcquireResult.SUCCESS;
        }catch (Exception e){
            log.warn("获取用户限流失败", e);
            return RateLimitAcquireResult.ERROR;
        }
    }

    public void initRL(String sessionId, String userId) {

        if (StringUtils.isEmpty(sessionId)) {
            throw new RuntimeException("sessionId is empty");
        }

        if (StringUtils.isEmpty(userId)) {
            throw new RuntimeException("userId is empty");
        }

        // 初始化限流
        RRateLimiter sessionMinRL =
                redissonClient.getRateLimiter(
                        Constants.RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + sessionId);
        sessionMinRL.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN)),
                1,
                RateIntervalUnit.MINUTES);

        RRateLimiter sessionHourRL =
                redissonClient.getRateLimiter(
                        Constants.RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + sessionId);
        sessionHourRL.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_HOUR)),
                1,
                RateIntervalUnit.HOURS);

        RRateLimiter sessionDayRL =
                redissonClient.getRateLimiter(
                        Constants.RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + sessionId);
        sessionDayRL.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_DAY)),
                1,
                RateIntervalUnit.DAYS);

        RRateLimiter userMinRL =
                redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId);
        userMinRL.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN)),
                1,
                RateIntervalUnit.MINUTES);

        RRateLimiter userHourRL =
                redissonClient.getRateLimiter(
                        Constants.RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId);
        userHourRL.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_HOUR)),
                1,
                RateIntervalUnit.HOURS);

        RRateLimiter userDayRL =
                redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId);
        userDayRL.trySetRate(
                RateType.OVERALL,
                Integer.parseInt(
                        sysConfigService.getConfigVal(
                                Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_DAY)),
                1,
                RateIntervalUnit.DAYS);
    }



    public void destroyRL(String sessionId, String userId) {

        if (StringUtils.isEmpty(sessionId)) {
            throw new RuntimeException("sessionId is empty");
        }



        log.info("删除会话限流 session: {} user: {}", sessionId, userId);

        try {

            RRateLimiter sessionMinRL =
                    redissonClient.getRateLimiter(
                            Constants.RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + sessionId);
            sessionMinRL.delete();
        } catch (Exception e) {
            log.warn("", e);
        }
        try {

            RRateLimiter sessionHourRL =
                    redissonClient.getRateLimiter(
                            Constants.RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + sessionId);
            sessionHourRL.delete();
        } catch (Exception e) {
            log.warn("", e);
        }
        try {

            RRateLimiter sessionDayRL =
                    redissonClient.getRateLimiter(
                            Constants.RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + sessionId);
            sessionDayRL.delete();
        } catch (Exception e) {
            log.warn("", e);
        }

        // 如果当前用户没有其他会话, 则删除用户限流


        if (StringUtils.isEmpty(userId)) {
            return;
        }

        try {

            RRateLimiter userMinRL =
                    redissonClient.getRateLimiter(
                            Constants.RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId);
            userMinRL.delete();
        } catch (Exception e) {
            log.warn("", e);
        }
        try {

            RRateLimiter userHourRL =
                    redissonClient.getRateLimiter(
                            Constants.RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId);
            userHourRL.delete();
        } catch (Exception e) {
            log.warn("", e);
        }
        try {

            RRateLimiter userDayRL =
                    redissonClient.getRateLimiter(
                            Constants.RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId);
            userDayRL.delete();
        } catch (Exception e) {
            log.warn("", e);
        }
    }

    public enum RateLimitAcquireResult {
        SUCCESS,
        ERROR,
        MIN, HOUR, DAY,
    }
}
