package com.fdkankan.redis.util;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;

import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class RedisLockUtil {

    private ConcurrentHashMap<String, String> THREADID_LOCKVAL_MAP = new ConcurrentHashMap<>();

    @Autowired
    private RedisTemplate redisTemplate;

    private static final String UNLOCK_LUA =
            "if redis.call('get',KEYS[1]) == ARGV[1] then\n" +
            "    return redis.call('del',KEYS[1])\n" +
            "else\n" +
            "    return 0\n" +
            "end";

    /**
     * 加锁，自旋重试三次
     * 默认以线程id作为锁的值
     *
     * @param lockKey 锁key
     * @param expireTime 锁过期时间 单位 毫秒
     * @return
     */
    public boolean lock(String lockKey, int expireTime) {
        return this.lock(lockKey, null, expireTime);
    }

    /**
     * 加锁，自旋重试三次
     * @param lockKey 锁key
     * @param lockVal 锁value
     * @param expireTime 锁过期时间 单位 毫秒
     * @return
     */
    public boolean lock(String lockKey, String lockVal, int expireTime) {
        boolean locked = false;
        int tryCount = 3;
        String threadId = null;
        String uuid = null;
        if(lockVal == null){
            threadId = String.valueOf(Thread.currentThread().getId());
            uuid = UUID.randomUUID().toString();
            lockVal = uuid + ":" + threadId;
            log.info("加锁lockval：{}",lockVal);
        }
        while (tryCount > 0) {
            locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, expireTime, TimeUnit.SECONDS);
            if(locked){
                if(threadId != null){
                    THREADID_LOCKVAL_MAP.put(threadId, lockVal);
                }
                return locked;
            }
            tryCount--;
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return locked;
    }

    /**
     * 非原子解锁，可能解别人锁，不安全
     *
     * @return
     */
    public boolean unlock(String lockKey) {
        return this.unlock(lockKey, null);
    }

    /**
     * 非原子解锁，可能解别人锁，不安全
     *
     * @return
     */
    public boolean unlock(String lockKey, String lockVal) {
        if(lockVal == null){
            String threadId = String.valueOf(Thread.currentThread().getId());
            lockVal = THREADID_LOCKVAL_MAP.get(threadId);
        }
        if (StrUtil.isEmpty(lockKey) || Objects.isNull(lockVal))
            return false;
        boolean releaseLock = false;
        String val = (String) redisTemplate.opsForValue().get(lockKey);
        if (lockVal.equals(val)) {
            releaseLock = redisTemplate.delete(lockKey);
        }
        return releaseLock;
    }

    /**
     * 使用lua脚本解锁，不会解除别人锁
     *
     * @param
     * @return
     */
    public boolean unlockLua(String lockKey) {
        return this.unlockLua(lockKey, null);
    }

    /**
     * 使用lua脚本解锁，不会解除别人锁
     *
     * @param
     * @return
     */
    public boolean unlockLua(String lockKey, String lockVal) {
        if(lockVal == null){
            String threadId = String.valueOf(Thread.currentThread().getId());
            lockVal = THREADID_LOCKVAL_MAP.get(threadId);
        }
        log.info("解锁lockval：{}",lockVal);
        if (StrUtil.isEmpty(lockKey) || Objects.isNull(lockVal))
            return false;
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript();
        redisScript.setScriptText(UNLOCK_LUA);
        redisScript.setResultType(Long.class);

        Object result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
        return result.equals(Long.valueOf(1));
    }





}
