package com.elab.redis.interceptor.impl;

import com.elab.redis.annotation.CacheLoopSubmit;
import com.elab.redis.interceptor.ICacheProcessService;
import com.elab.redis.utils.CacheParseUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 防重复提交
 *
 * @author ： liukx
 * @time ： 2020/7/10 - 10:58
 */
@Component
public class CacheLoopProcessImpl implements ICacheProcessService {

    @Autowired
    private RedissonClient client;

    @Value("${spring.application.name}")
    private String applicationName;

    private Logger logger = LoggerFactory.getLogger(CacheLoopProcessImpl.class);

    @Override
    public boolean subscribe(Method method) {
        return CacheParseUtil.isContainAnnotation(method, CacheLoopSubmit.class);
    }

    @Override
    public Object invokeWithinTransaction(ReflectiveMethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        CacheLoopSubmit cacheLoopSubmit = AnnotationUtils.getAnnotation(method, CacheLoopSubmit.class);
        String[] unionKey = cacheLoopSubmit.unionKey();
        int timeOut = cacheLoopSubmit.timeOut();
        String clazzName = method.getDeclaringClass().getName();
        Object[] arguments = invocation.getArguments();

        String cacheKey = generateUnionKey(unionKey, arguments);
        String lockKey = applicationName + "|" + clazzName + "." + method.getName();

        RMap<Object, Object> map = client.getMap(lockKey);
        RLock lock = map.getLock(cacheKey);
        try {
            if (lock.tryLock(timeOut, TimeUnit.SECONDS)) {
                logger.debug("当前线程抢占到锁");
                Object proceed = invocation.proceed();
                return proceed;
            }
        } catch (InterruptedException e) {
            logger.warn("尝试获取锁超时", e);
        } finally {
            lock.unlock();
        }
        return null;
    }

    /**
     * 通过key定义的集合到对应的参数里面生成对应的唯一键
     * 非键值对类型: [0]
     * 键值对类型: [0]{xxx}
     *
     * @param unionKey  唯一key集合
     * @param arguments 参数集合
     * @return
     * @throws Exception
     */
    private String generateUnionKey(String[] unionKey, Object[] arguments) throws Exception {
        StringBuffer unionValue = new StringBuffer();
        for (int i = 0; i < unionKey.length; i++) {
            String key = unionKey[i];
            if (key.startsWith("[")) {
                String paramIndex = getParamIndex(key, "[", "]");
                Integer index = Integer.valueOf(paramIndex);
                Object value = null;

                if (key.indexOf("{") > -1) {
                    String property = getParamIndex(key, "{", "}");
                    value = BeanUtils.getProperty(arguments[index], property);
                } else {
                    value = arguments[index];
                }

                if (value == null) {
                    unionValue.append("_");
                } else {
                    unionValue.append(value + "_");
                }
            }
        }
        return unionValue.toString();
    }

    public static String getParamIndex(String str, String startSymbol, String endSymbol) {
        int start = str.indexOf(startSymbol) + 1;
        int end = str.indexOf(endSymbol, start);
        String content = str.substring(start, end);
        return content;
    }

}
