package com.red.circle.aspect;

import com.red.circle.common.business.core.util.IpUtil;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Slf4j
@Aspect
@Component
public class RequestAspect {

  private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>();

  @Value("${req.ignore.urls}")
  private List<String> ignoreList;

  @Value("${req.limit.count}")
  private Integer limitCount;

  @Value("${req.statistic.time}")
  private Integer statisticTime;

  @Autowired
  private RedisTemplate<String, Object> redisTemplate;

  @Pointcut("execution(public * com.red.circle.live.adapter.app.*Controller.*(..))")
  public void pointcut() {
  }

  @Before("pointcut()")
  public void doBefore(JoinPoint joinPoint) {
    // 接收到请求，记录请求内容
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    String traceId = UUID.randomUUID().toString().replace("-", "");
    MDC.put("traceId", traceId);
    START_TIME.set(System.currentTimeMillis());
    if (null == attributes) {
      return;
    }
    HttpServletRequest request = attributes.getRequest();
    String ipAddr = IpUtil.getIpAddr(request);
    String requestURL = String.valueOf(request.getRequestURL());
    String ipMethod = ipAddr + requestURL;
    boolean countReq = Boolean.TRUE;
    if (null != ignoreList) {
      for (String url : ignoreList) {
        if (requestURL.contains(url)) {
          countReq = Boolean.FALSE;
          break;
        }
      }
    }
    if (countReq) {
      Long requestURLExpire = redisTemplate.getExpire(requestURL);
      Long ipMethodExpire = redisTemplate.getExpire(ipMethod);
      Long ipAddrExpire = redisTemplate.getExpire(ipAddr);
      if (ipAddrExpire != null && ipAddrExpire < 0) {
        redisTemplate.opsForValue().set(ipAddr, 0, statisticTime, TimeUnit.SECONDS);
      }
      if (requestURLExpire != null && requestURLExpire < 0) {
        redisTemplate.opsForValue().set(requestURL, 0, statisticTime, TimeUnit.SECONDS);
      }
      if (ipMethodExpire != null && ipMethodExpire < 0) {
        redisTemplate.opsForValue().set(ipMethod, 0, statisticTime, TimeUnit.SECONDS);
      }

      redisTemplate.opsForValue().setIfAbsent(ipMethod, 0, statisticTime, TimeUnit.SECONDS);
      redisTemplate.opsForValue().setIfAbsent(ipAddr, 0, statisticTime, TimeUnit.SECONDS);
      redisTemplate.opsForValue().setIfAbsent(requestURL, 0, statisticTime, TimeUnit.SECONDS);
      Long reqCountForIpMethod = redisTemplate.opsForValue().increment(ipMethod);
      Long reqCountForIp = redisTemplate.opsForValue().increment(ipAddr);
      Long reqCountForUrl = redisTemplate.opsForValue().increment(requestURL);
      if (reqCountForIpMethod == null || reqCountForIpMethod > 1000 || reqCountForIp == null
          || reqCountForIp > 1000 || null == reqCountForUrl || reqCountForUrl > 1000) {
        log.warn(" Request URL {} reqCountForIpMethod {} reqCountForIp {} reqCountForUrl {}",
            ipMethod,
            reqCountForIpMethod, reqCountForIp,
            reqCountForUrl);
      }
      if (null != reqCountForIp && limitCount < reqCountForIp) {
        throw new RuntimeException(
            "请求失败 Retry fail for limitCount=" + limitCount + " reqCountForIp=" + reqCountForIp);
      }
    }
  }

  @AfterReturning(returning = "ret", pointcut = "pointcut()")
  public void doAfterReturning(Object ret) {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    if (null != attributes) {
      HttpServletRequest request = attributes.getRequest();
      // 处理完请求，返回内容
      long useTime = System.currentTimeMillis() - START_TIME.get();
      if (useTime > 1000) {
        log.warn(" Request [{},{}] Response Time : {} ms ", request.getMethod(),
            request.getRequestURL(), useTime);
      }
    }
    START_TIME.remove();
    MDC.remove("traceId");
  }

}
