package tech.easyflow.agent.runtime.agui;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.agentscope.core.agui.model.AguiMessage;
import io.agentscope.core.agui.model.RunAgentInput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.entity.AgentSkillBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.runtime.AgentChatCapability;
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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 将受控 AG-UI RunAgentInput 映射为现有 Agent 业务请求。
 *
 * <p>客户端历史、工具、上下文和 state 均不进入 Runtime 权限或会话恢复逻辑。</p>
 */
@Component
public class AgentAguiRunInputMapper {

    private static final Logger LOG = LoggerFactory.getLogger(AgentAguiRunInputMapper.class);
    private static final int MAX_ATTACHMENTS_PER_TYPE = 32;
    private static final int MAX_BINDINGS_PER_TYPE = 256;
    private static final int MAX_SKILL_BINDINGS = 20;
    private static final int MAX_CAPABILITIES = 32;
    private static final int MAX_CAPABILITY_RESOURCE_IDS = 256;
    private static final int MAX_FORWARDED_PROPS_BYTES = 1_048_576;
    private static final int MAX_IDENTIFIER_LENGTH = 128;
    private static final int MAX_PROMPT_LENGTH = 65_536;
    private static final Set<String> FORMAL_EASYFLOW_KEYS = Set.of("input");
    private static final Set<String> DRAFT_EASYFLOW_KEYS = Set.of("draft", "input");
    private static final Set<String> INPUT_KEYS = Set.of(
            "capabilities", "documentUploadIds", "imageUploadIds");
    private static final Set<String> DRAFT_KEYS = Set.of(
            "agent", "knowledgeBindings", "toolBindings", "skillBindings");
    private static final Set<String> SKILL_BINDING_KEYS = Set.of("skillId", "sortNo");

    private final ObjectMapper objectMapper;

    /**
     * 创建输入映射器。
     *
     * @param objectMapper Jackson 映射器
     */
    public AgentAguiRunInputMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    /**
     * 映射正式聊天请求。
     *
     * @param agentId URL 中的可信 Agent ID
     * @param input AG-UI 运行输入
     * @return 现有正式聊天请求
     */
    public AgentChatRequest toFormalRequest(BigInteger agentId, RunAgentInput input) {
        ValidatedInput validated = validate(input, false);
        AgentChatRequest request = new AgentChatRequest();
        request.setAgentId(agentId);
        request.setSessionId(parseFormalThreadId(validated.threadId()));
        request.setPrompt(validated.userMessage().getContent());
        Map<String, Object> inputProps = nestedMap(easyflowProps(input), "input");
        request.setImageUploadIds(stringList(
                inputProps.get("imageUploadIds"), "imageUploadIds", MAX_ATTACHMENTS_PER_TYPE));
        request.setDocumentUploadIds(stringList(
                inputProps.get("documentUploadIds"), "documentUploadIds", MAX_ATTACHMENTS_PER_TYPE));
        List<AgentChatCapability> capabilities = convertList(
                inputProps.get("capabilities"), AgentChatCapability.class,
                "capabilities", MAX_CAPABILITIES);
        validateCapabilities(capabilities);
        request.setCapabilities(capabilities);
        return request;
    }

    /**
     * 映射草稿试用请求。
     *
     * @param input AG-UI 运行输入
     * @return 现有草稿试用请求
     */
    public AgentDraftChatRequest toDraftRequest(RunAgentInput input) {
        ValidatedInput validated = validate(input, true);
        Map<String, Object> easyflow = easyflowProps(input);
        Map<String, Object> inputProps = nestedMap(easyflow, "input");
        Map<String, Object> draftProps = nestedMap(easyflow, "draft");
        AgentDraftChatRequest request = new AgentDraftChatRequest();
        request.setSessionId(validated.threadId());
        request.setPrompt(validated.userMessage().getContent());
        request.setImageUploadIds(stringList(
                inputProps.get("imageUploadIds"), "imageUploadIds", MAX_ATTACHMENTS_PER_TYPE));
        request.setDocumentUploadIds(stringList(
                inputProps.get("documentUploadIds"), "documentUploadIds", MAX_ATTACHMENTS_PER_TYPE));
        request.setAgent(convertRequired(draftProps.get("agent"), Agent.class, "Agent 草稿不能为空"));
        request.setToolBindings(convertList(
                draftProps.get("toolBindings"), AgentToolBinding.class,
                "toolBindings", MAX_BINDINGS_PER_TYPE));
        request.setKnowledgeBindings(convertList(
                draftProps.get("knowledgeBindings"), AgentKnowledgeBinding.class,
                "knowledgeBindings", MAX_BINDINGS_PER_TYPE));
        request.setSkillBindings(convertSkillBindings(draftProps.get("skillBindings")));
        return request;
    }

    /**
     * 获取 AG-UI wire 上下文。
     *
     * @param input AG-UI 运行输入
     * @return wire 上下文
     */
    public AgentAguiWireContext wireContext(RunAgentInput input) {
        ValidatedInput validated = validateCommon(input);
        return new AgentAguiWireContext(
                validated.threadId(),
                input.getRunId(),
                validated.userMessage().getId(),
                validated.userMessage().getContent());
    }

    private ValidatedInput validate(RunAgentInput input, boolean draft) {
        ValidatedInput validated = validateCommon(input);
        validateForwardedProps(input, draft);
        return validated;
    }

    private ValidatedInput validateCommon(RunAgentInput input) {
        if (input == null) {
            throw new BusinessException("AG-UI 运行输入不能为空");
        }
        requireIdentifier(input.getThreadId(), "threadId");
        requireIdentifier(input.getRunId(), "runId");
        if (input.getMessages() == null || input.getMessages().size() != 1) {
            throw new BusinessException("AG-UI 入口每轮只接受一条用户消息");
        }
        if (input.getTools() != null && !input.getTools().isEmpty()) {
            throw new BusinessException("当前 Agent 入口不接受客户端工具");
        }
        if (input.getContext() != null && !input.getContext().isEmpty()) {
            throw new BusinessException("当前 Agent 入口不接受客户端上下文");
        }
        if (input.getState() != null && !input.getState().isEmpty()) {
            throw new BusinessException("当前 Agent 入口不接受客户端 state");
        }
        AguiMessage userMessage = input.getMessages().get(0);
        if (userMessage == null || userMessage.getContent() == null) {
            throw new BusinessException("AG-UI 输入缺少本轮用户消息");
        }
        if (!userMessage.isUserMessage()) {
            throw new BusinessException("AG-UI 入口只接受用户消息");
        }
        requireIdentifier(userMessage.getId(), "messageId");
        if (userMessage.hasToolCalls()
                || (userMessage.getToolCallId() != null && !userMessage.getToolCallId().isBlank())) {
            throw new BusinessException("AG-UI 用户消息不能携带工具调用");
        }
        if (userMessage.getContent().length() > MAX_PROMPT_LENGTH) {
            throw new BusinessException("Agent 输入内容过长");
        }
        return new ValidatedInput(input.getThreadId(), userMessage);
    }

    private void validateForwardedProps(RunAgentInput input, boolean draft) {
        Map<String, Object> forwardedProps = input.getForwardedProps() == null
                ? Map.of()
                : input.getForwardedProps();
        rejectUnknownKeys(forwardedProps, Set.of("easyflow"), "forwardedProps");
        validateSerializedSize(forwardedProps);
        Map<String, Object> easyflow = easyflowProps(input);
        rejectUnknownKeys(easyflow, draft ? DRAFT_EASYFLOW_KEYS : FORMAL_EASYFLOW_KEYS, "easyflow");
        Map<String, Object> inputProps = nestedMap(easyflow, "input");
        rejectUnknownKeys(inputProps, INPUT_KEYS, "easyflow.input");
        if (draft) {
            Map<String, Object> draftProps = nestedMap(easyflow, "draft");
            rejectUnknownKeys(draftProps, DRAFT_KEYS, "easyflow.draft");
        }
    }

    private void validateSerializedSize(Map<String, Object> forwardedProps) {
        try {
            if (objectMapper.writeValueAsBytes(forwardedProps).length > MAX_FORWARDED_PROPS_BYTES) {
                throw new BusinessException("AG-UI forwardedProps 内容过大");
            }
        } catch (JsonProcessingException exception) {
            throw new BusinessException("AG-UI forwardedProps 格式不合法");
        }
    }

    private BigInteger parseFormalThreadId(String threadId) {
        try {
            BigInteger value = new BigInteger(threadId);
            if (value.signum() <= 0) {
                throw new NumberFormatException("non-positive");
            }
            return value;
        } catch (NumberFormatException exception) {
            throw new BusinessException("正式 Agent threadId 必须是有效会话 ID");
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> easyflowProps(RunAgentInput input) {
        Object value = input.getForwardedProps() == null
                ? null
                : input.getForwardedProps().get("easyflow");
        if (value == null) {
            return Map.of();
        }
        if (!(value instanceof Map<?, ?> map)) {
            throw new BusinessException("AG-UI easyflow 扩展格式不合法");
        }
        return (Map<String, Object>) map;
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> nestedMap(Map<String, Object> source, String key) {
        Object value = source.get(key);
        if (value == null) {
            return Map.of();
        }
        if (!(value instanceof Map<?, ?> map)) {
            throw new BusinessException("AG-UI " + key + " 扩展格式不合法");
        }
        return (Map<String, Object>) map;
    }

    private List<String> stringList(Object value, String name, int maximumSize) {
        if (value == null) {
            return List.of();
        }
        if (!(value instanceof List<?> list) || list.size() > maximumSize) {
            throw new BusinessException("AG-UI " + name + " 数量不合法");
        }
        List<String> result = new ArrayList<>(list.size());
        for (Object item : list) {
            if (!(item instanceof String text) || text.isBlank()
                    || text.length() > MAX_IDENTIFIER_LENGTH) {
                throw new BusinessException("AG-UI " + name + " 内容不合法");
            }
            result.add(text);
        }
        return result;
    }

    private <T> List<T> convertList(Object value,
                                    Class<T> targetType,
                                    String name,
                                    int maximumSize) {
        if (value == null) {
            return List.of();
        }
        if (!(value instanceof List<?> list) || list.size() > maximumSize) {
            throw new BusinessException("AG-UI " + name + " 数量不合法");
        }
        List<T> result = new ArrayList<>(list.size());
        try {
            for (Object item : list) {
                result.add(objectMapper.convertValue(item, targetType));
            }
        } catch (IllegalArgumentException exception) {
            throw new BusinessException("AG-UI " + name + " 内容不合法");
        }
        return result;
    }

    /**
     * 将客户端 Skill 引用转换为最小领域绑定，服务端快照与摘要字段不会进入 Runtime。
     *
     * @param value 客户端 Skill 引用列表
     * @return 仅包含 Skill ID 与排序号的绑定
     */
    private List<AgentSkillBinding> convertSkillBindings(Object value) {
        if (value == null) {
            return List.of();
        }
        if (!(value instanceof List<?> list) || list.size() > MAX_SKILL_BINDINGS) {
            throw new BusinessException("AG-UI skillBindings 数量不合法");
        }
        List<AgentSkillBinding> result = new ArrayList<>(list.size());
        for (Object item : list) {
            if (!(item instanceof Map<?, ?> raw)) {
                throw new BusinessException("AG-UI skillBindings 内容不合法");
            }
            Map<String, Object> binding = stringKeyMap(raw, "skillBindings");
            rejectUnknownKeys(binding, SKILL_BINDING_KEYS, "easyflow.draft.skillBindings");
            AgentSkillBinding converted = new AgentSkillBinding();
            converted.setSkillId(positiveBigInteger(binding.get("skillId"), "skillId"));
            converted.setSortNo(optionalInteger(binding.get("sortNo"), "sortNo"));
            result.add(converted);
        }
        return result;
    }

    /**
     * 将任意 Map 规范为字符串键 Map。
     *
     * @param source 原始 Map
     * @param name 字段名称
     * @return 字符串键 Map
     */
    private Map<String, Object> stringKeyMap(Map<?, ?> source, String name) {
        Map<String, Object> result = new LinkedHashMap<>();
        for (Map.Entry<?, ?> entry : source.entrySet()) {
            if (!(entry.getKey() instanceof String key)) {
                throw new BusinessException("AG-UI " + name + " 内容不合法");
            }
            result.put(key, entry.getValue());
        }
        return result;
    }

    /**
     * 解析正整数 ID。
     *
     * @param value 原始值
     * @param name 字段名称
     * @return 正整数 ID
     */
    private BigInteger positiveBigInteger(Object value, String name) {
        if (value == null) {
            throw new BusinessException("AG-UI " + name + " 不能为空");
        }
        try {
            BigInteger result = new BigInteger(String.valueOf(value));
            if (result.signum() <= 0) {
                throw new NumberFormatException("non-positive");
            }
            return result;
        } catch (NumberFormatException exception) {
            throw new BusinessException("AG-UI " + name + " 不合法");
        }
    }

    /**
     * 解析可选整数。
     *
     * @param value 原始值
     * @param name 字段名称
     * @return 整数或 null
     */
    private Integer optionalInteger(Object value, String name) {
        if (value == null) {
            return null;
        }
        try {
            return Integer.valueOf(String.valueOf(value));
        } catch (NumberFormatException exception) {
            throw new BusinessException("AG-UI " + name + " 不合法");
        }
    }

    private void validateCapabilities(List<AgentChatCapability> capabilities) {
        for (AgentChatCapability capability : capabilities) {
            if (capability == null || capability.getType() == null
                    || capability.getType().isBlank() || capability.getType().length() > 64
                    || capability.getResourceIds().size() > MAX_CAPABILITY_RESOURCE_IDS) {
                throw new BusinessException("AG-UI capabilities 内容不合法");
            }
        }
    }

    private <T> T convertRequired(Object value, Class<T> targetType, String message) {
        if (value == null) {
            throw new BusinessException(message);
        }
        try {
            return objectMapper.convertValue(value, targetType);
        } catch (IllegalArgumentException exception) {
            throw new BusinessException(message);
        }
    }

    private void requireIdentifier(String value, String name) {
        if (value == null || value.isBlank() || value.length() > MAX_IDENTIFIER_LENGTH
                || !value.matches("[A-Za-z0-9._:-]+")) {
            throw new BusinessException("AG-UI " + name + " 不合法");
        }
    }

    private void rejectUnknownKeys(Map<String, Object> source, Set<String> allowedKeys, String name) {
        if (!allowedKeys.containsAll(source.keySet())) {
            LOG.debug("Reject unsupported AG-UI keys, namespace={}, keys={}", name, source.keySet());
            throw new BusinessException("AG-UI " + name + " 包含不支持的字段");
        }
    }

    private record ValidatedInput(String threadId, AguiMessage userMessage) {
    }
}
