package com.adx.tracking.storage;

import com.adx.tracking.leader.Lease;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.security.SecureRandom;
import java.time.Duration;
import java.util.Collections;
import java.util.HexFormat;

/**
 * 对应 Go internal/storage/redis_lock.go。
 * 基于 Redis SETNX + Lua 脚本 实现分布式锁。
 *
 * 续约脚本（renewLockScript）：仅当 key 的 value == token 时才 PEXPIRE。
 * 释放脚本（releaseLockScript）：仅当 key 的 value == token 时才 DEL。
 */
public class RedisLock {

    private static final String RENEW_SCRIPT =
            "if redis.call('GET', KEYS[1]) == ARGV[1] then\n" +
            "  return redis.call('PEXPIRE', KEYS[1], ARGV[2])\n" +
            "end\n" +
            "return 0";

    private static final String RELEASE_SCRIPT =
            "if redis.call('GET', KEYS[1]) == ARGV[1] then\n" +
            "  return redis.call('DEL', KEYS[1])\n" +
            "end\n" +
            "return 0";

    private final StringRedisTemplate redis;

    public RedisLock(StringRedisTemplate redis) {
        this.redis = redis;
    }

    /**
     * 尝试获取锁。对应 Go Acquire()。
     * 返回 null 表示未获取到锁，非 null 表示获取成功的 Lease。
     */
    public Lease tryAcquire(String key, Duration ttl) {
        if (key == null || key.isBlank()) throw new IllegalArgumentException("lock key is required");
        if (ttl == null || ttl.isZero() || ttl.isNegative()) throw new IllegalArgumentException("lock ttl must be positive");

        String token = newToken();
        Boolean acquired = redis.opsForValue().setIfAbsent(key, token, ttl);
        if (Boolean.TRUE.equals(acquired)) {
            return new RedisLease(redis, key, token);
        }
        return null;
    }

    private static String newToken() {
        byte[] bytes = new byte[16];
        new SecureRandom().nextBytes(bytes);
        return HexFormat.of().formatHex(bytes);
    }

    // ─── 内部 Lease 实现 ─────────────────────────────────────────────────────

    public static class RedisLease implements Lease {
        private final StringRedisTemplate redis;
        private final String key;
        private final String token;

        private static final DefaultRedisScript<Long> RENEW_REDIS_SCRIPT;
        private static final DefaultRedisScript<Long> RELEASE_REDIS_SCRIPT;

        static {
            RENEW_REDIS_SCRIPT = new DefaultRedisScript<>();
            RENEW_REDIS_SCRIPT.setScriptText(RedisLock.RENEW_SCRIPT);
            RENEW_REDIS_SCRIPT.setResultType(Long.class);

            RELEASE_REDIS_SCRIPT = new DefaultRedisScript<>();
            RELEASE_REDIS_SCRIPT.setScriptText(RedisLock.RELEASE_SCRIPT);
            RELEASE_REDIS_SCRIPT.setResultType(Long.class);
        }

        RedisLease(StringRedisTemplate redis, String key, String token) {
            this.redis = redis;
            this.key   = key;
            this.token = token;
        }

        /**
         * 续约：对应 Go Renew()。返回 true 表示续约成功（锁仍属于我们）。
         */
        @Override
        public boolean renew(Duration ttl) {
            Long result = redis.execute(RENEW_REDIS_SCRIPT,
                    Collections.singletonList(key),
                    token,
                    String.valueOf(ttl.toMillis()));
            return Long.valueOf(1).equals(result);
        }

        /**
         * 释放：对应 Go Release()。
         */
        @Override
        public void release() {
            redis.execute(RELEASE_REDIS_SCRIPT,
                    Collections.singletonList(key),
                    token);
        }
    }
}
