package com.ruoyi.common.core.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * JDK 1.8 兼容版分布式锁工具类（基于 Redis）
 */
@Component
public class DistributedLockUtil {
    @Autowired
    private RedisCache redisCache;

    // 锁默认配置
    private static final long DEFAULT_EXPIRE_SECONDS = 30;
    private static final long DEFAULT_ACQUIRE_TIMEOUT_SECONDS = 5;
    private static final long RENEW_INTERVAL_SECONDS = DEFAULT_EXPIRE_SECONDS / 3;

    // 续期线程池
    private final ScheduledExecutorService renewExecutor = Executors.newScheduledThreadPool(
            Runtime.getRuntime().availableProcessors(),
            new ThreadFactory() {
                private final AtomicBoolean init = new AtomicBoolean(false);

                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "distributed-lock-renewer");
                    thread.setDaemon(true);
                    if (init.compareAndSet(false, true)) {
                        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                            @Override
                            public void run() {
                                renewExecutor.shutdown();
                            }
                        }));
                    }
                    return thread;
                }
            }
    );

    // 释放锁 Lua 脚本（原子操作）
    private static final String RELEASE_LOCK_LUA_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "    return redis.call('del', KEYS[1]) " +
                    "else " +
                    "    return 0 " +
                    "end";
    private final DefaultRedisScript<Long> releaseScript = new DefaultRedisScript<>();

    // 初始化 Lua 脚本
    {
        releaseScript.setScriptText(RELEASE_LOCK_LUA_SCRIPT);
        releaseScript.setResultType(Long.class);
    }

    /**
     * 获取锁（带自动续期）
     */
    public String acquireLockWithRenewal(String lockKey) {
        return acquireLockWithRenewal(lockKey, DEFAULT_EXPIRE_SECONDS, DEFAULT_ACQUIRE_TIMEOUT_SECONDS);
    }

    /**
     * 自定义参数获取锁
     */
    public String acquireLockWithRenewal(String lockKey, long expireSeconds, long acquireTimeoutSeconds) {
        String identifier = UUID.randomUUID().toString();
        long endTime = System.currentTimeMillis() + acquireTimeoutSeconds * 1000;

        while (System.currentTimeMillis() < endTime) {
            boolean locked = tryLockOnce(lockKey, identifier, expireSeconds);
            if (locked) {
                startRenewal(lockKey, identifier, expireSeconds);
                return identifier;
            }

            // 指数退避重试
            try {
                long sleepMs = calculateBackoffSleep(System.currentTimeMillis() - (endTime - acquireTimeoutSeconds * 1000));
                Thread.sleep(sleepMs);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        return null;
    }

    /**
     * 原子释放锁
     */
    public boolean releaseLockSafely(String lockKey, String identifier) {
        if (identifier == null) {
            return false;
        }
        Long result = (Long) redisCache.redisTemplate.execute(
                releaseScript,
                Collections.singletonList(lockKey),
                identifier
        );
        return result != null && result > 0;
    }

    /**
     * 尝试获取一次锁
     */
    private boolean tryLockOnce(String lockKey, String identifier, long expireSeconds) {
        try {
            if (!redisCache.hasKey(lockKey)) {
                redisCache.setCacheObject(lockKey, identifier, (int) expireSeconds, TimeUnit.SECONDS);
                String storedId = redisCache.getCacheObject(lockKey);
                return identifier.equals(storedId);
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 启动锁自动续期
     */
    private void startRenewal(final String lockKey, final String identifier, final long expireSeconds) {
        final AtomicReference<ScheduledFuture<?>> futureRef = new AtomicReference<>();
        Runnable renewalTask = new Runnable() {
            @Override
            public void run() {
                try {
                    String storedId = redisCache.getCacheObject(lockKey);
                    if (identifier.equals(storedId)) {
                        redisCache.expire(lockKey, expireSeconds);
                    } else {
                        cancelFuture();
                    }
                } catch (Exception e) {
                    cancelFuture();
                }
            }

            private void cancelFuture() {
                ScheduledFuture<?> future = futureRef.get();
                if (future != null && !future.isCancelled()) {
                    future.cancel(false);
                }
            }
        };

        ScheduledFuture<?> future = renewExecutor.scheduleAtFixedRate(
                renewalTask,
                RENEW_INTERVAL_SECONDS,
                RENEW_INTERVAL_SECONDS,
                TimeUnit.SECONDS
        );
        futureRef.set(future);
    }

    /**
     * 指数退避算法（计算重试间隔）
     */
    private long calculateBackoffSleep(long elapsedMs) {
        int retryCount = (int) (elapsedMs / 100);
        long sleepMs = 100L * (1 << Math.min(retryCount, 10));
        return Math.min(sleepMs, 1000);
    }

    /**
     * 自动释放锁工具（支持 try-with-resources）
     */
    public static class AutoReleaseLock implements AutoCloseable {
        private final DistributedLockUtil lockUtil;
        private final String lockKey;
        private final String identifier;

        public AutoReleaseLock(DistributedLockUtil lockUtil, String lockKey, String identifier) {
            this.lockUtil = lockUtil;
            this.lockKey = lockKey;
            this.identifier = identifier;
        }

        public boolean isLocked() {
            return identifier != null;
        }

        @Override
        public void close() {
            if (isLocked()) {
                lockUtil.releaseLockSafely(lockKey, identifier);
            }
        }
    }

    /**
     * 简化锁使用
     */
    public AutoReleaseLock tryLock(String lockKey) {
        String identifier = acquireLockWithRenewal(lockKey);
        return new AutoReleaseLock(this, lockKey, identifier);
    }

    /**
     * 锁+超时时间
     * @param lockKey
     * @param timeout
     * @param unit
     * @return
     */
    public AutoReleaseLock tryLock(String lockKey, long timeout, TimeUnit unit) {
        // 1. 校验参数合法性（防御性编程）
        if (lockKey == null || lockKey.trim().isEmpty()) {
            throw new IllegalArgumentException("锁key不能为空");
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("超时时间不能为负数");
        }
        if (unit == null) {
            throw new IllegalArgumentException("时间单位不能为空");
        }

        // 2. 带超时获取锁（核心逻辑，需实现 acquireLockWithTimeout 方法）
        String identifier = acquireLockWithTimeout(lockKey, timeout, unit);

        // 3. 返回自动释放锁（复用原有 AutoReleaseLock，无需修改）
        return new AutoReleaseLock(this, lockKey, identifier);
    }

    /**
     * 带超时时间获取锁（支持自定义超时单位）
     * @param lockKey 锁键
     * @param timeout 获取锁的超时时间
     * @param unit 时间单位
     * @return 锁标识（获取失败返回null）
     */
    public String acquireLockWithTimeout(String lockKey, long timeout, TimeUnit unit) {
        // 转换超时时间为秒（向上取整避免精度丢失）
        long acquireTimeoutSeconds = (long) Math.ceil((double) unit.toMillis(timeout) / 1000);
        // 使用默认的锁过期时间（30秒），也可根据需要改为让调用方传入
        return acquireLockWithRenewal(lockKey, DEFAULT_EXPIRE_SECONDS, acquireTimeoutSeconds);
    }
}