package com.easyagents.agent.runtime.event.observer;

import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
import com.easyagents.agent.runtime.event.AgentRuntimeEventBridge;
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
import com.easyagents.agent.runtime.event.AgentRuntimeObserver;
import com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext;
import com.easyagents.agent.runtime.tool.AgentToolCategory;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import io.agentscope.core.hook.HookEvent;
import io.agentscope.core.hook.PostActingEvent;
import io.agentscope.core.hook.PreActingEvent;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.message.ToolResultBlock;
import io.agentscope.core.message.ToolUseBlock;
import reactor.core.publisher.Mono;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 监听 AgentScope 原生工具执行生命周期，并发射工具状态旁路事件。
 *
 * <p>该观察器复用 {@link AgentRuntimeEventType#TOOL_CALL} 和
 * {@link AgentRuntimeEventType#TOOL_RESULT}，用于 EasyFlow 展示工具开始与完成状态。
 * 它不修改 AgentScope HookEvent，也不写入模型上下文。</p>
 */
public class ToolExecutionObserver implements AgentRuntimeObserver {

    private final AgentRuntimeEventBridge eventBridge;
    private final AgentSkillRuntimeContext skillContext;
    private final Map<String, AgentToolSpec> toolSpecs;

    /**
     * 创建工具执行观察器。
     *
     * @param eventBridge 旁路事件桥
     */
    public ToolExecutionObserver(AgentRuntimeEventBridge eventBridge) {
        this(eventBridge, null, List.of());
    }

    /**
     * 创建工具执行观察器。
     *
     * @param eventBridge 旁路事件桥
     * @param skillContext Skill 上下文，用于跳过由 SkillExecutionObserver 处理的工具
     */
    public ToolExecutionObserver(AgentRuntimeEventBridge eventBridge,
                                 AgentSkillRuntimeContext skillContext) {
        this(eventBridge, skillContext, List.of());
    }

    /**
     * 创建工具执行观察器。
     *
     * @param eventBridge 旁路事件桥
     * @param skillContext Skill 上下文，用于跳过由 SkillExecutionObserver 处理的工具
     * @param toolSpecs 工具声明列表，用于补齐展示名称和治理元数据
     */
    public ToolExecutionObserver(AgentRuntimeEventBridge eventBridge,
                                 AgentSkillRuntimeContext skillContext,
                                 List<AgentToolSpec> toolSpecs) {
        this.eventBridge = eventBridge;
        this.skillContext = skillContext;
        this.toolSpecs = (toolSpecs == null ? List.<AgentToolSpec>of() : toolSpecs).stream()
                .filter(spec -> spec != null && spec.getName() != null && !spec.getName().isBlank())
                .collect(Collectors.toMap(AgentToolSpec::getName, Function.identity(), (left, right) -> left,
                        LinkedHashMap::new));
    }

    /**
     * 观察工具执行前后事件。
     *
     * @param event AgentScope Hook 事件
     * @return 完成信号
     */
    @Override
    public Mono<Void> observe(HookEvent event) {
        if (event instanceof PreActingEvent preActingEvent) {
            emitToolCall(preActingEvent);
            return Mono.empty();
        }
        if (event instanceof PostActingEvent postActingEvent) {
            emitToolResult(postActingEvent);
        }
        return Mono.empty();
    }

    private void emitToolCall(PreActingEvent event) {
        ToolUseBlock toolUse = event.getToolUse();
        if (toolUse == null) {
            return;
        }
        if (isSkillTool(toolUse.getName())) {
            return;
        }
        AgentRuntimeEvent runtimeEvent = eventBridge.event(AgentRuntimeEventType.TOOL_CALL);
        runtimeEvent.setToolCallId(toolUse.getId());
        runtimeEvent.getPayload().put("toolCallId", toolUse.getId());
        runtimeEvent.getPayload().put("name", toolUse.getName());
        runtimeEvent.getPayload().put("toolName", toolUse.getName());
        runtimeEvent.getPayload().put("status", "RUNNING");
        enrichToolPayload(runtimeEvent, toolUse.getName());
        eventBridge.emit(runtimeEvent);
    }

    private void emitToolResult(PostActingEvent event) {
        ToolResultBlock result = event.getToolResult();
        ToolUseBlock toolUse = event.getToolUse();
        if (result == null && toolUse == null) {
            return;
        }
        String toolCallId = result == null ? toolUse.getId() : result.getId();
        String toolName = result == null ? toolUse.getName() : result.getName();
        if (isSkillTool(toolName)) {
            return;
        }
        AgentRuntimeEvent runtimeEvent = eventBridge.event(AgentRuntimeEventType.TOOL_RESULT);
        runtimeEvent.setToolCallId(toolCallId);
        runtimeEvent.getPayload().put("toolCallId", toolCallId);
        runtimeEvent.getPayload().put("name", toolName);
        runtimeEvent.getPayload().put("toolName", toolName);
        runtimeEvent.getPayload().put("status", success(result) ? "SUCCESS" : "FAILED");
        runtimeEvent.getPayload().put("success", success(result));
        enrichToolPayload(runtimeEvent, toolName);
        eventBridge.emit(runtimeEvent);
    }

    private void enrichToolPayload(AgentRuntimeEvent runtimeEvent, String toolName) {
        AgentToolSpec toolSpec = toolSpecs.get(toolName);
        if (toolSpec == null) {
            return;
        }
        runtimeEvent.getPayload().put("toolCategory", toolSpec.getCategory().name());
        if (toolSpec.getMetadata() == null || toolSpec.getMetadata().isEmpty()) {
            return;
        }
        Map<String, Object> metadata = toolSpec.getMetadata();
        putIfPresent(runtimeEvent.getPayload(), metadata, "toolDisplayName");
        putIfPresent(runtimeEvent.getPayload(), metadata, "skillId");
        if (toolSpec.getCategory() == AgentToolCategory.KNOWLEDGE) {
            putIfPresent(runtimeEvent.getPayload(), metadata, "knowledgeId");
            putIfPresent(runtimeEvent.getPayload(), metadata, "knowledgeName");
            putIfPresent(runtimeEvent.getPayload(), metadata, "knowledgeRuntimeName");
        }
    }

    private void putIfPresent(Map<String, Object> payload, Map<String, Object> metadata, String key) {
        if (metadata.containsKey(key)) {
            payload.put(key, metadata.get(key));
        }
    }

    private boolean success(ToolResultBlock result) {
        if (result == null) {
            return false;
        }
        Object success = result.getMetadata() == null ? null : result.getMetadata().get("success");
        if (success instanceof Boolean) {
            return Boolean.TRUE.equals(success);
        }
        // AgentScope 1.x 将工具异常转换为不带 success metadata 的 "Error: ..." 文本结果。
        return result.getOutput().stream()
                .filter(TextBlock.class::isInstance)
                .map(TextBlock.class::cast)
                .map(TextBlock::getText)
                .noneMatch(text -> text != null && text.startsWith("Error: "));
    }

    private boolean isSkillTool(String toolName) {
        if (skillContext == null) {
            return false;
        }
        return skillContext.isSkillLoadTool(toolName) || skillContext.getActiveToolBinding(toolName) != null;
    }
}
