package com.izouma.nineth.aspect;

import com.izouma.nineth.annotations.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
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 java.lang.reflect.Method;
import java.util.Optional;

@Aspect
@Component
@Slf4j
public class RedisLockAspect {

    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Pointcut("@annotation(com.izouma.nineth.annotations.RedisLock)")
    public void redisLockPointCut() {
    }

    @Around(value = "redisLockPointCut() && @annotation(redisLock)")
    public void redisLock(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new StandardEvaluationContext(joinPoint.getSignature());
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        String key = redisLock.value();
        try {
            key = Optional.ofNullable(parser.parseExpression(redisLock.value()).getValue(context))
                    .map(Object::toString)
                    .orElse("default");
        } catch (Exception e) {
        }
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(key);
        Boolean success = ops.setIfAbsent(1, redisLock.expire(), redisLock.unit());
        if (Boolean.TRUE.equals(success)) {
            try {
                joinPoint.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
            }
            redisTemplate.delete(key);
        }
    }

}