package tech.easyflow.common.cache;

import com.alicp.jetcache.support.JavaValueEncoder;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisHashCommands;
import org.springframework.data.redis.connection.RedisScriptingCommands;
import org.springframework.data.redis.connection.ReturnType;

import java.time.Duration;
import java.util.Map;

/**
 * {@link RedisVersionedObjectStore} 二进制协议与脚本调用回归测试。
 */
public class RedisVersionedObjectStoreTest {

    /**
     * 验证状态对象继续按项目 Java 序列化协议读取。
     */
    @Test
    public void loadShouldDecodeExistingJavaPayload() {
        RedisConnectionFactory connectionFactory = Mockito.mock(RedisConnectionFactory.class);
        RedisConnection connection = Mockito.mock(RedisConnection.class);
        RedisHashCommands hashCommands = Mockito.mock(RedisHashCommands.class);
        Mockito.when(connectionFactory.getConnection()).thenReturn(connection);
        Mockito.when(connection.hashCommands()).thenReturn(hashCommands);
        Mockito.when(hashCommands.hGet(
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class)
        )).thenReturn(JavaValueEncoder.INSTANCE.apply("state-value"));
        RedisVersionedObjectStore store = new RedisVersionedObjectStore(connectionFactory);

        String loaded = store.load("workflowState:{instance}:chain", String.class);

        Assert.assertEquals("state-value", loaded);
        Mockito.verify(connection).close();
    }

    /**
     * 验证 CAS 更新通过单次 Redis Lua 调用完成。
     */
    @Test
    public void compareAndSetShouldUseSingleAtomicScript() {
        RedisConnectionFactory connectionFactory = Mockito.mock(RedisConnectionFactory.class);
        RedisConnection connection = Mockito.mock(RedisConnection.class);
        RedisScriptingCommands scriptingCommands = Mockito.mock(RedisScriptingCommands.class);
        Mockito.when(connectionFactory.getConnection()).thenReturn(connection);
        Mockito.when(connection.scriptingCommands()).thenReturn(scriptingCommands);
        Mockito.when(scriptingCommands.eval(
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.eq(ReturnType.INTEGER),
                ArgumentMatchers.eq(1),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class)
        )).thenReturn(1L);
        RedisVersionedObjectStore store = new RedisVersionedObjectStore(connectionFactory);

        boolean updated = store.compareAndSet(
                "workflowState:{instance}:chain",
                3L,
                "state-value",
                4L,
                Duration.ofDays(3));

        Assert.assertTrue(updated);
        Mockito.verify(scriptingCommands, Mockito.times(1)).eval(
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.eq(ReturnType.INTEGER),
                ArgumentMatchers.eq(1),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class));
        Mockito.verify(connection).close();
    }

    /**
     * 验证节点状态更新在同一脚本内校验链版本和 fencing token。
     */
    @Test
    public void compareAndSetFieldsShouldUseDoubleGuardedAtomicScript() {
        RedisConnectionFactory connectionFactory = Mockito.mock(RedisConnectionFactory.class);
        RedisConnection connection = Mockito.mock(RedisConnection.class);
        RedisScriptingCommands scriptingCommands = Mockito.mock(RedisScriptingCommands.class);
        Mockito.when(connectionFactory.getConnection()).thenReturn(connection);
        Mockito.when(connection.scriptingCommands()).thenReturn(scriptingCommands);
        Mockito.when(scriptingCommands.eval(
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.eq(ReturnType.INTEGER),
                ArgumentMatchers.eq(3),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class)
        )).thenReturn(1L);
        RedisVersionedObjectStore store = new RedisVersionedObjectStore(connectionFactory);

        boolean updated = store.compareAndSetFields(
                "workflowState:{instance}:node:node-1",
                2L,
                Map.of("status", "RUNNING"),
                3L,
                "workflowState:{instance}:chain",
                8L,
                "workflowState:{instance}:fence",
                12L,
                Duration.ofDays(3));

        Assert.assertTrue(updated);
        Mockito.verify(scriptingCommands).eval(
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.eq(ReturnType.INTEGER),
                ArgumentMatchers.eq(3),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class),
                ArgumentMatchers.any(byte[].class));
        Mockito.verify(connection).close();
    }
}
