package quant.rich.emoney.component;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.*;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import quant.rich.emoney.util.CallerLockUtil;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.concurrent.locks.ReentrantLock;

@Aspect
@Component
public class CallerLockAspect {

    private final SpelExpressionParser parser = new SpelExpressionParser();

    @Around("@annotation(quant.rich.emoney.component.CallerLockAspect.LockByCaller)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        LockByCaller annotation = method.getAnnotation(LockByCaller.class);

        // 获取 SpEL key（如果有）
        Object[] args = pjp.getArgs();
        String[] paramNames = signature.getParameterNames();
        Object[] extraKey = new Object[0];

        if (!annotation.key().isEmpty()) {
            EvaluationContext context = new StandardEvaluationContext();
            for (int i = 0; i < paramNames.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }

            Expression expression = parser.parseExpression(annotation.key());
            Object value = expression.getValue(context);
            extraKey = (value == null) ? new Object[0] : new Object[]{value};
        }

        // 复用工具类的锁逻辑
        ReentrantLock lock = CallerLockUtil.acquireLock(extraKey);

        lock.lock();
        try {
            return pjp.proceed();
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 在方法上添加此注解，可针对调用方加锁，即：<br>
     * 调用方法为 A 的，多次从 A 调用则加锁，从 B 调用时不受影响<br>
     * 需要开启 AOP：在任意配置类上增加注解：<i><code>@EnableAspectJAutoProxy</code></i>
     * @see CallerLockAspect
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public static @interface LockByCaller {
        /**
         * 可选参数，用于 SpEL 表达式获取 key
         * 例如：<ul>
         * <li>@LockByCaller(key = "#userId")</li>
         * <li>@LockByCaller(key = "#userId + ':' + #userName")</li>
         * </ul>
         * 当不指定时，不校验参数，单纯校验 Caller
         */
        String key() default "";
    }
}