package cn.iocoder.yudao.framework.redis.helper;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class RedisDistributedLock extends AbstractDistributedLock {

    @Resource
    private RedisHelper redisHelper;

    private ThreadLocal<String> lockFlag = new ThreadLocal<String>();

    public static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }


    public RedisDistributedLock() {
        super();
    }

    @Override
    public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
        boolean result = setRedis(key, expire);
        // 若是获取锁失败，按照传入的重试次数进行重试
        while ((!result) && retryTimes-- > 0) {
            try {
                log.debug("lock failed, retrying..." + retryTimes);
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                return false;
            }
            result = setRedis(key, expire);
        }
        return result;
    }

    /**
     *
     * @param key
     * @param expire MILLISECONDS
     * @return
     */
    private boolean setRedis(final String key, final long expire) {
        try {
            String uuid = UUID.randomUUID().toString();
            lockFlag.set(uuid);
            return redisHelper.setIfAbsent(key,uuid,expire,TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.info("redis lock error.", e);
        }
        return false;
    }


    @Override
    public boolean releaseLock(String key) {
        // 释放锁的时候，有可能由于持锁以后方法执行时间大于锁的有效期，此时有可能已经被另一个线程持有锁，因此不能直接删除
        try {
            DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<Boolean>(UNLOCK_LUA,Boolean.class);
            return redisHelper.execute(defaultRedisScript, Collections.singletonList(key),lockFlag.get());
        } catch (Exception e) {
            log.error("release lock occured an exception", e);
        } finally {
            // 清除掉ThreadLocal中的数据，避免内存溢出
            lockFlag.remove();
        }
        return false;
    }
}
