package tech.easyflow.agent.distributed;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import tech.easyflow.agent.config.AgentRuntimeProperties;

import java.time.Duration;

/**
 * Agent 运行态 Redis 路由注册表。
 */
@Component
public class AgentRuntimeRouteRegistry {

    private static final Logger LOG = LoggerFactory.getLogger(AgentRuntimeRouteRegistry.class);

    private static final String REQUEST_ROUTE_PREFIX = "easyflow:agent:runtime:request:";
    private static final String TOKEN_ROUTE_PREFIX = "easyflow:agent:runtime:resume-token:";
    private static final String NODE_HEARTBEAT_PREFIX = "easyflow:agent:runtime:node:";

    private final StringRedisTemplate stringRedisTemplate;
    private final AgentRuntimeProperties properties;
    private final ObjectMapper objectMapper;

    /**
     * 创建 Agent 运行态 Redis 路由注册表。
     *
     * @param stringRedisTemplate Redis 字符串模板
     * @param properties Agent 运行配置
     * @param objectMapper JSON 序列化器
     */
    public AgentRuntimeRouteRegistry(StringRedisTemplate stringRedisTemplate,
                                     AgentRuntimeProperties properties,
                                     ObjectMapper objectMapper) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.properties = properties;
        this.objectMapper = objectMapper;
    }

    /**
     * 注册运行请求 owner 节点。
     *
     * @param requestId 请求 ID
     */
    public void registerRun(String requestId) {
        if (requestId == null || requestId.isBlank()) {
            return;
        }
        stringRedisTemplate.opsForValue().set(requestKey(requestId), serializeRoute(currentRoute()), properties.getRouteTtl());
    }

    /**
     * 注册恢复令牌与请求 ID 的关系。
     *
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     */
    public void registerResumeToken(String requestId, String resumeToken) {
        if (requestId == null || requestId.isBlank() || resumeToken == null || resumeToken.isBlank()) {
            return;
        }
        stringRedisTemplate.opsForValue().set(tokenKey(resumeToken), requestId, properties.getRouteTtl());
    }

    /**
     * 查询请求 ID 所属节点。
     *
     * @param requestId 请求 ID
     * @return owner 节点 ID
     */
    public String findOwnerNode(String requestId) {
        AgentRuntimeRoute route = findOwnerRoute(requestId);
        return route == null ? null : route.getNodeId();
    }

    /**
     * 查询请求 ID 所属路由。
     *
     * @param requestId 请求 ID
     * @return owner 路由
     */
    public AgentRuntimeRoute findOwnerRoute(String requestId) {
        if (requestId == null || requestId.isBlank()) {
            return null;
        }
        String value = stringRedisTemplate.opsForValue().get(requestKey(requestId));
        if (value == null || value.isBlank()) {
            return null;
        }
        return deserializeRoute(value);
    }

    /**
     * 根据恢复令牌查询请求 ID。
     *
     * @param resumeToken 恢复令牌
     * @return 请求 ID
     */
    public String findRequestIdByResumeToken(String resumeToken) {
        if (resumeToken == null || resumeToken.isBlank()) {
            return null;
        }
        return stringRedisTemplate.opsForValue().get(tokenKey(resumeToken));
    }

    /**
     * 删除指定运行请求的路由。
     *
     * @param requestId 请求 ID
     */
    public void removeRun(String requestId) {
        if (requestId == null || requestId.isBlank()) {
            return;
        }
        deleteQuietly(requestKey(requestId));
    }

    /**
     * 删除指定恢复令牌的路由。
     *
     * @param resumeToken 恢复令牌
     */
    public void removeResumeToken(String resumeToken) {
        if (resumeToken == null || resumeToken.isBlank()) {
            return;
        }
        deleteQuietly(tokenKey(resumeToken));
    }

    /**
     * 获取当前节点 ID。
     *
     * @return 当前节点 ID
     */
    public String currentNodeId() {
        return properties.getInstanceId();
    }

    /**
     * 刷新当前节点存活心跳。
     *
     * @param ttl 心跳 TTL
     */
    public void heartbeat(Duration ttl) {
        stringRedisTemplate.opsForValue().set(nodeKey(properties.getInstanceId()), properties.getBootId(), ttl);
    }

    /**
     * 查询指定节点是否仍有存活心跳。
     *
     * @param nodeId 节点 ID
     * @return true 表示节点心跳仍有效
     */
    public boolean isNodeAlive(String nodeId) {
        return currentNodeBootId(nodeId) != null;
    }

    /**
     * 查询指定节点当前启动代 ID。
     *
     * @param nodeId 节点 ID
     * @return 启动代 ID
     */
    public String currentNodeBootId(String nodeId) {
        if (nodeId == null || nodeId.isBlank()) {
            return null;
        }
        return stringRedisTemplate.opsForValue().get(nodeKey(nodeId));
    }

    private String requestKey(String requestId) {
        return REQUEST_ROUTE_PREFIX + requestId;
    }

    private String tokenKey(String resumeToken) {
        return TOKEN_ROUTE_PREFIX + resumeToken;
    }

    private String nodeKey(String nodeId) {
        return NODE_HEARTBEAT_PREFIX + nodeId;
    }

    private AgentRuntimeRoute currentRoute() {
        AgentRuntimeRoute route = new AgentRuntimeRoute();
        route.setNodeId(properties.getInstanceId());
        route.setBootId(properties.getBootId());
        return route;
    }

    private String serializeRoute(AgentRuntimeRoute route) {
        try {
            return objectMapper.writeValueAsString(route);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Agent 运行路由序列化失败", e);
        }
    }

    private AgentRuntimeRoute deserializeRoute(String value) {
        try {
            if (value.trim().startsWith("{")) {
                return objectMapper.readValue(value, AgentRuntimeRoute.class);
            }
            AgentRuntimeRoute legacyRoute = new AgentRuntimeRoute();
            legacyRoute.setNodeId(value);
            return legacyRoute;
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Agent 运行路由反序列化失败", e);
        }
    }

    private void deleteQuietly(String key) {
        try {
            stringRedisTemplate.delete(key);
        } catch (RuntimeException e) {
            LOG.warn("清理 Agent 运行态 Redis 路由失败: key={}", key, e);
        }
    }
}
