package tech.easyflow.agent.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.security.AgentVisibilityQueryHelper;
import tech.easyflow.agent.vo.AgentOptionView;
import tech.easyflow.agent.vo.AgentResourceOptionsView;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.McpService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.PluginItemService;
import tech.easyflow.ai.service.PluginService;
import tech.easyflow.ai.service.PluginVisibilityService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Agent 与设计器依赖资源的安全选项查询服务。
 */
@Service
public class AgentOptionQueryService {

    private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
    };

    private final AgentService agentService;
    private final ModelService modelService;
    private final DocumentCollectionService documentCollectionService;
    private final WorkflowService workflowService;
    private final PluginService pluginService;
    private final PluginItemService pluginItemService;
    private final PluginVisibilityService pluginVisibilityService;
    private final McpService mcpService;
    private final AgentVisibilityQueryHelper agentVisibilityQueryHelper;
    private final ResourceAccessService resourceAccessService;
    private final ObjectMapper objectMapper;

    /**
     * 创建 Agent 安全选项查询服务。
     *
     * @param agentService Agent 服务
     * @param modelService 模型服务
     * @param documentCollectionService 知识库服务
     * @param workflowService 工作流服务
     * @param pluginService 插件服务
     * @param pluginItemService 插件工具服务
     * @param pluginVisibilityService 插件可见性服务
     * @param mcpService MCP 服务
     * @param agentVisibilityQueryHelper Agent 可见性查询助手
     * @param resourceAccessService 资源访问服务
     * @param objectMapper JSON 映射器
     */
    public AgentOptionQueryService(AgentService agentService,
                                   ModelService modelService,
                                   DocumentCollectionService documentCollectionService,
                                   WorkflowService workflowService,
                                   PluginService pluginService,
                                   PluginItemService pluginItemService,
                                   PluginVisibilityService pluginVisibilityService,
                                   McpService mcpService,
                                   AgentVisibilityQueryHelper agentVisibilityQueryHelper,
                                   ResourceAccessService resourceAccessService,
                                   ObjectMapper objectMapper) {
        this.agentService = agentService;
        this.modelService = modelService;
        this.documentCollectionService = documentCollectionService;
        this.workflowService = workflowService;
        this.pluginService = pluginService;
        this.pluginItemService = pluginItemService;
        this.pluginVisibilityService = pluginVisibilityService;
        this.mcpService = mcpService;
        this.agentVisibilityQueryHelper = agentVisibilityQueryHelper;
        this.resourceAccessService = resourceAccessService;
        this.objectMapper = objectMapper;
    }

    /**
     * 查询当前账号可见的 Agent 安全选项。
     *
     * @param publishedOnly 是否仅查询已发布 Agent
     * @return Agent 选项
     */
    public List<AgentOptionView> listAgentOptions(boolean publishedOnly) {
        LoginAccount account = requireAccount();
        QueryWrapper wrapper = QueryWrapper.create();
        agentVisibilityQueryHelper.applyReadableAccess(wrapper);
        wrapper.orderBy(Agent::getModified, false);
        if (publishedOnly) {
            wrapper.eq(Agent::getPublishStatus, PublishStatus.PUBLISHED.getCode())
                    .eq(Agent::getStatus, 1);
        }
        List<AgentOptionView> result = new ArrayList<>();
        ResourceAction action = publishedOnly ? ResourceAction.USE : ResourceAction.READ;
        for (Agent agent : agentService.list(wrapper)) {
            if (!resourceAccessService.canAccess(CategoryResourceType.AGENT, agent, action)) {
                continue;
            }
            result.add(toAgentOption(agent, publishedOnly));
        }
        return result;
    }

    /**
     * 查询 Agent 设计器所需的安全资源选项。
     *
     * @return 资源选项集合
     */
    public AgentResourceOptionsView listDesignerResourceOptions() {
        LoginAccount account = requireAccount();
        return new AgentResourceOptionsView(
                listModelOptions(account),
                listKnowledgeOptions(account),
                listWorkflowOptions(account),
                listPluginToolOptions(account),
                listMcpOptions(account)
        );
    }

    /**
     * 查询当前账号可用于 Agent 会话的知识库安全选项。
     *
     * @return 知识库选项
     */
    public List<AgentResourceOptionsView.ResourceOption> listKnowledgeOptions() {
        return listKnowledgeOptions(requireAccount());
    }

    /**
     * 查询指定 MCP 的安全工具列表。
     *
     * @param mcpId MCP ID
     * @return MCP 工具选项
     */
    public List<AgentResourceOptionsView.McpToolOption> listMcpTools(BigInteger mcpId) {
        LoginAccount account = requireAccount();
        Mcp mcp = mcpService.getOne(QueryWrapper.create()
                .eq(Mcp::getId, mcpId)
                .eq(Mcp::getTenantId, account.getTenantId())
                .eq(Mcp::getStatus, true));
        if (mcp == null) {
            throw new BusinessException("MCP 不存在或不可用");
        }
        Mcp detail = mcpService.getMcpTools(mcpId.toString());
        if (detail == null || detail.getTools() == null) {
            return Collections.emptyList();
        }
        List<AgentResourceOptionsView.McpToolOption> result = new ArrayList<>();
        for (Object tool : detail.getTools()) {
            Map<String, Object> value = objectMapper.convertValue(tool, MAP_TYPE);
            result.add(new AgentResourceOptionsView.McpToolOption(
                    text(value.get("name")),
                    text(value.get("description"))
            ));
        }
        return result;
    }

    /**
     * 查询当前租户可用于 Agent 的模型选项。
     *
     * @param account 当前登录账号
     * @return 模型选项
     */
    private List<AgentResourceOptionsView.ModelOption> listModelOptions(LoginAccount account) {
        Model query = new Model();
        query.setTenantId(account.getTenantId());
        query.setModelType(Model.MODEL_TYPES[0]);
        return modelService.listSelectableModels(query, false, "id", "desc").stream()
                .filter(model -> Objects.equals(model.getTenantId(), account.getTenantId()))
                .map(model -> new AgentResourceOptionsView.ModelOption(
                        model.getId(),
                        model.getTitle(),
                        model.getContextWindowTokens(),
                        model.getMaxOutputTokens()
                ))
                .toList();
    }

    /**
     * 查询当前账号可使用的已发布知识库。
     *
     * @param account 当前登录账号
     * @return 知识库选项
     */
    private List<AgentResourceOptionsView.ResourceOption> listKnowledgeOptions(LoginAccount account) {
        return documentCollectionService.list(QueryWrapper.create()
                        .eq(DocumentCollection::getTenantId, account.getTenantId())
                        .eq(DocumentCollection::getPublishStatus, PublishStatus.PUBLISHED.getCode())
                        .orderBy(DocumentCollection::getModified, false))
                .stream()
                .filter(item -> resourceAccessService.canAccess(
                        CategoryResourceType.KNOWLEDGE, item, ResourceAction.USE))
                .map(item -> new AgentResourceOptionsView.ResourceOption(
                        item.getId(), item.getTitle(), item.getDescription(), null))
                .toList();
    }

    /**
     * 查询当前账号可使用的已发布工作流。
     *
     * @param account 当前登录账号
     * @return 工作流选项
     */
    private List<AgentResourceOptionsView.ResourceOption> listWorkflowOptions(LoginAccount account) {
        return workflowService.list(QueryWrapper.create()
                        .eq(Workflow::getTenantId, account.getTenantId())
                        .eq(Workflow::getPublishStatus, PublishStatus.PUBLISHED.getCode())
                        .eq(Workflow::getStatus, 1)
                        .orderBy(Workflow::getModified, false))
                .stream()
                .filter(item -> resourceAccessService.canAccess(
                        CategoryResourceType.WORKFLOW, item, ResourceAction.USE))
                .map(item -> new AgentResourceOptionsView.ResourceOption(
                        item.getId(), item.getTitle(), item.getDescription(), item.getEnglishName()))
                .toList();
    }

    /**
     * 查询当前账号可使用的插件工具。
     *
     * @param account 当前登录账号
     * @return 插件工具选项
     */
    private List<AgentResourceOptionsView.PluginToolOption> listPluginToolOptions(LoginAccount account) {
        List<Plugin> plugins = pluginService.list(QueryWrapper.create()
                        .eq(Plugin::getTenantId, account.getTenantId()))
                .stream()
                .filter(plugin -> pluginVisibilityService.canAccessPlugin(
                        plugin.getCreatedBy(), plugin.getId()))
                .toList();
        if (plugins.isEmpty()) {
            return Collections.emptyList();
        }
        Map<BigInteger, String> pluginNames = new LinkedHashMap<>();
        plugins.forEach(plugin -> pluginNames.put(plugin.getId(), plugin.getName()));
        return pluginItemService.list(QueryWrapper.create()
                        .in(PluginItem::getPluginId, pluginNames.keySet())
                        .eq(PluginItem::getStatus, 1)
                        .orderBy(PluginItem::getId, false))
                .stream()
                .map(item -> new AgentResourceOptionsView.PluginToolOption(
                        item.getId(),
                        item.getName(),
                        item.getDescription(),
                        item.getEnglishName(),
                        pluginNames.get(item.getPluginId())
                ))
                .toList();
    }

    /**
     * 查询当前租户可使用的 MCP。
     *
     * @param account 当前登录账号
     * @return MCP 选项
     */
    private List<AgentResourceOptionsView.McpOption> listMcpOptions(LoginAccount account) {
        return mcpService.list(QueryWrapper.create()
                        .eq(Mcp::getTenantId, account.getTenantId())
                        .eq(Mcp::getStatus, true)
                        .orderBy(Mcp::getModified, false))
                .stream()
                .map(item -> new AgentResourceOptionsView.McpOption(
                        item.getId(),
                        item.getTitle(),
                        item.getDescription(),
                        item.getApprovalRequired()
                ))
                .toList();
    }

    /**
     * 将 Agent 转换为不包含运行配置的安全选项。
     *
     * @param agent Agent 数据
     * @param publishedOnly 是否读取发布快照中的展示信息
     * @return Agent 安全选项
     */
    private AgentOptionView toAgentOption(Agent agent, boolean publishedOnly) {
        Map<String, Object> snapshot = publishedOnly ? agent.getPublishedSnapshotJson() : Map.of();
        Map<String, Object> basic = snapshot == null ? Map.of() : map(snapshot.get("basicSummary"));
        Map<String, Object> model = snapshot == null ? Map.of() : map(snapshot.get("modelSummary"));
        Map<String, Object> interaction = publishedOnly
                ? map(snapshot == null ? null : snapshot.get("interactionConfigJson"))
                : agent.getInteractionConfigJson();
        return new AgentOptionView(
                agent.getId(),
                firstText(text(basic.get("name")), agent.getName()),
                firstText(text(basic.get("description")), agent.getDescription()),
                firstText(text(basic.get("avatar")), agent.getAvatar()),
                interaction,
                publishedOnly ? booleanValue(model.get("supportImage")) : null
        );
    }

    /**
     * 将对象安全转换为字符串键 Map。
     *
     * @param value 待转换值
     * @return Map；非 Map 值返回空 Map
     */
    private Map<String, Object> map(Object value) {
        if (!(value instanceof Map<?, ?> raw)) {
            return Collections.emptyMap();
        }
        Map<String, Object> result = new LinkedHashMap<>();
        raw.forEach((key, item) -> result.put(String.valueOf(key), item));
        return result;
    }

    /**
     * 转换可空布尔值。
     *
     * @param value 原始值
     * @return 布尔值或 null
     */
    private Boolean booleanValue(Object value) {
        return value == null ? null : Boolean.parseBoolean(String.valueOf(value));
    }

    /**
     * 返回首个非空文本。
     *
     * @param value 首选文本
     * @param fallback 备用文本
     * @return 最终文本
     */
    private String firstText(String value, String fallback) {
        return value == null || value.isBlank() ? fallback : value;
    }

    /**
     * 将对象转换为可空文本。
     *
     * @param value 原始值
     * @return 文本或 null
     */
    private String text(Object value) {
        return value == null ? null : String.valueOf(value);
    }

    /**
     * 获取带租户信息的当前登录账号。
     *
     * @return 当前登录账号
     * @throws BusinessException 登录状态无效时抛出
     */
    private LoginAccount requireAccount() {
        LoginAccount account = SaTokenUtil.getLoginAccount();
        if (account == null || account.getTenantId() == null) {
            throw new BusinessException("当前登录状态失效，请重新登录后再试");
        }
        return account;
    }
}
