package com.sonic.pigeon.config.limit;

import com.sonic.common.auth.domains.Session;
import com.sonic.common.enums.AppEnv;
import com.sonic.common.utils.IpAddressUtils;
import com.sonic.pigeon.enums.BizResultCode;
import com.sonic.pigeon.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.concurrent.TimeUnit;

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

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

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

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

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

    @Before("execution(public * com.sonic.*.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 (int i = 0; i < args.length; i++) {
            //解析方法的HttpServletRequest入参对象
            if (args[i] instanceof HttpServletRequest) {
                request = (HttpServletRequest) args[i];
            }
            //解析方法的Session入参对象、必须要配置了登录次数限制的才能进行解析
            if (args[i] instanceof Session && limit.loginCount() != Integer.MAX_VALUE) {
                session = (Session) args[i];
            }
        }
        //请求对象为空，则抛出异常
        BizResultCode.SYS_PARAMETERS_VALIDATE_EXCEPTION.check(request == null);
        int num = 0;
        String ipOrUserId = null;
        Integer limitCount = null;
        String url = null;
        boolean loginUserBl = false;
        try {
            num = 0;
            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) ? false : true;
            url = request.getRequestURI();
            String key = "req:limit:".concat(url).concat(":").concat(StringUtils.isNotEmpty(ipOrUserId) ? ipOrUserId.replace(":", "-") : ipOrUserId);
            //redis key eg: req:limit:/web/user-search/info-to-detail:127.0.0.1
            log.info("RequestLimitContract key:{}", key);
            //处理限流为-1的情况
            Long expTime = stringRedisTemplate.getExpire(key);
            if (expTime == null || expTime.intValue() == NULL_KEY_EXP_TIME_VALUE) {
                num = 1;
                stringRedisTemplate.opsForValue().set(key, "1", limit.time(), TimeUnit.MILLISECONDS);
            } else {
                num = stringRedisTemplate.opsForValue().increment(key, 1).intValue();
            }
            //处理获取过期时间如果为-1的话直接将限流redis删掉
            if(expTime != null && expTime.intValue() == NEVER_EXPIRES_VALUE) {
                //删除redis的key
                stringRedisTemplate.delete(key);
            }
        } catch (Exception e) {
            log.error("requestLimit error", e);
        }
        log.info("用户IP[" + ipOrUserId + "]访问地址[" + url + "]===>第[" + num + "]次");
        if (num > limitCount) {
            log.info("用户IP[" + ipOrUserId + "]访问地址[" + url + "]超过了限定的次数[" + limit.count() + "]");
            //未登录的用户达到限流时如果配置了跳转登录页的errorCode的话前端会直接去跳转登录页面
            throw new BusinessException(loginUserBl ? DEFAULT_ERROR_CODE : limit.noLoginErrorCode(), limit.message());
        }
    }

}
