package tech.easyflow.common.cache;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;

import java.lang.reflect.Field;
import java.time.Duration;
import java.util.List;

/**
 * {@link RedisLockExecutor} 回归测试。
 */
public class RedisLockExecutorTest {

    /**
     * 验证锁被占用时返回 null，便于调度任务跳过本轮执行。
     *
     * @throws Exception 反射注入异常
     */
    @Test
    public void tryAcquireShouldReturnNullWhenLockIsHeld() throws Exception {
        StringRedisTemplate redisTemplate = Mockito.mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations = mockValueOperations(false);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);

        RedisLockExecutor executor = new RedisLockExecutor();
        setRedisTemplate(executor, redisTemplate);

        RedisLockExecutor.LockHandle handle = executor.tryAcquire(
            "easyflow:test:lock",
            Duration.ZERO,
            Duration.ofSeconds(30)
        );

        Assert.assertNull(handle);
        Mockito.verify(valueOperations).setIfAbsent(
            ArgumentMatchers.eq("easyflow:test:lock"),
            ArgumentMatchers.anyString(),
            ArgumentMatchers.eq(Duration.ofSeconds(30))
        );
    }

    /**
     * 验证锁获取成功后释放会执行 owner token 校验脚本。
     *
     * @throws Exception 反射注入异常
     */
    @Test
    public void acquiredHandleShouldReleaseLockWithOwnerToken() throws Exception {
        StringRedisTemplate redisTemplate = Mockito.mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations = mockValueOperations(true);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        Mockito.when(redisTemplate.execute(
            ArgumentMatchers.<RedisScript<Long>>any(),
            ArgumentMatchers.<List<String>>any(),
            ArgumentMatchers.<Object[]>any()
        )).thenReturn(1L);

        RedisLockExecutor executor = new RedisLockExecutor();
        setRedisTemplate(executor, redisTemplate);

        RedisLockExecutor.LockHandle handle = executor.tryAcquire(
            "easyflow:test:lock",
            Duration.ZERO,
            Duration.ofSeconds(30)
        );

        Assert.assertNotNull(handle);
        handle.release();
        Mockito.verify(redisTemplate).execute(
            ArgumentMatchers.<RedisScript<Long>>any(),
            ArgumentMatchers.eq(List.of("easyflow:test:lock")),
            ArgumentMatchers.<Object[]>any()
        );
    }

    @SuppressWarnings("unchecked")
    private ValueOperations<String, String> mockValueOperations(boolean acquired) {
        ValueOperations<String, String> valueOperations = Mockito.mock(ValueOperations.class);
        Mockito.when(valueOperations.setIfAbsent(
            ArgumentMatchers.anyString(),
            ArgumentMatchers.anyString(),
            ArgumentMatchers.any(Duration.class)
        )).thenReturn(acquired);
        return valueOperations;
    }

    private void setRedisTemplate(RedisLockExecutor executor, StringRedisTemplate redisTemplate) throws Exception {
        Field field = RedisLockExecutor.class.getDeclaredField("stringRedisTemplate");
        field.setAccessible(true);
        field.set(executor, redisTemplate);
    }
}
