package com.gzzm.lobster.api.admin;

import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.common.JsonUtil;
import com.gzzm.lobster.common.ToolRiskLevel;
import com.gzzm.lobster.config.LobsterConfig;
import com.gzzm.lobster.identity.AdminGuard;
import com.gzzm.lobster.tool.ToolDefinitionConfig;
import com.gzzm.lobster.tool.ToolDefinitionConfigDao;
import com.gzzm.lobster.tool.ToolRegistry;
import com.gzzm.lobster.tool.mcp.McpToolBridge;
import com.gzzm.lobster.tool.mcp.McpToolCache;
import com.gzzm.lobster.tool.mcp.McpToolCacheDao;
import com.gzzm.lobster.tool.mcp.McpToolExposureMode;
import com.gzzm.lobster.tool.mcp.McpRuntimeToolCache;
import net.cyan.arachne.HttpMethod;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * MCP 工具发现缓存治理 API。
 */
@Service
public class AdminMcpToolApi {

    @Inject private McpToolCacheDao toolCacheDao;
    @Inject private ToolDefinitionConfigDao toolDefinitionConfigDao;
    @Inject private ToolRegistry toolRegistry;
    @Inject private McpToolBridge mcpToolBridge;
    @Inject private McpRuntimeToolCache runtimeToolCache;

    @Service(url = "/ai/api/admin/mcp-tools/list", method = HttpMethod.all)
    public Map<String, Object> list(Integer offset, Integer limit, String serverId, Boolean enabled) throws Exception {
        AdminGuard.requireAdmin();
        int o = offset == null ? 0 : Math.max(0, offset);
        int l = limit == null
                ? LobsterConfig.getListDefaultPageSize()
                : Math.min(LobsterConfig.getListMaxPageSize(), Math.max(1, limit));
        boolean scopedByServer = serverId != null && !serverId.trim().isEmpty();
        List<McpToolCache> rows;
        long total;
        if (scopedByServer) {
            rows = enabled == null
                    ? toolCacheDao.listByServer(serverId)
                    : toolCacheDao.listByServerAndEnabled(serverId, enabled);
            total = rows.size();
        } else {
            rows = enabled == null
                    ? toolCacheDao.listAll(o, l)
                    : toolCacheDao.listAllByEnabled(enabled, o, l);
            total = enabled == null ? toolCacheDao.countAll() : toolCacheDao.countByEnabled(enabled);
        }
        List<Map<String, Object>> items = new ArrayList<>();
        int end = scopedByServer ? Math.min(rows.size(), o + l) : rows.size();
        int start = scopedByServer ? Math.min(o, end) : 0;
        for (McpToolCache row : rows.subList(start, end)) items.add(toMap(row));
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("items", items);
        out.put("total", total);
        out.put("offset", o);
        out.put("limit", l);
        return out;
    }

    @Service(url = "/ai/api/admin/mcp-tools/{$0}/enabled", method = HttpMethod.post)
    public Map<String, Object> enabled(String toolKey, Boolean enabled) throws Exception {
        AdminGuard.requireAdmin();
        McpToolCache row = toolCacheDao.getByKey(toolKey);
        if (row == null) {
            throw new LobsterException("admin.mcp_tool.not_found", "MCP tool not found: " + toolKey);
        }
        boolean next = enabled == null ? !Boolean.TRUE.equals(row.getEnabled()) : enabled.booleanValue();
        row.setEnabled(Boolean.valueOf(next));
        row.setUpdateTime(new Date());
        toolCacheDao.save(row);
        boolean directlyExposed = next && row.getExposureMode() == McpToolExposureMode.DIRECT;
        syncGovernanceEnabled(row.getLocalToolName(), directlyExposed);
        if (directlyExposed) {
            mcpToolBridge.registerCachedTool(row.getLocalToolName());
        } else {
            runtimeToolCache().invalidateLocal(row.getLocalToolName());
            toolRegistry.unregister(row.getLocalToolName());
        }
        return toMap(row);
    }

    @Service(url = "/ai/api/admin/mcp-tools/{$0}/governance", method = HttpMethod.post)
    public Map<String, Object> governance(String toolKey, String riskLevel, Boolean requireConfirm,
                                          String exposureMode) throws Exception {
        AdminGuard.requireAdmin();
        McpToolCache row = toolCacheDao.getByKey(toolKey);
        if (row == null) {
            throw new LobsterException("admin.mcp_tool.not_found", "MCP tool not found: " + toolKey);
        }
        if (riskLevel != null && !riskLevel.trim().isEmpty()) {
            row.setRiskLevel(ToolRiskLevel.valueOf(riskLevel.trim()));
        }
        if (requireConfirm != null) {
            row.setRequireConfirm(requireConfirm);
        }
        if (exposureMode != null && !exposureMode.trim().isEmpty()) {
            row.setExposureMode(parseExposureMode(exposureMode));
        }
        row.setUpdateTime(new Date());
        toolCacheDao.save(row);

        ToolDefinitionConfig def = toolDefinitionConfigDao.getByName(row.getLocalToolName());
        if (def != null) {
            def.setRiskLevel(row.getRiskLevel());
            def.setRequireConfirm(row.getRequireConfirm());
            def.setEnabled(Boolean.valueOf(Boolean.TRUE.equals(row.getEnabled())
                    && row.getExposureMode() == McpToolExposureMode.DIRECT));
            def.setUpdateTime(new Date());
            toolDefinitionConfigDao.save(def);
        }
        if (Boolean.TRUE.equals(row.getEnabled()) && row.getExposureMode() == McpToolExposureMode.DIRECT) {
            mcpToolBridge.registerCachedTool(row.getLocalToolName());
        } else {
            runtimeToolCache().invalidateLocal(row.getLocalToolName());
            toolRegistry.unregister(row.getLocalToolName());
        }
        return toMap(row);
    }

    private void syncGovernanceEnabled(String localToolName, boolean enabled) throws Exception {
        if (localToolName == null || localToolName.trim().isEmpty()) return;
        ToolDefinitionConfig def = toolDefinitionConfigDao.getByName(localToolName);
        if (def == null) return;
        def.setEnabled(Boolean.valueOf(enabled));
        def.setUpdateTime(new Date());
        toolDefinitionConfigDao.save(def);
    }

    private McpRuntimeToolCache runtimeToolCache() {
        return runtimeToolCache == null ? new McpRuntimeToolCache() : runtimeToolCache;
    }

    private McpToolExposureMode parseExposureMode(String raw) {
        try {
            return McpToolExposureMode.valueOf(raw.trim().toUpperCase());
        } catch (Throwable t) {
            throw new LobsterException("admin.mcp_tool.invalid_exposure_mode",
                    "Invalid MCP tool exposureMode: " + raw);
        }
    }

    private Map<String, Object> toMap(McpToolCache t) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("toolKey", t.getToolKey());
        m.put("serverId", t.getServerId());
        m.put("namespace", t.getNamespace());
        m.put("orgId", t.getOrgId());
        m.put("remoteToolName", t.getRemoteToolName());
        m.put("localToolName", t.getLocalToolName());
        m.put("exposureMode", t.getExposureMode());
        m.put("displayName", t.getDisplayName());
        m.put("description", t.getDescription());
        m.put("inputSchemaJson", t.getInputSchemaJson());
        m.put("outputSchemaJson", t.getOutputSchemaJson());
        m.put("annotationsJson", t.getAnnotationsJson());
        m.put("riskLevel", t.getRiskLevel());
        m.put("requireConfirm", t.getRequireConfirm());
        m.put("enabled", t.getEnabled());
        m.put("schemaHash", t.getSchemaHash());
        m.put("lastDiscoveredAt", t.getLastDiscoveredAt());
        m.put("lastCallAt", t.getLastCallAt());
        m.put("lastErrorCode", t.getLastErrorCode());
        m.put("lastErrorMessage", t.getLastErrorMessage());
        Map<String, Object> source = parseAnnotations(t.getAnnotationsJson());
        m.put("apiCenterSystemKey", source.get("systemKey"));
        m.put("apiCenterAppId", source.get("appId"));
        m.put("apiCenterServiceId", source.get("serviceId"));
        m.put("apiCenterToolId", source.get("toolId"));
        m.put("createTime", t.getCreateTime());
        m.put("updateTime", t.getUpdateTime());
        return m;
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> parseAnnotations(String raw) {
        if (raw == null || raw.trim().isEmpty()) return new LinkedHashMap<>();
        try {
            Object parsed = JsonUtil.fromJson(raw, Object.class);
            if (parsed instanceof Map) return (Map<String, Object>) parsed;
        } catch (Throwable ignore) { /* invalid annotations should not break admin list */ }
        return new LinkedHashMap<>();
    }
}
