package tech.easyflow.common.cache;

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

import java.time.Duration;
import java.util.Collections;
import java.util.UUID;
import java.util.function.Supplier;

/**
 * Redis 分布式锁执行器。
 */
@Component
public class RedisLockExecutor {

    private static final Logger log = LoggerFactory.getLogger(RedisLockExecutor.class);

    private static final long RETRY_INTERVAL_MILLIS = 50L;

    private static final DefaultRedisScript<Long> RELEASE_LOCK_SCRIPT;
    private static final DefaultRedisScript<Long> RENEW_LOCK_SCRIPT;

    static {
        RELEASE_LOCK_SCRIPT = new DefaultRedisScript<>();
        RELEASE_LOCK_SCRIPT.setScriptText(
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del', KEYS[1]) " +
                        "else return 0 end"
        );
        RELEASE_LOCK_SCRIPT.setResultType(Long.class);
        RENEW_LOCK_SCRIPT = new DefaultRedisScript<>();
        RENEW_LOCK_SCRIPT.setScriptText(
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('pexpire', KEYS[1], ARGV[2]) " +
                        "else return 0 end"
        );
        RENEW_LOCK_SCRIPT.setResultType(Long.class);
    }

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 在分布式锁保护下执行无返回任务。
     *
     * @param lockKey 锁 key
     * @param waitTimeout 等待锁的最大时间
     * @param leaseTimeout 锁租约时间
     * @param task 业务任务
     */
    public void executeWithLock(String lockKey, Duration waitTimeout, Duration leaseTimeout, Runnable task) {
        executeWithLock(lockKey, waitTimeout, leaseTimeout, () -> {
            task.run();
            return null;
        });
    }

    /**
     * 在分布式锁保护下执行有返回任务。
     *
     * @param lockKey 锁 key
     * @param waitTimeout 等待锁的最大时间
     * @param leaseTimeout 锁租约时间
     * @param task 业务任务
     * @param <T> 返回类型
     * @return 任务返回值
     */
    public <T> T executeWithLock(String lockKey, Duration waitTimeout, Duration leaseTimeout, Supplier<T> task) {
        LockHandle handle = acquire(lockKey, waitTimeout, leaseTimeout);
        try {
            return task.get();
        } finally {
            handle.release();
        }
    }

    /**
     * 获取显式释放的分布式锁句柄。
     *
     * <p>长连接、SSE 或异步任务不能使用 callback 型锁，否则 callback 返回后锁会被提前释放。
     * 该方法返回 owner token 绑定的句柄，由调用方在运行完成、失败或取消时显式释放。</p>
     *
     * @param lockKey 锁 key
     * @param waitTimeout 等待时间
     * @param leaseTimeout 租约时间
     * @return 锁句柄
     */
    public LockHandle acquire(String lockKey, Duration waitTimeout, Duration leaseTimeout) {
        LockHandle handle = tryAcquire(lockKey, waitTimeout, leaseTimeout);
        if (handle == null) {
            throw new IllegalStateException("获取分布式锁失败，请稍后重试，lockKey=" + lockKey);
        }
        return handle;
    }

    /**
     * 尝试获取显式释放的分布式锁句柄。
     *
     * <p>返回 {@code null} 表示锁当前被其他节点持有。Redis 访问失败或等待过程被中断仍会抛出异常，
     * 调用方可据此区分“正常跳过”和“基础设施异常”。</p>
     *
     * @param lockKey 锁 key
     * @param waitTimeout 等待时间
     * @param leaseTimeout 租约时间
     * @return 获取成功时返回锁句柄，否则返回 null
     */
    public LockHandle tryAcquire(String lockKey, Duration waitTimeout, Duration leaseTimeout) {
        String lockValue = UUID.randomUUID().toString();
        boolean acquired = false;
        long deadline = System.nanoTime() + waitTimeout.toNanos();
        try {
            do {
                Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, leaseTimeout);
                if (Boolean.TRUE.equals(success)) {
                    acquired = true;
                    break;
                }
                if (System.nanoTime() >= deadline) {
                    break;
                }
                Thread.sleep(RETRY_INTERVAL_MILLIS);
            } while (System.nanoTime() <= deadline);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("等待分布式锁被中断，lockKey=" + lockKey, e);
        }
        if (!acquired) {
            return null;
        }
        return new LockHandle(lockKey, lockValue, leaseTimeout);
    }

    /**
     * 按 owner token 释放锁。
     *
     * @param lockKey 锁 key
     * @param lockValue owner token
     */
    public void releaseLock(String lockKey, String lockValue) {
        try {
            stringRedisTemplate.execute(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey), lockValue);
        } catch (Exception e) {
            log.warn("释放分布式锁失败，lockKey={}", lockKey, e);
        }
    }

    /**
     * 按 owner token 续期锁。
     *
     * @param lockKey 锁 key
     * @param lockValue owner token
     * @param leaseTimeout 新租约时间
     * @return 续期成功时为 true
     */
    public boolean renewLock(String lockKey, String lockValue, Duration leaseTimeout) {
        try {
            Long result = stringRedisTemplate.execute(RENEW_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    lockValue, String.valueOf(leaseTimeout.toMillis()));
            return Long.valueOf(1L).equals(result);
        } catch (Exception e) {
            log.warn("续期分布式锁失败，lockKey={}", lockKey, e);
            return false;
        }
    }

    /**
     * 显式分布式锁句柄。
     */
    public final class LockHandle implements AutoCloseable {

        private final String lockKey;
        private final String lockValue;
        private final Duration leaseTimeout;
        private volatile boolean released;

        private LockHandle(String lockKey, String lockValue, Duration leaseTimeout) {
            this.lockKey = lockKey;
            this.lockValue = lockValue;
            this.leaseTimeout = leaseTimeout;
        }

        /**
         * 续期当前锁。
         *
         * @return 续期成功时为 true
         */
        public boolean renew() {
            if (released) {
                return false;
            }
            return renewLock(lockKey, lockValue, leaseTimeout);
        }

        /**
         * 释放当前锁。
         */
        public void release() {
            if (released) {
                return;
            }
            released = true;
            releaseLock(lockKey, lockValue);
        }

        @Override
        public void close() {
            release();
        }
    }
}
