package com.fizz.business.utils;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
public class RedisLockUtil {
    private static final Logger log = LoggerFactory.getLogger(RedisLockUtil.class);
    public static final RedisScript<Long> UNLOCK_SCRIPT = new DefaultRedisScript("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class);
    private static final String UNLOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    private static RedisTemplate<String, String> REDIS_TEMPLATE;

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
        REDIS_TEMPLATE = redisTemplate;
    }

    private RedisLockUtil() {
    }

    public static boolean setIfAbsent(@NotNull String key, @NotNull String value, int seconds) {
        Boolean result = REDIS_TEMPLATE.opsForValue().setIfAbsent(key, value, (long)seconds, TimeUnit.SECONDS);
        return result != null && result;
    }

    public static boolean setIfPresent(@NotNull String key, @NotNull String value, int seconds) {
        Boolean result = REDIS_TEMPLATE.opsForValue().setIfPresent(key, value, (long)seconds, TimeUnit.SECONDS);
        return result != null && result;
    }

    public static void delLock(@NotNull String key, @NotNull String value) {
        REDIS_TEMPLATE.execute(UNLOCK_SCRIPT, Collections.singletonList(key), new Object[]{value});
    }

    public static <V> Pair<Boolean, V> lockHandle(@NotNull String key, @NotNull Callable<V> callable, @NotNull Function<Exception, Pair<Boolean, V>> exceptionHandle) {
        String threadName = Thread.currentThread().getName();
        String msg = "RedisLockUtil " + threadName + " {}:" + key;
        String value = IdUtil.simpleUUID();
        boolean success = setIfAbsent(key, value, 6000);
        if (!success) {
            log.debug(msg, "锁获取失败");
            return new Pair(false, (Object)null);
        } else {
            log.debug(msg, "锁获取成功");

            Pair var8;
            try {
                V v = callable.call();
                log.debug(msg, "执行完成");
                var8 = new Pair(true, v);
                return var8;
            } catch (Exception var12) {
                log.error(StrUtil.format(msg, new Object[]{"成功获取锁但执行失败"}), var12);
                var8 = (Pair)exceptionHandle.apply(var12);
            } finally {
                delLock(key, value);
            }

            return var8;
        }
    }

    public static <V> Pair<Boolean, V> lockHandle(@NotNull String key, @NotNull Callable<V> callable) {
        return lockHandle(key, callable, (e) -> {
            throw new RuntimeException(key + " 成功获取锁但执行失败:" + e.getMessage());
        });
    }
}
