import { execFile, spawn } from 'node:child_process';
import { promisify } from 'node:util';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { getEngine } from '../config/engine-config.js';
const execFileAsync = promisify(execFile);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/** Resolve the current installed version from package.json */
function resolveCurrentVersion(envVersion) {
    // 1. Env var set by CLI (production)
    if (envVersion && envVersion !== '0.0.0')
        return envVersion;
    // 2. Walk up from this file to find expxagents package.json
    //    Production: server/dist/api/system-routes.js → ../../package.json (name=expxagents)
    //    Dev: server/src/api/system-routes.ts → ../../../cli/package.json
    let dir = __dirname;
    while (dir !== path.dirname(dir)) {
        const pkgPath = path.join(dir, 'package.json');
        if (fs.existsSync(pkgPath)) {
            try {
                const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
                if (pkg.name === 'expxagents')
                    return pkg.version ?? '0.0.0';
            }
            catch { /* continue */ }
        }
        dir = path.dirname(dir);
    }
    // 3. Dev fallback: try ../cli/package.json from project root
    try {
        const devCliPkg = path.join(__dirname, '..', '..', '..', 'cli', 'package.json');
        if (fs.existsSync(devCliPkg)) {
            const pkg = JSON.parse(fs.readFileSync(devCliPkg, 'utf-8'));
            if (pkg.name === 'expxagents')
                return pkg.version ?? '0.0.0';
        }
    }
    catch { /* ignore */ }
    return envVersion || '0.0.0';
}
async function fetchLatestVersion() {
    try {
        const { stdout } = await execFileAsync('npm', ['view', 'expxagents', 'version'], {
            timeout: 10_000,
        });
        return stdout.trim() || null;
    }
    catch {
        return null;
    }
}
function compareVersions(current, latest) {
    const c = current.split('.').map(Number);
    const l = latest.split('.').map(Number);
    for (let i = 0; i < 3; i++) {
        if ((l[i] ?? 0) > (c[i] ?? 0))
            return true;
        if ((l[i] ?? 0) < (c[i] ?? 0))
            return false;
    }
    return false;
}
export async function systemRoutes(app, opts) {
    const packageVersion = resolveCurrentVersion(opts.packageVersion);
    // GET /api/system/info — server discovery (no auth needed)
    app.get('/api/system/info', async (_request, reply) => {
        const address = app.server.address();
        const port = typeof address === 'string' ? address : address?.port ?? 3001;
        const proto = process.env.NODE_ENV === 'production' ? 'https' : 'http';
        const host = app.server._events?.request?._socket?.remoteAddress
            ?? _request.headers.host?.split(':')[0]
            ?? '127.0.0.1';
        return reply.send({
            port,
            url: `${proto}://${host}:${port}`,
            wsUrl: `${proto === 'https' ? 'wss' : 'ws'}://${host}:${port}`,
            version: packageVersion,
            engine: getEngine(),
        });
    });
    // PATCH /api/system/engine — hot-swap engine (no restart needed)
    app.patch('/api/system/engine', {
        preHandler: [app.requireAuth],
    }, async (request, reply) => {
        const { engine } = request.body ?? {};
        if (!engine || !['claude-code', 'opencode', 'auto'].includes(engine)) {
            return reply.code(400).send({ error: 'Invalid engine. Use: claude-code, opencode, or auto' });
        }
        const currentEngine = opts.engineManager?.engineType ?? getEngine();
        if (currentEngine === engine) {
            return reply.send({ ok: true, engine, message: 'Engine already set', restarted: false, hotSwap: false });
        }
        // Kill active sessions from old engine
        const activeSquads = opts.engineManager?.getActiveSquads() ?? [];
        console.log(`[system] Switching engine from "${currentEngine}" to "${engine}"...`);
        if (activeSquads.length > 0) {
            console.log(`[system] Aborting ${activeSquads.length} active session(s): ${activeSquads.join(', ')}`);
        }
        // Hot-swap the engine
        opts.engineManager?.switchEngine(engine);
        return reply.send({
            ok: true,
            engine,
            message: `Engine switched to ${engine}`,
            restarted: false,
            hotSwap: true,
            abortedSessions: activeSquads.length,
        });
    });
    // GET /api/system/models — list available models from OpenCode
    app.get('/api/system/models', async (_request, reply) => {
        const engine = process.env.AGENT_ENGINE ?? 'auto';
        if (engine === 'claude-code') {
            return reply.send({
                engine: 'claude-code',
                models: [
                    { id: 'claude-opus-4-6', name: 'Claude Opus 4', provider: 'anthropic' },
                    { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4', provider: 'anthropic' },
                    { id: 'claude-haiku-4-5', name: 'Claude Haiku 4', provider: 'anthropic' },
                ],
                default: 'claude-sonnet-4-6',
            });
        }
        const opencodeUrl = process.env.OPENCODE_BASE_URL ?? 'http://127.0.0.1:4096';
        try {
            const res = await fetch(`${opencodeUrl}/provider`, {
                headers: { 'Content-Type': 'application/json' },
            });
            if (!res.ok) {
                return reply.code(502).send({ error: 'Failed to fetch models from OpenCode' });
            }
            const data = await res.json();
            const models = [];
            for (const provider of data.all) {
                for (const model of Object.values(provider.models)) {
                    if (model.capabilities?.toolcall) {
                        models.push({
                            id: model.id,
                            name: model.name,
                            provider: provider.name,
                            hasTools: true,
                            cost: model.cost ?? { input: 0, output: 0 },
                        });
                    }
                }
            }
            const defaultModel = data.default?.default ?? Object.keys(data.all[0]?.models ?? {})[0] ?? '';
            return reply.send({
                engine: 'opencode',
                models,
                default: defaultModel,
            });
        }
        catch (err) {
            return reply.code(502).send({ error: `OpenCode unavailable: ${err instanceof Error ? err.message : String(err)}` });
        }
    });
    // GET /api/system/version — check current vs latest
    app.get('/api/system/version', {
        preHandler: [app.requireAuth],
    }, async (_request, reply) => {
        const latest = await fetchLatestVersion();
        const updateAvailable = latest ? compareVersions(packageVersion, latest) : false;
        return reply.send({
            current: packageVersion,
            latest: latest ?? packageVersion,
            updateAvailable,
        });
    });
    // POST /api/system/update — update and restart
    app.post('/api/system/update', {
        preHandler: [app.requireAuth],
    }, async (_request, reply) => {
        const latest = await fetchLatestVersion();
        if (!latest || !compareVersions(packageVersion, latest)) {
            return reply.send({ ok: false, message: 'Already up to date' });
        }
        reply.send({ ok: true, message: `Updating to ${latest}...` });
        // Run update and restart in background after response is sent
        setTimeout(async () => {
            try {
                const port = process.env.PORT ?? '3001';
                const cwd = process.cwd();
                const runtimeDir = path.join(cwd, '.expxagents');
                fs.mkdirSync(runtimeDir, { recursive: true });
                const logFile = path.join(runtimeDir, 'update.log');
                const scriptFile = path.join(runtimeDir, 'restart.sh');
                // Find npx absolute path so detached process can find it
                const { stdout: npxPath } = await execFileAsync('which', ['npx']);
                const npx = npxPath.trim();
                console.log(`[system] Preparing update to expxagents@${latest}...`);
                // Preserve DATA_DIR so the new process uses the same database
                const dataDir = process.env.DATA_DIR ?? '';
                // Write a self-contained restart script
                const script = [
                    '#!/bin/sh',
                    `LOG="${logFile}"`,
                    '# Clear old version so the new CLI detects its own version',
                    'unset EXPXAGENTS_VERSION',
                    `export DATA_DIR="${dataDir}"`,
                    `echo "[$(date)] Starting update to expxagents@${latest}" > "$LOG"`,
                    'sleep 3',
                    `echo "[$(date)] Running npx expxagents@${latest} server" >> "$LOG"`,
                    `exec ${npx} --yes expxagents@${latest} server >> "$LOG" 2>&1`,
                ].join('\n');
                fs.writeFileSync(scriptFile, script, { mode: 0o755 });
                // Remove old version env var so it doesn't leak to new process
                const { EXPXAGENTS_VERSION: _, ...restEnv } = process.env;
                const cleanEnv = { ...restEnv, PORT: port };
                const child = spawn(scriptFile, [], {
                    cwd,
                    detached: true,
                    stdio: 'ignore',
                    env: cleanEnv,
                });
                child.unref();
                console.log(`[system] Restart script created at ${scriptFile}`);
                console.log(`[system] Log at ${logFile}`);
                console.log(`[system] Exiting current server...`);
                // Use exit code 120 to signal "update restart" — CLI won't auto-restart on non-zero codes
                process.exit(120);
            }
            catch (err) {
                console.error('[system] Update failed:', err);
            }
        }, 500);
    });
    // POST /api/system/restart — restart server (same version)
    app.post('/api/system/restart', {
        preHandler: [app.requireAuth],
    }, async (_request, reply) => {
        reply.send({ ok: true, message: 'Restarting server...' });
        setTimeout(() => {
            console.log('[system] Restart requested via dashboard');
            // Exit code 0 triggers CLI restart loop (same version)
            process.exit(0);
        }, 500);
    });
}
//# sourceMappingURL=system-routes.js.map