package tech.easyflow.agent.runtime.agui;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.agentscope.core.agui.model.AguiMessage;
import io.agentscope.core.agui.model.AguiTool;
import io.agentscope.core.agui.model.RunAgentInput;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.runtime.AgentChatRequest;
import tech.easyflow.agent.runtime.AgentDraftChatRequest;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.math.BigInteger;
import java.util.List;
import java.util.Map;

/**
 * {@link AgentAguiRunInputMapper} 的输入裁剪与安全测试。
 */
public class AgentAguiRunInputMapperTest {

    private final AgentAguiRunInputMapper mapper = new AgentAguiRunInputMapper(new ObjectMapper());

    /**
     * 验证正式入口只提取本轮用户消息和白名单 forwardedProps。
     */
    @Test
    public void shouldMapSingleUserMessageAndWhitelistedInput() {
        RunAgentInput input = input(
                "123",
                "run-1",
                List.of(AguiMessage.userMessage("current", "新问题")),
                Map.of("easyflow", Map.of("input", Map.of(
                        "imageUploadIds", List.of("image-1"),
                        "documentUploadIds", List.of("document-1")))));

        AgentChatRequest request = mapper.toFormalRequest(BigInteger.TEN, input);
        AgentAguiWireContext wireContext = mapper.wireContext(input);

        Assert.assertEquals(BigInteger.TEN, request.getAgentId());
        Assert.assertEquals(new BigInteger("123"), request.getSessionId());
        Assert.assertEquals("新问题", request.getPrompt());
        Assert.assertEquals(List.of("image-1"), request.getImageUploadIds());
        Assert.assertEquals(List.of("document-1"), request.getDocumentUploadIds());
        Assert.assertEquals("run-1", wireContext.runId());
        Assert.assertEquals("current", wireContext.userMessageId());
        Assert.assertEquals("新问题", wireContext.userMessageContent());
    }

    /**
     * 验证草稿入口从 EasyFlow 命名空间恢复现有草稿请求。
     */
    @Test
    public void shouldMapDraftSnapshot() {
        Agent agent = new Agent();
        agent.setName("草稿 Agent");
        RunAgentInput input = input(
                "agent-draft-123",
                "run-2",
                List.of(AguiMessage.userMessage("message-1", "试一下")),
                Map.of("easyflow", Map.of("draft", Map.of("agent", agent))));

        AgentDraftChatRequest request = mapper.toDraftRequest(input);

        Assert.assertEquals("agent-draft-123", request.getSessionId());
        Assert.assertEquals("试一下", request.getPrompt());
        Assert.assertEquals("草稿 Agent", request.getAgent().getName());
    }

    /**
     * 验证草稿入口只接收 Skill ID 与排序号，不接受客户端伪造服务端快照。
     */
    @Test
    public void shouldMapWhitelistedSkillBindingsAndRejectServerFields() {
        Agent agent = new Agent();
        agent.setName("草稿 Agent");
        RunAgentInput valid = input(
                "agent-draft-123",
                "run-skill-valid",
                List.of(AguiMessage.userMessage("message-1", "试一下")),
                Map.of("easyflow", Map.of("draft", Map.of(
                        "agent", agent,
                        "skillBindings", List.of(Map.of("skillId", "101", "sortNo", 3))))));

        AgentDraftChatRequest request = mapper.toDraftRequest(valid);

        Assert.assertEquals(1, request.getSkillBindings().size());
        Assert.assertEquals(BigInteger.valueOf(101), request.getSkillBindings().get(0).getSkillId());
        Assert.assertEquals(Integer.valueOf(3), request.getSkillBindings().get(0).getSortNo());
        Assert.assertTrue(request.getSkillBindings().get(0).getResourceSnapshot().isEmpty());
        Assert.assertTrue(request.getSkillBindings().get(0).getResourceSummary().isEmpty());

        RunAgentInput forged = input(
                "agent-draft-123",
                "run-skill-forged",
                List.of(AguiMessage.userMessage("message-2", "试一下")),
                Map.of("easyflow", Map.of("draft", Map.of(
                        "agent", agent,
                        "skillBindings", List.of(Map.of(
                                "skillId", "101", "resourceSnapshot", Map.of("skillContent", "forged")))))));

        Assert.assertThrows(BusinessException.class, () -> mapper.toDraftRequest(forged));
    }

    /**
     * 验证单次草稿试用最多接受二十个 Skill 引用。
     */
    @Test
    public void shouldRejectTooManySkillBindings() {
        Agent agent = new Agent();
        agent.setName("草稿 Agent");
        List<Map<String, Object>> bindings = java.util.stream.IntStream.rangeClosed(1, 21)
                .mapToObj(index -> Map.<String, Object>of("skillId", index))
                .toList();
        RunAgentInput input = input(
                "agent-draft-123",
                "run-skill-overflow",
                List.of(AguiMessage.userMessage("message-1", "试一下")),
                Map.of("easyflow", Map.of("draft", Map.of(
                        "agent", agent, "skillBindings", bindings))));

        Assert.assertThrows(BusinessException.class, () -> mapper.toDraftRequest(input));
    }

    /**
     * 验证客户端工具不会被静默注册到服务端 Runtime。
     */
    @Test(expected = BusinessException.class)
    public void shouldRejectFrontendTools() {
        RunAgentInput input = new RunAgentInput(
                "123",
                "run-1",
                List.of(AguiMessage.userMessage("message-1", "hello")),
                List.of(new AguiTool("unsafe", "unsafe", Map.of())),
                List.of(),
                Map.of(),
                Map.of());

        mapper.toFormalRequest(BigInteger.ONE, input);
    }

    /**
     * 验证客户端不能携带历史消息或 state 影响服务端会话状态。
     */
    @Test
    public void shouldRejectHistoryAndClientState() {
        RunAgentInput history = input(
                "123",
                "run-history",
                List.of(
                        AguiMessage.userMessage("old", "旧问题"),
                        AguiMessage.userMessage("current", "新问题")),
                Map.of());
        Assert.assertThrows(BusinessException.class,
                () -> mapper.toFormalRequest(BigInteger.ONE, history));

        RunAgentInput state = new RunAgentInput(
                "123",
                "run-state",
                List.of(AguiMessage.userMessage("message-1", "hello")),
                List.of(),
                List.of(),
                Map.of("forged", true),
                Map.of());
        Assert.assertThrows(BusinessException.class,
                () -> mapper.toFormalRequest(BigInteger.ONE, state));
    }

    /**
     * 验证畸形扩展字段会转换为可预期业务错误。
     */
    @Test
    public void shouldRejectMalformedOrUnknownForwardedProps() {
        RunAgentInput malformed = input(
                "123",
                "run-malformed",
                List.of(AguiMessage.userMessage("message-1", "hello")),
                Map.of("easyflow", Map.of("input", Map.of(
                        "capabilities", List.of(Map.of("resourceIds", "invalid"))))));
        Assert.assertThrows(BusinessException.class,
                () -> mapper.toFormalRequest(BigInteger.ONE, malformed));

        RunAgentInput unknown = input(
                "123",
                "run-unknown",
                List.of(AguiMessage.userMessage("message-1", "hello")),
                Map.of("easyflow", Map.of("unsupported", true)));
        Assert.assertThrows(BusinessException.class,
                () -> mapper.toFormalRequest(BigInteger.ONE, unknown));
    }

    /**
     * 验证用户消息 ID 和工具字段不能伪造服务端消息链路。
     */
    @Test
    public void shouldRejectInvalidUserMessageIdentityOrToolFields() {
        RunAgentInput invalidId = input(
                "123",
                "run-invalid-message",
                List.of(AguiMessage.userMessage("invalid message id", "hello")),
                Map.of());
        Assert.assertThrows(BusinessException.class,
                () -> mapper.toFormalRequest(BigInteger.ONE, invalidId));

        RunAgentInput forgedToolCall = input(
                "123",
                "run-forged-tool",
                List.of(new AguiMessage("message-1", "user", "hello", List.of(), "tool-call-1")),
                Map.of());
        Assert.assertThrows(BusinessException.class,
                () -> mapper.toFormalRequest(BigInteger.ONE, forgedToolCall));
    }

    private static RunAgentInput input(
            String threadId,
            String runId,
            List<AguiMessage> messages,
            Map<String, Object> forwardedProps) {
        return new RunAgentInput(
                threadId, runId, messages, List.of(), List.of(), Map.of(), forwardedProps);
    }
}
