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.stereotype.Service;
import tech.easyflow.agent.config.AgentRuntimeProperties;
import tech.easyflow.common.mq.core.MQMessage;
import tech.easyflow.common.mq.core.MQProducer;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.math.BigInteger;
import java.util.Date;
import java.util.UUID;

/**
 * Agent 运行态远程命令生产者。
 */
@Service
public class AgentRuntimeCommandProducer {

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

    private final MQProducer mqProducer;
    private final ObjectMapper objectMapper;
    private final AgentRuntimeProperties properties;
    private final AgentRuntimeCommandResultRegistry resultRegistry;

    /**
     * 测试子类构造器。
     */
    protected AgentRuntimeCommandProducer() {
        this.mqProducer = null;
        this.objectMapper = null;
        this.properties = null;
        this.resultRegistry = null;
    }

    /**
     * 创建 Agent 运行态远程命令生产者。
     *
     * @param mqProducer MQ 生产者
     * @param objectMapper JSON 序列化器
     * @param properties Agent 运行配置
     * @param resultRegistry 远程命令结果注册表
     */
    public AgentRuntimeCommandProducer(MQProducer mqProducer,
                                       ObjectMapper objectMapper,
                                       AgentRuntimeProperties properties,
                                       AgentRuntimeCommandResultRegistry resultRegistry) {
        this.mqProducer = mqProducer;
        this.objectMapper = objectMapper;
        this.properties = properties;
        this.resultRegistry = resultRegistry;
    }

    /**
     * 投递远程批准命令。
     *
     * @param targetNodeId 目标节点 ID
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     * @param operatorId 操作人 ID
     * @param userId 用户 ID
     */
    public void sendApprove(String targetNodeId,
                            String requestId,
                            String resumeToken,
                            BigInteger operatorId,
                            String userId) {
        sendAndWait(
                targetNodeId, requestId, resumeToken, null, null,
                AgentRuntimeCommandAction.APPROVE, null, operatorId, userId
        );
    }

    /**
     * 投递携带 AG-UI 审批 ID 的远程批准命令。
     *
     * @param targetNodeId 目标节点 ID
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     * @param approvalId 不透明审批 ID
     * @param operatorId 操作人 ID
     * @param userId 用户 ID
     */
    public void sendApprove(String targetNodeId,
                            String requestId,
                            String resumeToken,
                            String approvalId,
                            BigInteger operatorId,
                            String userId) {
        sendAndWait(
                targetNodeId, requestId, resumeToken, null, approvalId,
                AgentRuntimeCommandAction.APPROVE, null, operatorId, userId
        );
    }

    /**
     * 投递远程拒绝命令。
     *
     * @param targetNodeId 目标节点 ID
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     * @param reason 拒绝原因
     * @param operatorId 操作人 ID
     * @param userId 用户 ID
     */
    public void sendReject(String targetNodeId,
                           String requestId,
                           String resumeToken,
                           String reason,
                           BigInteger operatorId,
                           String userId) {
        sendAndWait(
                targetNodeId, requestId, resumeToken, null, null,
                AgentRuntimeCommandAction.REJECT, reason, operatorId, userId
        );
    }

    /**
     * 投递携带 AG-UI 审批 ID 的远程拒绝命令。
     *
     * @param targetNodeId 目标节点 ID
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     * @param approvalId 不透明审批 ID
     * @param reason 拒绝原因
     * @param operatorId 操作人 ID
     * @param userId 用户 ID
     */
    public void sendReject(String targetNodeId,
                           String requestId,
                           String resumeToken,
                           String approvalId,
                           String reason,
                           BigInteger operatorId,
                           String userId) {
        sendAndWait(
                targetNodeId, requestId, resumeToken, null, approvalId,
                AgentRuntimeCommandAction.REJECT, reason, operatorId, userId
        );
    }

    /**
     * 投递远程审批过期命令。
     *
     * @param targetNodeId 目标节点 ID
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     * @param reason 过期原因
     */
    public void sendExpire(String targetNodeId,
                           String requestId,
                           String resumeToken,
                           String reason) {
        sendAndWait(
                targetNodeId, requestId, resumeToken, null, null,
                AgentRuntimeCommandAction.EXPIRE, reason, null, null
        );
    }

    /**
     * 投递远程 Agent 全部运行取消命令。
     *
     * @param targetNodeId 目标节点 ID
     * @param agentId Agent ID
     * @param reason 取消原因
     */
    public void sendCancelAgent(String targetNodeId, String agentId, String reason) {
        sendAndWait(
                targetNodeId, null, null, agentId, null,
                AgentRuntimeCommandAction.CANCEL_AGENT, reason, null, null
        );
    }

    /**
     * 投递远程单次运行取消命令。
     *
     * @param targetNodeId 目标节点 ID
     * @param requestId 内部请求 ID
     * @param userId 当前用户 ID
     * @param reason 取消原因
     */
    public void sendCancelRun(String targetNodeId, String requestId, String userId, String reason) {
        sendAndWait(
                targetNodeId, requestId, null, null, null,
                AgentRuntimeCommandAction.CANCEL_RUN, reason, null, userId
        );
    }

    /**
     * 投递远程运行命令并等待目标节点确认。
     *
     * @param targetNodeId 目标节点 ID
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     * @param agentId Agent ID
     * @param approvalId AG-UI 不透明审批 ID
     * @param action 命令动作
     * @param reason 操作原因
     * @param operatorId 操作人 ID
     * @param userId 用户 ID
     * @throws BusinessException 命令投递、处理或确认失败时抛出
     */
    private void sendAndWait(String targetNodeId,
                             String requestId,
                             String resumeToken,
                             String agentId,
                             String approvalId,
                             AgentRuntimeCommandAction action,
                             String reason,
                             BigInteger operatorId,
                             String userId) {
        if (targetNodeId == null || targetNodeId.isBlank()) {
            throw new BusinessException("Agent 运行节点不可用，请重新发起对话");
        }
        AgentRuntimeCommandMessage command = new AgentRuntimeCommandMessage();
        command.setCommandId(UUID.randomUUID().toString());
        command.setRequestId(requestId);
        command.setResumeToken(resumeToken);
        command.setAgentId(agentId);
        command.setApprovalId(approvalId);
        command.setAction(action);
        command.setReason(reason);
        command.setOperatorId(operatorId);
        command.setUserId(userId);
        command.setTargetNodeId(targetNodeId);
        command.setOccurredAt(new Date());

        MQMessage message = new MQMessage();
        message.setMessageId(command.getCommandId());
        message.setTopic(commandTopic(targetNodeId));
        message.setKey(command.getCommandId());
        message.setCreatedAt(command.getOccurredAt());
        try {
            message.setBody(objectMapper.writeValueAsString(command));
            String recordId = mqProducer.send(message);
            LOG.info("Agent 远程运行命令已投递: action={}, requestId={}, agentId={}, targetNodeId={}, recordId={}",
                    action, requestId, agentId, targetNodeId, recordId);
            AgentRuntimeCommandResult result = resultRegistry.waitForResult(command.getCommandId());
            if (!result.isSuccess()) {
                throw new BusinessException(result.getMessage());
            }
        } catch (JsonProcessingException e) {
            throw new BusinessException("Agent 运行命令序列化失败");
        } catch (BusinessException e) {
            throw e;
        } catch (RuntimeException e) {
            LOG.error("Agent 远程运行命令投递失败: action={}, requestId={}, agentId={}, targetNodeId={}",
                    action, requestId, agentId, targetNodeId, e);
            throw new BusinessException("Agent 运行节点不可用，请重新发起对话");
        } finally {
            deleteResultQuietly(command.getCommandId());
        }
    }

    /**
     * 构建目标节点命令主题。
     *
     * @param nodeId 节点 ID
     * @return 命令主题
     */
    private String commandTopic(String nodeId) {
        return properties.getCommandTopicPrefix() + ":" + nodeId;
    }

    /**
     * 清理远程命令确认结果，失败时由 Redis TTL 兜底。
     *
     * @param commandId 命令 ID
     */
    private void deleteResultQuietly(String commandId) {
        try {
            resultRegistry.deleteResult(commandId);
        } catch (RuntimeException e) {
            LOG.warn("Agent 远程运行命令结果清理失败，等待 TTL 兜底: commandId={}", commandId, e);
        }
    }
}
