package tech.easyflow.agent.distributed;

import com.fasterxml.jackson.databind.ObjectMapper;
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 tech.easyflow.agent.config.AgentRuntimeProperties;
import tech.easyflow.agent.distributed.AgentRuntimeRouteRegistry;

import java.time.Duration;

/**
 * {@link AgentRuntimeRouteRegistry} 回归测试。
 */
public class AgentRuntimeRouteRegistryTest {

    /**
     * 验证注册运行态和恢复令牌时写入 Redis 路由。
     */
    @Test
    public void registerShouldWriteRunAndTokenRoutes() {
        StringRedisTemplate redisTemplate = Mockito.mock(StringRedisTemplate.class);
        @SuppressWarnings("unchecked")
        ValueOperations<String, String> valueOperations = Mockito.mock(ValueOperations.class);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        AgentRuntimeProperties properties = properties("node-a");
        AgentRuntimeRouteRegistry registry = registry(redisTemplate, properties);

        registry.registerRun("request-1");
        registry.registerResumeToken("request-1", "token-1");

        Mockito.verify(valueOperations).set(
                ArgumentMatchers.eq("easyflow:agent:runtime:request:request-1"),
                ArgumentMatchers.contains("\"nodeId\":\"node-a\""),
                ArgumentMatchers.eq(Duration.ofHours(24)));
        Mockito.verify(valueOperations).set(
                "easyflow:agent:runtime:resume-token:token-1", "request-1", Duration.ofHours(24));
    }

    /**
     * 验证运行结束时清理 Redis 路由。
     */
    @Test
    public void removeShouldDeleteRunAndTokenRoutes() {
        StringRedisTemplate redisTemplate = Mockito.mock(StringRedisTemplate.class);
        AgentRuntimeRouteRegistry registry = registry(redisTemplate, properties("node-a"));

        registry.removeRun("request-1");
        registry.removeResumeToken("token-1");

        Mockito.verify(redisTemplate).delete("easyflow:agent:runtime:request:request-1");
        Mockito.verify(redisTemplate).delete("easyflow:agent:runtime:resume-token:token-1");
    }

    /**
     * 验证查询 owner 节点和 token 反查请求 ID。
     */
    @Test
    public void findShouldReadRoutes() {
        StringRedisTemplate redisTemplate = Mockito.mock(StringRedisTemplate.class);
        @SuppressWarnings("unchecked")
        ValueOperations<String, String> valueOperations = Mockito.mock(ValueOperations.class);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        Mockito.when(valueOperations.get(ArgumentMatchers.eq("easyflow:agent:runtime:request:request-1")))
                .thenReturn("{\"nodeId\":\"node-a\",\"bootId\":\"boot-a\"}");
        Mockito.when(valueOperations.get(ArgumentMatchers.eq("easyflow:agent:runtime:resume-token:token-1")))
                .thenReturn("request-1");
        AgentRuntimeRouteRegistry registry = registry(redisTemplate, properties("node-a"));

        Assert.assertEquals("node-a", registry.findOwnerNode("request-1"));
        Assert.assertEquals("boot-a", registry.findOwnerRoute("request-1").getBootId());
        Assert.assertEquals("request-1", registry.findRequestIdByResumeToken("token-1"));
    }

    /**
     * 验证节点心跳写入和存活查询。
     */
    @Test
    public void heartbeatShouldWriteAndReadNodeAliveState() {
        StringRedisTemplate redisTemplate = Mockito.mock(StringRedisTemplate.class);
        @SuppressWarnings("unchecked")
        ValueOperations<String, String> valueOperations = Mockito.mock(ValueOperations.class);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        AgentRuntimeProperties properties = properties("node-a");
        Mockito.when(valueOperations.get("easyflow:agent:runtime:node:node-a")).thenReturn(properties.getBootId());
        AgentRuntimeRouteRegistry registry = registry(redisTemplate, properties);

        registry.heartbeat(Duration.ofSeconds(90));

        Mockito.verify(valueOperations).set("easyflow:agent:runtime:node:node-a", properties.getBootId(), Duration.ofSeconds(90));
        Assert.assertTrue(registry.isNodeAlive("node-a"));
        Assert.assertEquals(properties.getBootId(), registry.currentNodeBootId("node-a"));
    }

    private AgentRuntimeProperties properties(String instanceId) {
        AgentRuntimeProperties properties = new AgentRuntimeProperties();
        properties.setInstanceId(instanceId);
        properties.setRouteTtl(Duration.ofHours(24));
        return properties;
    }

    private AgentRuntimeRouteRegistry registry(StringRedisTemplate redisTemplate, AgentRuntimeProperties properties) {
        return new AgentRuntimeRouteRegistry(redisTemplate, properties, new ObjectMapper());
    }
}
