package com.gzzm.lobster.api.admin;

import com.gzzm.lobster.common.JsonUtil;
import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.config.LobsterConfig;
import com.gzzm.lobster.identity.AdminGuard;
import com.gzzm.lobster.storage.ContentStore;
import com.gzzm.lobster.tool.mcp.McpCallLog;
import com.gzzm.lobster.tool.mcp.McpCallLogDao;
import net.cyan.arachne.HttpMethod;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

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

/**
 * MCP call log admin API. Summaries stay in DB; full request/response payloads
 * are read from ContentStore refs on demand.
 */
@Service
public class AdminMcpCallApi {

    @Inject private McpCallLogDao callLogDao;
    @Inject private ContentStore contentStore;

    @Service(url = "/ai/api/admin/mcp-calls/list", method = HttpMethod.all)
    public Map<String, Object> list(String runId, String threadId, String toolCallId,
                                    String serverId, Integer offset, Integer limit) 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));

        List<McpCallLog> rows;
        long total;
        if (runId != null && !runId.isEmpty()) {
            rows = callLogDao.listByRun(runId, o, l);
            total = callLogDao.countByRun(runId);
        } else if (threadId != null && !threadId.isEmpty()) {
            rows = callLogDao.listByThread(threadId, o, l);
            total = callLogDao.countByThread(threadId);
        } else if (toolCallId != null && !toolCallId.isEmpty()) {
            rows = callLogDao.listByToolCall(toolCallId, o, l);
            total = callLogDao.countByToolCall(toolCallId);
        } else if (serverId != null && !serverId.isEmpty()) {
            rows = callLogDao.listByServer(serverId, o, l);
            total = callLogDao.countByServer(serverId);
        } else {
            rows = callLogDao.listAll(o, l);
            total = callLogDao.countAll();
        }

        List<Map<String, Object>> items = new ArrayList<>();
        if (rows != null) {
            for (McpCallLog row : rows) items.add(toSummary(row));
        }
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("items", items);
        out.put("total", Long.valueOf(total));
        out.put("offset", Integer.valueOf(o));
        out.put("limit", Integer.valueOf(l));
        return out;
    }

    @Service(url = "/ai/api/admin/mcp-calls/{$0}", method = HttpMethod.all)
    public Map<String, Object> get(String callId) throws Exception {
        AdminGuard.requireAdmin();
        McpCallLog row = callLogDao.getLog(callId);
        if (row == null) throw new LobsterException("admin.mcp_call.not_found", "MCP call not found: " + callId);
        return toFull(row);
    }

    @Service(url = "/ai/api/admin/mcp-calls/{$0}/payload", method = HttpMethod.all)
    public Map<String, Object> payload(String callId) throws Exception {
        AdminGuard.requireAdmin();
        McpCallLog row = callLogDao.getLog(callId);
        if (row == null) throw new LobsterException("admin.mcp_call.not_found", "MCP call not found: " + callId);
        Map<String, Object> out = toFull(row);
        out.put("request", readJsonRef(row.getRequestJsonRef(), "requestJsonRef"));
        out.put("response", readJsonRef(row.getResponseJsonRef(), "responseJsonRef"));
        return out;
    }

    private Object readJsonRef(String ref, String field) {
        if (ref == null || ref.isEmpty()) {
            Map<String, Object> missing = new LinkedHashMap<>();
            missing.put("missing", Boolean.TRUE);
            missing.put("field", field);
            return missing;
        }
        if (contentStore == null) {
            throw new LobsterException("admin.mcp_call.content_store_unavailable",
                    "ContentStore is unavailable while reading " + field);
        }
        String json = contentStore.read(ref);
        if (json == null) {
            throw new LobsterException("admin.mcp_call.payload_missing",
                    "MCP call payload ref is not readable: " + ref);
        }
        try {
            return JsonUtil.fromJson(json, Object.class);
        } catch (Throwable t) {
            Map<String, Object> raw = new LinkedHashMap<>();
            raw.put("parseFailed", Boolean.TRUE);
            raw.put("raw", json);
            return raw;
        }
    }

    private static Map<String, Object> toSummary(McpCallLog row) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("callId", row.getCallId());
        m.put("serverId", row.getServerId());
        m.put("localToolName", row.getLocalToolName());
        m.put("remoteToolName", row.getRemoteToolName());
        m.put("threadId", row.getThreadId());
        m.put("runId", row.getRunId());
        m.put("toolCallId", row.getToolCallId());
        m.put("requestId", row.getRequestId());
        m.put("userId", row.getUserId());
        m.put("orgId", row.getOrgId());
        m.put("status", row.getStatus());
        m.put("durationMs", row.getDurationMs());
        m.put("requestSummary", parseMaybeJson(row.getRequestSummary()));
        m.put("responseSummary", parseMaybeJson(row.getResponseSummary()));
        m.put("hasRequestPayload", Boolean.valueOf(row.getRequestJsonRef() != null && !row.getRequestJsonRef().isEmpty()));
        m.put("hasResponsePayload", Boolean.valueOf(row.getResponseJsonRef() != null && !row.getResponseJsonRef().isEmpty()));
        m.put("errorCode", row.getErrorCode());
        m.put("errorMessage", row.getErrorMessage());
        m.put("createTime", row.getCreateTime());
        return m;
    }

    private static Map<String, Object> toFull(McpCallLog row) {
        Map<String, Object> m = toSummary(row);
        m.put("requestJsonRef", row.getRequestJsonRef());
        m.put("responseJsonRef", row.getResponseJsonRef());
        return m;
    }

    private static Object parseMaybeJson(String raw) {
        if (raw == null || raw.isEmpty()) return null;
        try {
            return JsonUtil.fromJson(raw, Object.class);
        } catch (Throwable ignore) {
            return raw;
        }
    }
}
