package tech.easyflow.ai.easyagentsflow.upload;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
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.util.List;

/**
 * {@link WorkflowApiUploadStore} Redis 原子性契约测试。
 */
public class WorkflowApiUploadStoreTest {

    /**
     * 验证记录与清理索引通过同槽 Lua 脚本原子创建。
     */
    @Test
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void createShouldUseSameSlotAtomicScript() {
        StringRedisTemplate redisTemplate =
                Mockito.mock(StringRedisTemplate.class);
        Mockito.doReturn(1L).when(redisTemplate).execute(
                ArgumentMatchers.<RedisScript<Long>>any(),
                ArgumentMatchers.<List<String>>any(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString());
        WorkflowApiUploadStore store = new WorkflowApiUploadStore(
                redisTemplate,
                new ObjectMapper());
        WorkflowApiUploadRecord record = record("request-1", null);

        store.create(record);

        ArgumentCaptor<RedisScript<Long>> scriptCaptor =
                ArgumentCaptor.forClass((Class) RedisScript.class);
        ArgumentCaptor<List<String>> keysCaptor =
                ArgumentCaptor.forClass((Class) List.class);
        Mockito.verify(redisTemplate).execute(
                scriptCaptor.capture(),
                keysCaptor.capture(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString());
        Assert.assertTrue(
                scriptCaptor.getValue().getScriptAsString()
                        .contains("redis.call('zadd'"));
        Assert.assertEquals(3, keysCaptor.getValue().size());
        Assert.assertTrue(keysCaptor.getValue().stream()
                .allMatch(key -> key.contains("{api-upload}")));
    }

    /**
     * 验证重新绑定执行 ID 时会在同一脚本中清除旧索引。
     *
     * @throws Exception 上传记录序列化失败
     */
    @Test
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void bindExecutionShouldReplacePreviousIndexAtomically()
            throws Exception {
        StringRedisTemplate redisTemplate =
                Mockito.mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations =
                Mockito.mock(ValueOperations.class);
        Mockito.when(redisTemplate.opsForValue())
                .thenReturn(valueOperations);
        ObjectMapper objectMapper = new ObjectMapper();
        WorkflowApiUploadRecord record =
                record("request-1", "execution-old");
        Mockito.when(valueOperations.get(
                        "easyflow:workflow:{api-upload}:record:request-1"))
                .thenReturn(objectMapper.writeValueAsString(record));
        Mockito.doReturn(1L).when(redisTemplate).execute(
                ArgumentMatchers.<RedisScript<Long>>any(),
                ArgumentMatchers.<List<String>>any(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString());
        WorkflowApiUploadStore store = new WorkflowApiUploadStore(
                redisTemplate,
                objectMapper);

        store.bindExecution("request-1", "execution-new");

        ArgumentCaptor<RedisScript<Long>> scriptCaptor =
                ArgumentCaptor.forClass((Class) RedisScript.class);
        ArgumentCaptor<List<String>> keysCaptor =
                ArgumentCaptor.forClass((Class) List.class);
        Mockito.verify(redisTemplate).execute(
                scriptCaptor.capture(),
                keysCaptor.capture(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString(),
                ArgumentMatchers.anyString());
        Assert.assertEquals(
                "easyflow:workflow:{api-upload}:execution:execution-new",
                keysCaptor.getValue().get(1));
        Assert.assertEquals(
                "easyflow:workflow:{api-upload}:execution:execution-old",
                keysCaptor.getValue().get(2));
        Assert.assertTrue(
                scriptCaptor.getValue().getScriptAsString()
                        .contains("redis.call('del', KEYS[3])"));
    }

    /**
     * 验证旧版 Redis 记录仍可把历史 requestId 作为内部上传 ID 读取。
     *
     * @throws Exception JSON 反序列化失败
     */
    @Test
    public void legacyRecordShouldResolveHistoricalUploadId()
            throws Exception {
        WorkflowApiUploadRecord record = new ObjectMapper().readValue(
                "{\"requestId\":\"legacy-upload-1\"}",
                WorkflowApiUploadRecord.class);

        Assert.assertEquals("legacy-upload-1", record.getUploadId());
    }

    /**
     * 创建测试上传记录。
     *
     * @param uploadId 内部上传 ID
     * @param executeId 执行 ID
     * @return 上传记录
     */
    private WorkflowApiUploadRecord record(
            String uploadId,
            String executeId) {
        WorkflowApiUploadRecord record = new WorkflowApiUploadRecord();
        record.setUploadId(uploadId);
        record.setRequestId("http-request-1");
        record.setExecuteId(executeId);
        record.setCleanupAt(1_000L);
        return record;
    }
}
