package com.red.circle.other.app.util;

import com.red.circle.component.redis.service.RedisService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 分布式锁工具类
 * 基于Redis实现的分布式锁，支持自动加锁/解锁
 *
 * @author system
 * @date 2025-01-22
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DistributedLockUtil {

    private final RedisService redisService;

    /**
     * 默认锁过期时间（秒）
     */
    private static final long DEFAULT_LOCK_EXPIRE_TIME = 10;

    /**
     * 使用分布式锁执行业务逻辑（无返回值）
     *
     * @param lockKey    锁的key
     * @param runnable   业务逻辑
     */
    public void executeWithLock(String lockKey, Runnable runnable) {
        executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, runnable, "The request is too frequent. Please try again later");
    }

    /**
     * 使用分布式锁执行业务逻辑（无返回值）
     *
     * @param lockKey    锁的key
     * @param runnable   业务逻辑
     * @param errorMsg   获取锁失败时的错误信息
     */
    public void executeWithLock(String lockKey, Runnable runnable, String errorMsg) {
        executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, runnable, errorMsg);
    }

    /**
     * 使用分布式锁执行业务逻辑（无返回值，自定义过期时间）
     *
     * @param lockKey        锁的key
     * @param expireTime     锁过期时间（秒）
     * @param runnable       业务逻辑
     * @param errorMsg       获取锁失败时的错误信息
     */
    public void executeWithLock(String lockKey, long expireTime, Runnable runnable, String errorMsg) {
        executeWithLock(lockKey, expireTime, () -> {
            runnable.run();
            return null;
        }, errorMsg);
    }

    /**
     * 使用分布式锁执行业务逻辑（有返回值）
     *
     * @param lockKey    锁的key
     * @param supplier   业务逻辑
     * @param errorMsg   获取锁失败时的错误信息
     * @param <T>        返回值类型
     * @return 业务逻辑执行结果
     */
    public <T> T executeWithLock(String lockKey, Supplier<T> supplier, String errorMsg) {
        return executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, supplier, errorMsg);
    }


  /**
   * 使用分布式锁执行业务逻辑（有返回值）
   *
   * @param lockKey    锁的key
   * @param supplier   业务逻辑
   * @param <T>        返回值类型
   * @return 业务逻辑执行结果
   */
  public <T> T executeWithLock(String lockKey, Supplier<T> supplier) {
    return executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, supplier, "The request is too frequent. Please try again later");
  }

    /**
     * 使用分布式锁执行业务逻辑（有返回值，自定义过期时间）
     *
     * @param lockKey        锁的key
     * @param expireTime     锁过期时间（秒）
     * @param supplier       业务逻辑
     * @param errorMsg       获取锁失败时的错误信息
     * @param <T>            返回值类型
     * @return 业务逻辑执行结果
     */
    public <T> T executeWithLock(String lockKey, long expireTime, Supplier<T> supplier, String errorMsg) {
        // 尝试获取锁
        boolean lockSuccess = tryLock(lockKey, expireTime);

        if (!lockSuccess) {
            log.warn("获取分布式锁失败, lockKey={}", lockKey);
            throw new RuntimeException(errorMsg);
        }

        try {
            log.debug("获取分布式锁成功, lockKey={}", lockKey);
            return supplier.get();
        } finally {
            // 释放锁
            unlock(lockKey);
            log.debug("释放分布式锁, lockKey={}", lockKey);
        }
    }

    /**
     * 尝试获取锁
     *
     * @param lockKey    锁的key
     * @param expireTime 锁过期时间（秒）
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, long expireTime) {
        return redisService.setIfAbsent(
                lockKey,
                String.valueOf(System.currentTimeMillis()),
                expireTime,
                TimeUnit.SECONDS
        );
    }

    /**
     * 释放锁
     *
     * @param lockKey 锁的key
     */
    public void unlock(String lockKey) {
        redisService.delete(lockKey);
    }

    /**
     * 构建锁的key
     *
     * @param prefix 前缀
     * @param keys   key组成部分
     * @return 完整的锁key
     */
    public static String buildLockKey(String prefix, Object... keys) {
        StringBuilder sb = new StringBuilder(prefix);
        for (Object key : keys) {
            sb.append(":").append(key);
        }
        return sb.toString();
    }
}