package com.lesingle.creation.common.aspect;

import com.lesingle.creation.common.annotation.RateLimiter;
import com.lesingle.creation.common.exception.BusinessException;
import com.lesingle.creation.common.util.RateLimiterUtil;
import com.lesingle.creation.common.util.TraceIdUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * 限流切面
 * <p>
 * 拦截标注了 @RateLimiter 注解的方法，实现限流控制
 *
 * @author lesingle
 * @since 1.0.0
 */
@Slf4j
@Aspect
@Component
public class RateLimiterAspect {

    private static final ExpressionParser PARSER = new SpelExpressionParser();

    /**
     * 前置通知：执行限流检查
     */
    @Before("@annotation(rateLimiter)")
    public void before(JoinPoint joinPoint, RateLimiter rateLimiter) {
        // 获取注解参数
        String keyExpression = rateLimiter.key();
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        String message = rateLimiter.message();

        // 解析 Key
        String key = parseKey(keyExpression, joinPoint);

        // 执行限流检查
        boolean allowed = RateLimiterUtil.tryAcquire(key, time, count);
        if (!allowed) {
            String traceId = TraceIdUtil.getTraceId();
            log.warn("【{}】请求被限流 | Key: {} | 限制：{}/{}s", traceId, key, count, time);
            throw new BusinessException(message);
        }

        log.debug("限流检查通过 | Key: {} | 当前计数：{}", key, RateLimiterUtil.getCurrentCount(key));
    }

    /**
     * 解析限流 Key
     * 支持 SpEL 表达式
     */
    private String parseKey(String keyExpression, JoinPoint joinPoint) {
        if (keyExpression.isEmpty()) {
            // 默认使用 IP 地址作为 key
            return "rate_limit:" + getIpAddress();
        }

        // 解析 SpEL 表达式
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        String[] paramNames = signature.getParameterNames();

        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }

        try {
            Expression expression = PARSER.parseExpression(keyExpression);
            Object value = expression.getValue(context);
            return "rate_limit:" + (value != null ? value.toString() : "unknown");
        } catch (Exception e) {
            log.error("解析限流 Key 失败：{}", keyExpression, e);
            return "rate_limit:error";
        }
    }

    /**
     * 获取请求 IP 地址
     */
    private String getIpAddress() {
        try {
            ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                String ip = request.getHeader("x-forwarded-for");
                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getRemoteAddr();
                }
                if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
                    ip = InetAddress.getLocalHost().getHostAddress();
                }
                return ip;
            }
        } catch (UnknownHostException e) {
            log.error("获取 IP 地址失败：{}", e.getMessage());
        }
        return "unknown";
    }

}
