package com.sonic.shark.limit;


import com.sonic.common.auth.domains.Session;
import com.sonic.common.enums.AppEnv;
import com.sonic.common.utils.IpAddressUtils;
import com.sonic.shark.enums.BizResultCode;
import com.sonic.shark.enums.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 限流控制器
 */
@Order(99)
@Slf4j
@Aspect
@Component
public class RequestLimitContract {

    @Value("${spring.profiles.active}")
    private String runMode;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * redis key不存在的过期时间值
     */
    private final Integer NULL_KEY_EXP_TIME_VALUE = -2;

    /**
     * redis key永不过期的过期时间值
     */
    private final Integer NEVER_EXPIRES_VALUE = -1;

    /**
     * 默认的异常码
     */
    private static final String DEFAULT_ERROR_CODE = "1999999";

    @Before("execution(public * com.sonic.shark.controller..*.*(..)) && @annotation(limit)")
    public void requestLimit(final JoinPoint joinPoint, RequestLimit limit) {
        //dev环境直接放行不做拦截
        if (StringUtils.isNotBlank(runMode) && AppEnv.dev.name().equals(runMode)) {
            return;
        }
        Object[] args = joinPoint.getArgs();
        HttpServletRequest request = null;
        Session session = null;
        for (Object arg : args) {
            //解析方法的HttpServletRequest入参对象
            if (arg instanceof HttpServletRequest) {
                request = (HttpServletRequest) arg;
            }
            //解析方法的Session入参对象、必须要配置了登录次数限制的才能进行解析
            if (arg instanceof Session && limit.loginCount() != Integer.MAX_VALUE) {
                session = (Session) arg;
            }
        }
        //判断请求是否为空，是否需要抛出异常
        BizResultCode.MISS_PARAM_ERROR.check(request == null);
        int num = 0;
        String ipOrUserId = null;
        Integer limitCount = null;
        String url = null;
        boolean loginUserBl = false;
        try {
            ipOrUserId = (session == null || session.getUserId() == null) ? IpAddressUtils.getIpAddress(request) : session.getUserId().toString();
            limitCount = (session == null || session.getUserId() == null) ? limit.count() : limit.loginCount();
            loginUserBl = session != null && session.getUserId() != null;
            url = request.getRequestURI();
            //eg: limit:path:/mobile/third/login:127-0-0-1
            String key = "limit:path:".concat(url).concat(":").concat(StringUtils.isNotEmpty(ipOrUserId) ? ipOrUserId.replace(":", "-") : ipOrUserId);
            //处理限流为-1的情况
            Long expTime = stringRedisTemplate.getExpire(key);
            if (expTime.intValue() == NULL_KEY_EXP_TIME_VALUE) {
                num = 1;
                stringRedisTemplate.opsForValue().set(key, "1", limit.time(), TimeUnit.MILLISECONDS);
            } else {
                num = Objects.requireNonNull(stringRedisTemplate.opsForValue().increment(key, 1)).intValue();
            }
            //处理获取过期时间如果为-1的话直接将限流redis删掉
            if(expTime.intValue() == NEVER_EXPIRES_VALUE) {
                //删除redis的key
                stringRedisTemplate.delete(key);
            }
        } catch (Exception e) {
            log.error("requestLimit error", e);
        }
        if (num > limitCount) {
            log.info("===> 限流触发，访问地址:{}, 用户信息:{}, 限定的次数:{}", ipOrUserId, url, limit.count());
            //未登录的用户达到限流时如果配置了跳转登录页的errorCode的话前端会直接去跳转登录页面
            throw new BusinessException(loginUserBl ? DEFAULT_ERROR_CODE : limit.noLoginErrorCode(), limit.message());
        }
    }

}
