package com.izouma.nineth.aspect;

import com.izouma.nineth.annotations.RedisLock;
import com.izouma.nineth.exception.BusinessException;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
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 final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    private final RedissonClient redissonClient;

    public RedisLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

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

    @Around(value = "redisLockPointCut() && @annotation(redisLock)")
    public Object 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 ignored) {
        }
        log.info("enter redisLock aspect key: {}", key);
        RLock lock = redissonClient.getLock(key);
        if (redisLock.behavior() == RedisLock.Behavior.WAIT) {
            lock.lock(redisLock.expire(), redisLock.unit());
            log.info("get redisLock success");
        } else {
            if (!lock.tryLock()) {
                log.info("get redisLock fail");
                throw new BusinessException("获取锁失败");
            }
            log.info("get redisLock success");
        }
        Object res = null;
        try {
            res = joinPoint.proceed();
            try {
                lock.unlock();
            } catch (Exception ignored) {
            }
        } catch (Throwable e) {
            try {
                lock.unlock();
            } catch (Exception ignored) {
            }
            if (e instanceof BusinessException) {
                throw (BusinessException) e;
            }
            throw new RuntimeException(e);
        }
        return res;
    }
}