import { Router } from "express";
import { validateToken } from "../middleware/auth.js";
import { claimMcpLaunchBinding, getLatestAgentToken, getMcpLaunchQueueSize } from "../services/mcp-launch-binding-queue.js";
import { clearRuntimeBinding, getRuntimeAccessToken, getRuntimeBindingPublicState, getRuntimePairingCode, hasRuntimeBinding, saveRuntimeBinding, validateRuntimeBindingToken, validateRuntimePairingCode, } from "../services/runtime-binding.js";
import { createLogger } from "../utils/logger.js";
const log = createLogger("runtime-binding-route");
export const runtimeBindingRouter = Router();
/**
 * 主流程：MCP Node token 必须通过 /runtime/binding/claim-agent 从启动绑定领取。
 * 不再返回 bridge/user/server scoped runtime token，避免 MCP Node 降级使用旧 bridge 级凭据。
 */
export function resolveRuntimeTokenRequest(input) {
    void input;
    return {
        success: false,
        error: "MCP runtime token must be claimed from launch binding; bridge-scoped token refresh is disabled",
    };
}
function extractRuntimeToken(req) {
    const headerToken = req.header("X-Runtime-Token");
    if (headerToken) {
        return headerToken.trim();
    }
    const authorization = req.header("Authorization") || "";
    const bearerMatch = authorization.match(/^Bearer\s+(.+)$/i);
    return bearerMatch?.[1]?.trim() || "";
}
runtimeBindingRouter.get("/binding/status", (_req, res) => {
    const state = getRuntimeBindingPublicState();
    res.json({
        ok: true,
        ...state,
        pairingRequired: !state.paired,
    });
});
runtimeBindingRouter.post("/binding/pair", (req, res) => {
    const { accessToken, instanceId, userId, schedulerBaseUrl, pairingCode } = req.body || {};
    const existingBinding = hasRuntimeBinding();
    const currentRuntimeToken = extractRuntimeToken(req);
    if (existingBinding) {
        const canRotate = validateRuntimeBindingToken(currentRuntimeToken);
        if (!canRotate) {
            res.status(401).json({
                error: "unauthorized",
                message: "Bridge is already paired. Provide the current runtime token to rotate binding.",
            });
            return;
        }
    }
    else if (!validateRuntimePairingCode(pairingCode)) {
        res.status(401).json({
            error: "invalid_pairing_code",
            message: "Invalid pairing code. Read the current pairing code from the bridge console and try again.",
        });
        return;
    }
    try {
        saveRuntimeBinding({
            accessToken: String(accessToken || ""),
            instanceId: instanceId ? String(instanceId) : undefined,
            userId: userId ? String(userId) : undefined,
            schedulerBaseUrl: schedulerBaseUrl ? String(schedulerBaseUrl) : undefined,
        });
        log.info(`[runtime-bridge] paired with instanceId=${instanceId || "unknown"}`);
        res.json({
            ok: true,
            ...getRuntimeBindingPublicState(),
        });
    }
    catch (error) {
        const err = error;
        res
            .status(400)
            .json({ error: err.message || "pair runtime bridge failed" });
    }
});
function isLoopbackRequest(req) {
    const ip = req.ip || req.socket.remoteAddress || "";
    return ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1";
}
runtimeBindingRouter.post("/binding/request-token", async (req, res) => {
    if (!isLoopbackRequest(req)) {
        res.status(403).json({ ok: false, error: "loopback request required" });
        return;
    }
    const result = resolveRuntimeTokenRequest(req.body || {});
    if (!result.success || !result.runtimeAccessToken) {
        res.status(400).json({
            ok: false,
            error: result.error || "request runtime token failed",
        });
        return;
    }
    res.json({
        ok: true,
        runtimeAccessToken: result.runtimeAccessToken,
        updated: result.updated,
    });
});
runtimeBindingRouter.post("/binding/claim-agent", (req, res) => {
    if (!isLoopbackRequest(req)) {
        res.status(403).json({ ok: false, error: "loopback request required" });
        return;
    }
    const { agentId, workspacePath, serverUrl } = req.body || {};
    if (!workspacePath || !String(workspacePath).trim()) {
        res.status(400).json({ ok: false, error: "workspacePath is required" });
        return;
    }
    if (!serverUrl || !String(serverUrl).trim()) {
        res.status(400).json({ ok: false, error: "serverUrl is required" });
        return;
    }
    const claimed = claimMcpLaunchBinding({ agentId, workspacePath, serverUrl });
    if (!claimed) {
        res.status(404).json({
            ok: false,
            error: "launch binding not found for workspacePath and serverUrl",
            queueSize: getMcpLaunchQueueSize(workspacePath),
        });
        return;
    }
    res.json({
        ok: true,
        agentId: claimed.agentId,
        workspacePath: claimed.workspacePath,
        runtimeAccessToken: claimed.runtimeAccessToken,
        userId: claimed.userId,
        schedulerBaseUrl: claimed.schedulerBaseUrl,
        serverUrl: claimed.serverUrl,
        mcpHttpUrl: claimed.mcpHttpUrl,
        bindingId: claimed.id,
        remainingQueueSize: getMcpLaunchQueueSize(workspacePath),
    });
});
/**
 * MCP node 在 401 后向 bridge 查询最新 session token 的端点。
 * 仅接受本地回环请求，仅返回缓存中最新的 token。
 */
runtimeBindingRouter.post("/binding/refresh-token", (req, res) => {
    if (!isLoopbackRequest(req)) {
        res.status(403).json({ ok: false, error: "loopback request required" });
        return;
    }
    const { agentId, currentToken } = req.body || {};
    const normalizedAgentId = String(agentId || "").trim();
    if (!normalizedAgentId) {
        res.status(400).json({ ok: false, error: "agentId is required" });
        return;
    }
    const latest = getLatestAgentToken(normalizedAgentId);
    if (!latest) {
        res.status(404).json({ ok: false, error: "no cached token for agent" });
        return;
    }
    // 如果最新 token 与当前 token 相同，说明服务端已撤销，bridge 侧没有更新的
    const normalizedCurrent = String(currentToken || "").trim();
    if (normalizedCurrent && latest.runtimeAccessToken === normalizedCurrent) {
        res.status(409).json({
            ok: false,
            error: "token_unchanged",
            message: "Bridge cached token is the same as current; token may be revoked server-side",
        });
        return;
    }
    res.json({
        ok: true,
        runtimeAccessToken: latest.runtimeAccessToken,
    });
});
runtimeBindingRouter.post("/binding/issue", validateToken, (req, res) => {
    const { accessToken, instanceId, userId, schedulerBaseUrl } = req.body || {};
    const nextToken = String(accessToken || "").trim();
    if (!nextToken) {
        res.status(400).json({ error: "accessToken is required" });
        return;
    }
    try {
        const previousToken = getRuntimeAccessToken();
        const updated = previousToken !== nextToken;
        if (updated) {
            saveRuntimeBinding({
                accessToken: nextToken,
                instanceId: instanceId ? String(instanceId) : undefined,
                userId: userId ? String(userId) : undefined,
                schedulerBaseUrl: schedulerBaseUrl
                    ? String(schedulerBaseUrl)
                    : undefined,
            });
            log.info(`[runtime-bridge] runtime token redispatched and updated: instanceId=${instanceId || "unknown"}`);
        }
        else {
            log.info(`[runtime-bridge] runtime token redispatched but unchanged: instanceId=${instanceId || "unknown"}`);
        }
        res.json({
            ok: true,
            updated,
            ...getRuntimeBindingPublicState(),
        });
    }
    catch (error) {
        const err = error;
        res
            .status(400)
            .json({ error: err.message || "issue runtime token failed" });
    }
});
runtimeBindingRouter.post("/binding/unpair", validateToken, (_req, res) => {
    clearRuntimeBinding();
    res.json({
        ok: true,
        ...getRuntimeBindingPublicState(),
    });
});
export function logRuntimeBindingStartupState() {
    const state = getRuntimeBindingPublicState();
    if (state.paired) {
        log.info(`[runtime-bridge] runtime binding loaded: instanceId=${state.instanceId || "unknown"}`);
        return;
    }
    log.info("[runtime-bridge] bridge is unpaired; sensitive APIs will reject requests until paired");
    log.info(`[runtime-bridge] pairing code: ${getRuntimePairingCode()}`);
}
