import { z } from 'zod';
import { authMiddleware } from '../auth/middleware.js';
import { supabase } from '../utils/supabase.js';
import { listAllProviders, resolveActiveModel } from './registry.js';
import { MODELS, config } from '../config.js';
async function tenantIdFor(req) {
    if (req.tenant?.id)
        return req.tenant.id;
    if (!req.user?.id)
        return null;
    const { data } = await supabase
        .from('tenants')
        .select('id')
        .eq('owner_id', req.user.id)
        .order('created_at', { ascending: false })
        .limit(1);
    const row = data?.[0];
    return row?.id ?? null;
}
const setActiveBody = z.object({
    tier: z.enum(['brain', 'flash', 'reasoning']),
    provider: z.string().min(1),
    model: z.string().min(1),
});
export async function registerModelsRoutes(app) {
    // GET /api/models/providers — list everything
    app.get('/api/models/providers', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await tenantIdFor(req);
        if (!tid)
            return reply.status(401).send({ error: 'No tenant' });
        const providers = listAllProviders();
        reply.send({ providers });
    });
    // GET /api/models/active — currently selected per tier
    app.get('/api/models/active', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await tenantIdFor(req);
        if (!tid)
            return reply.status(401).send({ error: 'No tenant' });
        const [brain, flash, reasoning] = await Promise.all([
            resolveActiveModel(tid, 'brain'),
            resolveActiveModel(tid, 'flash'),
            resolveActiveModel(tid, 'reasoning'),
        ]);
        reply.send({
            brain: brain ?? { provider: MODELS.brain.provider, model: MODELS.brain.model, source: 'default' },
            flash: flash ?? { provider: MODELS.brain.provider, model: MODELS.brain.model, source: 'default' },
            reasoning: reasoning ?? { provider: MODELS.reasoning.provider, model: MODELS.reasoning.model, source: 'default' },
        });
    });
    // POST /api/models/active — set per-tenant preference
    app.post('/api/models/active', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await tenantIdFor(req);
        if (!tid)
            return reply.status(401).send({ error: 'No tenant' });
        const parsed = setActiveBody.safeParse(req.body);
        if (!parsed.success)
            return reply.status(400).send({ error: parsed.error.flatten() });
        const { tier, provider, model } = parsed.data;
        // Validate the chosen model actually exists in the registry
        const all = listAllProviders();
        const prov = all.find(p => p.name === provider);
        if (!prov)
            return reply.status(400).send({ error: `Unknown provider: ${provider}` });
        const m = prov.models.find(x => x.model === model);
        if (!m)
            return reply.status(400).send({ error: `Unknown model on ${provider}: ${model}` });
        if (!prov.available) {
            return reply.status(400).send({
                error: `${prov.display_name} is not configured. Paste your API key first (env var: ${prov.api_key_env}).`,
            });
        }
        const { upsertFact } = await import('../autonomy/identity.js');
        await upsertFact(tid, `preference.model.${tier}`, `${provider}:${model}`, 'preference', 'owner');
        reply.send({ ok: true, tier, provider, model });
    });
    // POST /api/models/test — verify a model with a 1-token round-trip
    app.post('/api/models/test', { preHandler: [authMiddleware] }, async (req, reply) => {
        const tid = await tenantIdFor(req);
        if (!tid)
            return reply.status(401).send({ error: 'No tenant' });
        const { provider, model } = (req.body ?? {});
        if (!provider || !model)
            return reply.status(400).send({ error: 'provider + model required' });
        try {
            const { content } = await invokeProviderOnce(provider, model, 'Reply with: ok');
            reply.send({ ok: true, content: content.slice(0, 200) });
        }
        catch (err) {
            reply.status(502).send({ ok: false, error: err.message });
        }
    });
    // ─── BYOK (Bring Your Own Key) endpoints ─────────────────────────
    // Persisted to <dataDir>/.env (the canonical user-writable env file —
    // see src/utils/load-env.ts for search order). Writes are atomic
    // (write to tmp, then rename) so a crash mid-write can't corrupt.
    // GET /api/models/keys — list which provider keys are currently set
    // (mask the actual value — return only the last 4 chars).
    app.get('/api/models/keys', { preHandler: [authMiddleware] }, async (_req, reply) => {
        const keys = [
            { env: 'ANTHROPIC_API_KEY', provider: 'anthropic', display: 'Anthropic Claude', prefix: 'sk-ant-', get_url: 'https://console.anthropic.com/settings/keys' },
            { env: 'OPENAI_API_KEY', provider: 'openai', display: 'OpenAI (GPT-4o)', prefix: 'sk-', get_url: 'https://platform.openai.com/api-keys' },
            { env: 'GOOGLE_AI_API_KEY', provider: 'gemini', display: 'Google Gemini', prefix: '', get_url: 'https://aistudio.google.com/apikey' },
            { env: 'DEEPSEEK_API_KEY', provider: 'deepseek', display: 'DeepSeek', prefix: 'sk-', get_url: 'https://platform.deepseek.com/api_keys' },
            { env: 'MOONSHOT_API_KEY', provider: 'kimi', display: 'Moonshot Kimi K2', prefix: 'sk-', get_url: 'https://platform.moonshot.cn/console/api-keys' },
            { env: 'TOGETHER_API_KEY', provider: 'together', display: 'Together AI (Qwen)', prefix: '', get_url: 'https://api.together.ai/settings/api-keys' },
            { env: 'NVIDIA_NIM_API_KEY', provider: 'nvidia-nim', display: 'NVIDIA NIM (optional)', prefix: 'nvapi-', get_url: 'https://build.nvidia.com/explore/discover' },
        ].map(k => {
            const v = process.env[k.env];
            const set = !!(v && v.length > 8 && !v.startsWith('mock_'));
            return { ...k, set, last4: set ? v.slice(-4) : null };
        });
        reply.send({ keys });
    });
    // POST /api/models/keys — set a provider key. Body: { env, value }
    app.post('/api/models/keys', { preHandler: [authMiddleware] }, async (req, reply) => {
        const body = (req.body ?? {});
        const ALLOWED = new Set([
            'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GOOGLE_AI_API_KEY',
            'DEEPSEEK_API_KEY', 'MOONSHOT_API_KEY', 'TOGETHER_API_KEY',
            'NVIDIA_NIM_API_KEY',
        ]);
        if (!body.env || !ALLOWED.has(body.env)) {
            return reply.status(400).send({ error: 'env must be one of the supported keys' });
        }
        const value = String(body.value ?? '').trim();
        if (value.length < 16) {
            return reply.status(400).send({ error: 'value looks too short to be a real API key' });
        }
        try {
            await persistEnvKey(body.env, value);
            process.env[body.env] = value;
            // CRITICAL: every provider reads its key from the `config` object
            // (parsed once at boot), NOT from process.env. Without updating
            // config live, a pasted key stays invisible — the provider throws
            // "API key not configured" and the chain falls back to the default.
            // This is the core reason "I added my key but it still won't connect".
            config[body.env] = value;
            // If the user pastes their own NVIDIA/Ollama key, stop pointing at
            // the bundled Ollama base URL unless they set their own — let the
            // direct-NVIDIA default kick in on next boot. (Their key + base get
            // re-resolved by config on restart; the chain rebuild below makes
            // the provider live for this process.)
            // Rebuild the failover chain so the new key takes effect NOW,
            // without requiring an app restart. This is the fix for "I added
            // my API key in Settings but it still won't connect".
            try {
                const { rebuildFailoverChains } = await import('./failover.js');
                await rebuildFailoverChains();
            }
            catch { /* non-fatal: key is persisted; restart will pick it up */ }
            reply.send({ ok: true, env: body.env, last4: value.slice(-4) });
        }
        catch (err) {
            reply.status(500).send({ error: err.message });
        }
    });
    // DELETE /api/models/keys/:env — clear a provider key
    app.delete('/api/models/keys/:env', { preHandler: [authMiddleware] }, async (req, reply) => {
        const ALLOWED = new Set([
            'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GOOGLE_AI_API_KEY',
            'DEEPSEEK_API_KEY', 'MOONSHOT_API_KEY', 'TOGETHER_API_KEY',
            'NVIDIA_NIM_API_KEY',
        ]);
        if (!ALLOWED.has(req.params.env)) {
            return reply.status(400).send({ error: 'unknown key' });
        }
        try {
            await persistEnvKey(req.params.env, ''); // empty value = remove line
            delete process.env[req.params.env];
            delete config[req.params.env]; // keep the live config in sync (see POST)
            // Rebuild so the removed provider drops out of the chain now.
            try {
                const { rebuildFailoverChains } = await import('./failover.js');
                await rebuildFailoverChains();
            }
            catch { /* non-fatal */ }
            reply.send({ ok: true });
        }
        catch (err) {
            reply.status(500).send({ error: err.message });
        }
    });
}
/**
 * Persist a key=value pair to <dataDir>/.env atomically. Empty value removes
 * the line. Other lines in the file are preserved verbatim.
 *
 * The dataDir .env is the canonical user-writable env per the search order in
 * src/utils/load-env.ts — gateway processes pick it up on next restart, and
 * we also set process.env[key] = value in the caller so the current process
 * sees the new key immediately (failover/route resolution re-reads env per
 * provider invocation).
 */
async function persistEnvKey(envName, value) {
    const { getDataDir } = await import('../utils/data-dir.js');
    const { readFileSync, writeFileSync, existsSync, renameSync, chmodSync } = await import('node:fs');
    const { join } = await import('node:path');
    const envPath = join(getDataDir(), '.env');
    const existing = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
    const lines = existing.split('\n');
    const keyLine = `${envName}=${value}`;
    let replaced = false;
    const out = [];
    for (const line of lines) {
        if (line.startsWith(`${envName}=`) || line.startsWith(`${envName} =`)) {
            if (value) {
                out.push(keyLine);
                replaced = true;
            }
            // else: drop the line entirely
        }
        else {
            out.push(line);
        }
    }
    if (!replaced && value) {
        if (out.length > 0 && out[out.length - 1] !== '')
            out.push('');
        out.push(keyLine);
    }
    const tmpPath = `${envPath}.tmp`;
    writeFileSync(tmpPath, out.join('\n'), { encoding: 'utf-8', mode: 0o600 });
    renameSync(tmpPath, envPath);
    try {
        chmodSync(envPath, 0o600);
    }
    catch { /* best effort on Windows */ }
}
async function invokeProviderOnce(provider, model, prompt) {
    const opts = { temperature: 0, maxTokens: 16, timeoutMs: 20_000 };
    if (provider === 'anthropic') {
        const { anthropic } = await import('./anthropic.js');
        const prev = anthropic.model;
        anthropic.setModel(model);
        try {
            const r = await anthropic.chat([{ role: 'user', content: prompt }], opts);
            return { content: r.content };
        }
        finally {
            anthropic.model = prev;
        }
    }
    if (provider === 'openai') {
        const { openai } = await import('./openai.js');
        const prev = openai.model;
        openai.setModel(model);
        try {
            const r = await openai.chat([{ role: 'user', content: prompt }], opts);
            return { content: r.content };
        }
        finally {
            openai.model = prev;
        }
    }
    if (provider === 'kimi') {
        const { kimi } = await import('./kimi.js');
        const prev = kimi.model;
        kimi.setModel(model);
        try {
            const r = await kimi.chat([{ role: 'user', content: prompt }], opts);
            return { content: r.content };
        }
        finally {
            kimi.model = prev;
        }
    }
    if (provider === 'nvidia-nim') {
        const { nvidia } = await import('./nvidia.js');
        const r = await nvidia.chat([{ role: 'user', content: prompt }], opts);
        return { content: r.content };
    }
    throw new Error(`Test not wired for provider: ${provider}`);
}
//# sourceMappingURL=routes.js.map