package com.jy.framework.utils;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.jy.framework.config.JyConfig;
import com.jy.framework.exception.ServiceException;
import com.jy.framework.redis.RedisCache;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 锁工具类
 */
@SuppressWarnings("unused")
public class LockUtil {

    private static final RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
    private static final JyConfig JY_CONFIG = SpringUtil.getBean(JyConfig.class);
    private static final String PREFIX = JY_CONFIG.getProjectName() + StringPool.COLON + "redissonLock" + StringPool.COLON;

    /**
     * 乐观锁
     *
     * @param maxRetryCount   最大重试次数
     * @param millis          再次执行等待时长
     * @param booleanSupplier 执行方法
     */
    public static void optimisticLock(int maxRetryCount, long millis, BooleanSupplier booleanSupplier) {

        int retryCount = 0;

        while (retryCount < maxRetryCount) {
            if (booleanSupplier.getAsBoolean()) {
                return;
            }
            retryCount++;
            ThreadUtil.sleep(millis);
        }

        throw new ServiceException(HttpStatus.HTTP_UNAVAILABLE, "系统繁忙，请稍候再试");
    }

    /**
     * 乐观锁
     * <p>默认最多执行3次，每次失败等待300毫秒</p>
     *
     * @param booleanSupplier 执行方法
     */
    public static void optimisticLock(BooleanSupplier booleanSupplier) {
        optimisticLock(3, 300, booleanSupplier);
    }

    /**
     * 加锁获取缓存，防止缓存击穿
     *
     * @param cacheKey 缓存key
     * @param supplier 执行获取缓存方法
     * @param <T>      缓存值
     * @return 缓存值
     */
    public static <T> T cacheLock(String cacheKey, Supplier<T> supplier) {
        T change = RedisCache.get(cacheKey);
        if (ObjectUtil.isNull(change)) {
            change = lock(cacheKey, () -> {
                T tempChange = RedisCache.get(cacheKey);
                if (ObjectUtil.isNull(tempChange)) {
                    tempChange = supplier.get();
                    RedisCache.set(cacheKey, tempChange, Duration.ofMinutes(30));
                }
                return tempChange;
            });
        }
        return change;
    }

    /**
     * 加锁
     *
     * @param lockKey  key
     * @param runnable 执行逻辑
     */
    public static void lock(String lockKey, Runnable runnable) {
        RLock lock = redissonClient.getLock(PREFIX + lockKey);
        try {
            lock.lock();
            runnable.run();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 加锁
     *
     * @param lockKey  key
     * @param supplier 执行逻辑
     * @param <T>      执行结果类型
     * @return 执行结果
     */
    public static <T> T lock(String lockKey, Supplier<T> supplier) {
        RLock lock = redissonClient.getLock(PREFIX + lockKey);
        try {
            lock.lock();
            return supplier.get();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 尝试加锁
     *
     * @param lockKey  key
     * @param consumer 执行逻辑
     */
    public static void tryLock(String lockKey, Consumer<Boolean> consumer) {
        RLock lock = redissonClient.getLock(PREFIX + lockKey);
        boolean tryLock = false;
        try {
            tryLock = lock.tryLock();
            consumer.accept(tryLock);
        } finally {
            if (tryLock) {
                lock.unlock();
            }
        }
    }

    /**
     * 尝试加锁
     *
     * @param lockKey  key
     * @param function 执行逻辑
     * @param <T>      执行结果类型
     * @return 执行结果
     */
    public static <T> T tryLock(String lockKey, Function<Boolean, T> function) {
        RLock lock = redissonClient.getLock(PREFIX + lockKey);
        boolean tryLock = false;
        try {
            tryLock = lock.tryLock();
            return function.apply(tryLock);
        } finally {
            if (tryLock) {
                lock.unlock();
            }
        }
    }

    /**
     * 尝试加锁
     *
     * @param lockKey  key
     * @param consumer 执行逻辑
     * @param time     等待时间（秒）
     */
    public static void tryLock(String lockKey, Consumer<Boolean> consumer, long time) {
        tryLock(lockKey, consumer, time, TimeUnit.SECONDS);
    }

    /**
     * 尝试加锁
     *
     * @param lockKey  key
     * @param function 执行逻辑
     * @param time     等待时间（秒）
     * @param <T>      执行结果类型
     * @return 执行结果
     */
    public static <T> T tryLock(String lockKey, Function<Boolean, T> function, long time) {
        return tryLock(lockKey, function, time, TimeUnit.SECONDS);
    }

    /**
     * 尝试加锁
     *
     * @param lockKey  key
     * @param consumer 执行逻辑
     * @param time     等待时间
     * @param unit     时间单位
     */
    public static void tryLock(String lockKey, Consumer<Boolean> consumer, long time, TimeUnit unit) {
        RLock lock = redissonClient.getLock(PREFIX + lockKey);
        boolean tryLock = false;
        try {
            tryLock = lock.tryLock(time, unit);
            consumer.accept(tryLock);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (tryLock) {
                lock.unlock();
            }
        }
    }

    /**
     * 尝试加锁
     *
     * @param lockKey  key
     * @param function 执行逻辑
     * @param time     等待时间
     * @param unit     时间单位
     * @param <T>      执行结果类型
     * @return 执行结果
     */
    public static <T> T tryLock(String lockKey, Function<Boolean, T> function, long time, TimeUnit unit) {
        RLock lock = redissonClient.getLock(PREFIX + lockKey);
        boolean tryLock = false;
        try {
            tryLock = lock.tryLock(time, unit);
            return function.apply(tryLock);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (tryLock) {
                lock.unlock();
            }
        }
    }

}
