import { readFile, writeFile, rename } from "node:fs/promises";
import { initVertexAuth, clearVertexAuth } from "../lib/vertexAuth.js";
// Atomic + 0600 config write: temp file then rename, so a crash or concurrent
// save can't corrupt config.json (which may hold API keys). Rename also forces
// 0600 perms even if a looser-perm config pre-existed.
async function writeConfigAtomic(cfgPath, data) {
    const tmp = `${cfgPath}.${process.pid}.tmp`;
    await writeFile(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
    await rename(tmp, cfgPath);
}
const KEY_PREFIX_MAP = {
    openai: ["sk-"],
    xai: ["xai-"],
    gemini: ["AI"],
};
const VALIDATE_URL_MAP = {
    openai: "https://api.openai.com/v1/models",
    xai: "https://api.x.ai/v1/models",
    gemini: "https://generativelanguage.googleapis.com/v1beta/models",
};
const CONFIG_KEY_MAP = {
    openai: "apiKey",
    xai: "xaiApiKey",
    gemini: "geminiApiKey",
};
function isKeyProvider(v) {
    return v === "openai" || v === "xai" || v === "gemini";
}
function maskKey(key) {
    if (key.length <= 10)
        return "***";
    return `${key.slice(0, 4)}..${key.slice(-2)}`;
}
function keySourceForProvider(ctx, provider) {
    if (provider === "openai")
        return { key: ctx.apiKey, source: ctx.apiKeySource || "none" };
    if (provider === "xai")
        return { key: ctx.xaiApiKey, source: ctx.xaiApiKeySource || "none" };
    if (provider === "gemini")
        return { key: ctx.geminiApiKey, source: ctx.geminiApiKeySource || "none" };
    return { key: undefined, source: "none" };
}
export function mountKeyRoutes(app, ctx) {
    app.get("/api/keys/status", (_req, res) => {
        const status = {};
        for (const provider of ["openai", "xai", "gemini"]) {
            const { key, source } = keySourceForProvider(ctx, provider);
            status[provider] = {
                configured: !!key,
                source,
                valid: !!key,
                maskedKey: key ? maskKey(key) : null,
            };
        }
        const vertexJson = ctx.vertexServiceAccountJson;
        const vertexSource = vertexJson
            ? (process.env.VERTEX_SERVICE_ACCOUNT_JSON ? "env" : "config")
            : "none";
        status.vertex = {
            configured: !!vertexJson,
            source: vertexSource,
            valid: !!vertexJson,
            maskedKey: ctx.vertexProjectId ? `project: ${ctx.vertexProjectId}` : null,
        };
        res.json(status);
    });
    // Vertex JSON — dedicated route (before generic :provider)
    app.put("/api/keys/vertex", async (req, res) => {
        const { serviceAccountJson } = req.body;
        if (!serviceAccountJson || typeof serviceAccountJson !== "string") {
            return res.status(400).json({ ok: false, error: "Missing serviceAccountJson", code: "MISSING_KEY" });
        }
        const trimmed = serviceAccountJson.trim();
        if (trimmed.length > 50 * 1024) {
            return res.status(400).json({ ok: false, error: "Service account JSON too large (max 50KB)", code: "KEY_TOO_LARGE" });
        }
        let parsed;
        try {
            parsed = JSON.parse(trimmed);
        }
        catch {
            return res.status(400).json({ ok: false, error: "Invalid JSON", code: "INVALID_JSON" });
        }
        if (parsed.type !== "service_account" || !parsed.project_id) {
            return res.status(400).json({
                ok: false,
                error: "JSON must be a Google Cloud service account (type: service_account, project_id required)",
                code: "INVALID_SERVICE_ACCOUNT",
            });
        }
        // Validate by initializing auth (catches key format issues)
        try {
            initVertexAuth(trimmed);
        }
        catch {
            return res.status(400).json({ ok: false, error: "Service account validation failed", code: "KEY_VALIDATION_FAILED" });
        }
        // Save to config.json
        const cfgPath = ctx.config.storage.configFile;
        let existing = {};
        try {
            existing = JSON.parse(await readFile(cfgPath, "utf-8"));
        }
        catch { /* new file */ }
        existing.vertexServiceAccountJson = trimmed;
        existing.geminiAuthMode = "vertex";
        await writeConfigAtomic(cfgPath, existing);
        // Hot-update runtime
        ctx.vertexServiceAccountJson = trimmed;
        ctx.vertexProjectId = parsed.project_id;
        ctx.hasVertexKey = true;
        ctx.geminiAuthMode = "vertex";
        return res.json({ ok: true, provider: "vertex", source: "config", valid: true, projectId: parsed.project_id });
    });
    app.delete("/api/keys/vertex", async (_req, res) => {
        const source = ctx.vertexServiceAccountJson
            ? (process.env.VERTEX_SERVICE_ACCOUNT_JSON ? "env" : "config")
            : "none";
        if (source === "env") {
            return res.status(400).json({ ok: false, error: "Cannot remove env-sourced key", code: "ENV_KEY_IMMUTABLE" });
        }
        const cfgPath = ctx.config.storage.configFile;
        let existing = {};
        try {
            existing = JSON.parse(await readFile(cfgPath, "utf-8"));
        }
        catch { /* ignore */ }
        delete existing.vertexServiceAccountJson;
        await writeConfigAtomic(cfgPath, existing);
        clearVertexAuth();
        ctx.vertexServiceAccountJson = undefined;
        ctx.vertexProjectId = undefined;
        ctx.hasVertexKey = false;
        return res.json({ ok: true, provider: "vertex", removed: true });
    });
    app.put("/api/keys/:provider", async (req, res) => {
        const { provider } = req.params;
        if (!isKeyProvider(provider)) {
            return res.status(400).json({ ok: false, error: "Invalid provider", code: "INVALID_PROVIDER" });
        }
        const { apiKey } = req.body;
        if (!apiKey || typeof apiKey !== "string" || apiKey.trim().length === 0) {
            return res.status(400).json({ ok: false, error: "Missing apiKey", code: "MISSING_KEY" });
        }
        const trimmed = apiKey.trim();
        if (trimmed.length > 512) {
            return res.status(400).json({ ok: false, error: "API key too large", code: "KEY_TOO_LARGE" });
        }
        // Format check
        const validPrefix = KEY_PREFIX_MAP[provider].some((p) => trimmed.startsWith(p));
        if (!validPrefix) {
            return res.status(400).json({
                ok: false,
                error: `Invalid key format for ${provider}: expected prefix ${KEY_PREFIX_MAP[provider].join(" or ")}`,
                code: "INVALID_KEY_FORMAT",
            });
        }
        // Validate against provider API
        try {
            const url = VALIDATE_URL_MAP[provider];
            const opts = { signal: AbortSignal.timeout(10_000) };
            if (provider === "gemini") {
                opts.headers = { "x-goog-api-key": trimmed };
                const validateRes = await fetch(url, opts);
                if (!validateRes.ok)
                    throw new Error(`HTTP ${validateRes.status}`);
            }
            else {
                opts.headers = { Authorization: `Bearer ${trimmed}` };
                const validateRes = await fetch(url, opts);
                if (!validateRes.ok)
                    throw new Error(`HTTP ${validateRes.status}`);
            }
        }
        catch (e) {
            return res.status(400).json({
                ok: false,
                error: `API key validation failed: ${e.message || "unknown"}`,
                code: "KEY_VALIDATION_FAILED",
            });
        }
        // Save to config.json
        const cfgPath = ctx.config.storage.configFile;
        let existing = {};
        try {
            existing = JSON.parse(await readFile(cfgPath, "utf-8"));
        }
        catch { /* new file */ }
        existing[CONFIG_KEY_MAP[provider]] = trimmed;
        await writeConfigAtomic(cfgPath, existing);
        // Hot-update runtime context
        if (provider === "openai") {
            ctx.apiKey = trimmed;
            ctx.apiKeySource = "config";
            ctx.hasApiKey = true;
            try {
                const OpenAI = (await import("openai")).default;
                ctx.openai = new OpenAI({ apiKey: trimmed });
            }
            catch { /* ignore */ }
        }
        else if (provider === "xai") {
            ctx.xaiApiKey = trimmed;
            ctx.xaiApiKeySource = "config";
            ctx.hasXaiApiKey = true;
        }
        else if (provider === "gemini") {
            ctx.geminiApiKey = trimmed;
            ctx.geminiApiKeySource = "config";
            ctx.hasGeminiApiKey = true;
            ctx.geminiAuthMode = "apikey";
            existing[CONFIG_KEY_MAP[provider]] = trimmed;
            existing.geminiAuthMode = "apikey";
        }
        return res.json({ ok: true, provider, source: "config", valid: true });
    });
    app.delete("/api/keys/:provider", async (req, res) => {
        const { provider } = req.params;
        if (!isKeyProvider(provider)) {
            return res.status(400).json({ ok: false, error: "Invalid provider", code: "INVALID_PROVIDER" });
        }
        const { source } = keySourceForProvider(ctx, provider);
        if (source === "env") {
            return res.status(400).json({ ok: false, error: "Cannot remove env-sourced key", code: "ENV_KEY_IMMUTABLE" });
        }
        // Remove from config.json
        const cfgPath = ctx.config.storage.configFile;
        let existing = {};
        try {
            existing = JSON.parse(await readFile(cfgPath, "utf-8"));
        }
        catch { /* ignore */ }
        delete existing[CONFIG_KEY_MAP[provider]];
        await writeConfigAtomic(cfgPath, existing);
        // Clear runtime
        if (provider === "openai") {
            ctx.apiKey = undefined;
            ctx.apiKeySource = "none";
            ctx.hasApiKey = false;
            ctx.openai = null;
        }
        else if (provider === "xai") {
            ctx.xaiApiKey = undefined;
            ctx.xaiApiKeySource = "none";
            ctx.hasXaiApiKey = false;
        }
        else if (provider === "gemini") {
            ctx.geminiApiKey = undefined;
            ctx.geminiApiKeySource = "none";
            ctx.hasGeminiApiKey = false;
        }
        return res.json({ ok: true, provider, removed: true });
    });
}
