package com.elab.redis.interceptor.impl;

import com.elab.core.utils.StringUtils;
import com.elab.redis.annotation.CacheLoopSubmit;
import com.elab.redis.config.ElabRedisProperties;
import com.elab.redis.consts.CacheConstants;
import com.elab.redis.exceptions.ReSubmitException;
import com.elab.redis.interceptor.ICacheProcessService;
import com.elab.redis.utils.CacheParseUtil;
import com.elab.redis.utils.RedisLockUtils;
import org.redisson.api.RLock;
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.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 {

    private String prefix = "loopSubmit";

    @Autowired
    private ElabRedisProperties redisProperties;

    @Autowired
    private RedissonClient client;

    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();
        String clazzName = method.getDeclaringClass().getName();
        Object[] arguments = invocation.getArguments();

        String cacheKey = CacheParseUtil.generateUnionKey(unionKey, arguments);

        String cacheName = cacheLoopSubmit.cacheName();

        if (StringUtils.isEmpty(cacheKey)) {
            cacheName = getPrefix() + CacheConstants.CACHE_SEPARATOR + prefix + CacheConstants.CACHE_SEPARATOR + clazzName + CacheConstants.CACHE_SEPARATOR + method.getName();
        } else {
            cacheName = getPrefix() + CacheConstants.CACHE_SEPARATOR + cacheName;
        }

        cacheName = cacheName + CacheConstants.CACHE_SEPARATOR + cacheKey;

        RLock lockObject = client.getLock(cacheName);

        try {
            if (tryLock(lockObject, cacheLoopSubmit)) {
                Object proceed = invocation.proceed();
                return proceed;
            } else {
                String errorMsg = cacheLoopSubmit.errorMsg();
                logger.warn("触发重复提交条件:" + cacheName);
                throw new ReSubmitException(errorMsg);
            }
        } catch (InterruptedException e) {
            logger.warn("尝试获取锁超时", e);
        } finally {
            RedisLockUtils.releaseLock(lockObject);
        }
        return null;
    }

    /**
     * 尝试获取锁
     *
     * @param lock            锁对象
     * @param cacheLoopSubmit 注解
     * @return
     */
    private boolean tryLock(RLock lock, CacheLoopSubmit cacheLoopSubmit) {
        try {
            if (cacheLoopSubmit.isWaitComplete()) {
                return lock.tryLock(cacheLoopSubmit.timeOut(), TimeUnit.MILLISECONDS);
            } else {
                return lock.tryLock();
            }
        } catch (Exception e) {
            logger.warn("获取锁异常:" + e.getMessage());
            return false;
        }
    }

    private String getPrefix() {
        if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getPrefixWith())) {
            return redisProperties.getPrefixWith();
        }
        return "";
    }


}
