import path from 'path';
import http from 'http';
import fs from 'fs';
import mime from 'mime-types';
import express from 'express';
import { scanPlugins, getPluginsConfig, getPluginsDir, savePluginsConfig, getPluginDir, resolvePluginAssetPath, installPluginFromGit, updatePluginFromGit, uninstallPlugin, } from '../utils/plugin-loader.js';
import { startPluginServer, stopPluginServer, getPluginPort, isPluginRunning, } from '../utils/plugin-process-manager.js';
const router = express.Router();
// GET / — List all installed plugins (includes server running status)
router.get('/', (req, res) => {
    try {
        const plugins = scanPlugins().map(p => ({
            ...p,
            serverRunning: p.server ? isPluginRunning(p.name) : false,
        }));
        res.json({ plugins });
    }
    catch (err) {
        res.status(500).json({ error: 'Failed to scan plugins', details: err.message });
    }
});
// GET /:name/manifest — Get single plugin manifest
router.get('/:name/manifest', (req, res) => {
    try {
        if (!/^[a-zA-Z0-9_-]+$/.test(req.params.name)) {
            return res.status(400).json({ error: 'Invalid plugin name' });
        }
        const plugins = scanPlugins();
        const plugin = plugins.find(p => p.name === req.params.name);
        if (!plugin) {
            return res.status(404).json({ error: 'Plugin not found' });
        }
        res.json(plugin);
    }
    catch (err) {
        res.status(500).json({ error: 'Failed to read plugin manifest', details: err.message });
    }
});
// GET /:name/assets/* — Serve plugin static files.
//
// The pattern is written as a literal RegExp rather than an Express route
// string because path-to-regexp v8 (pulled in as a transitive dep by any
// Express 5 beta / Router v2) rejects the `*` unnamed wildcard with
// "Missing parameter name at index 15" and the app refuses to boot. A
// regex sidesteps path-to-regexp entirely and works on every version of
// Express / path-to-regexp we've tested. Capture groups land in
// req.params[0] / [1] — same wire as the old `:name` + `*` would give us.
router.get(/^\/([a-zA-Z0-9_-]+)\/assets\/(.*)$/, (req, res) => {
    const pluginName = req.params[0];
    if (!/^[a-zA-Z0-9_-]+$/.test(pluginName)) {
        return res.status(400).json({ error: 'Invalid plugin name' });
    }
    const assetPath = req.params[1];
    if (!assetPath) {
        return res.status(400).json({ error: 'No asset path specified' });
    }
    const resolvedPath = resolvePluginAssetPath(pluginName, assetPath);
    if (!resolvedPath) {
        return res.status(404).json({ error: 'Asset not found' });
    }
    try {
        const stat = fs.statSync(resolvedPath);
        if (!stat.isFile()) {
            return res.status(404).json({ error: 'Asset not found' });
        }
    }
    catch {
        return res.status(404).json({ error: 'Asset not found' });
    }
    const contentType = mime.lookup(resolvedPath) || 'application/octet-stream';
    res.setHeader('Content-Type', contentType);
    // Prevent CDN/proxy caching of plugin assets so updates take effect immediately
    res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Expires', '0');
    const stream = fs.createReadStream(resolvedPath);
    stream.on('error', () => {
        if (!res.headersSent) {
            res.status(500).json({ error: 'Failed to read asset' });
        }
        else {
            res.end();
        }
    });
    stream.pipe(res);
});
// PUT /:name/enable — Toggle plugin enabled/disabled (starts/stops server if applicable)
router.put('/:name/enable', async (req, res) => {
    try {
        const { enabled } = req.body;
        if (typeof enabled !== 'boolean') {
            return res.status(400).json({ error: '"enabled" must be a boolean' });
        }
        const plugins = scanPlugins();
        const plugin = plugins.find(p => p.name === req.params.name);
        if (!plugin) {
            return res.status(404).json({ error: 'Plugin not found' });
        }
        const config = getPluginsConfig();
        config[req.params.name] = { ...config[req.params.name], enabled };
        savePluginsConfig(config);
        // Start or stop the plugin server as needed
        if (plugin.server) {
            if (enabled && !isPluginRunning(plugin.name)) {
                const pluginDir = getPluginDir(plugin.name);
                if (pluginDir) {
                    try {
                        await startPluginServer(plugin.name, pluginDir, plugin.server);
                    }
                    catch (err) {
                        console.error(`[Plugins] Failed to start server for "${plugin.name}":`, err.message);
                    }
                }
            }
            else if (!enabled && isPluginRunning(plugin.name)) {
                await stopPluginServer(plugin.name);
            }
        }
        res.json({ success: true, name: req.params.name, enabled });
    }
    catch (err) {
        res.status(500).json({ error: 'Failed to update plugin', details: err.message });
    }
});
// POST /install — Install plugin from git URL
router.post('/install', async (req, res) => {
    try {
        const { url } = req.body;
        if (!url || typeof url !== 'string') {
            return res.status(400).json({ error: '"url" is required and must be a string' });
        }
        // Basic URL validation
        if (!url.startsWith('https://') && !url.startsWith('git@')) {
            return res.status(400).json({ error: 'URL must start with https:// or git@' });
        }
        const manifest = await installPluginFromGit(url);
        // Auto-start the server if the plugin has one (enabled by default)
        if (manifest.server) {
            const pluginDir = getPluginDir(manifest.name);
            if (pluginDir) {
                try {
                    await startPluginServer(manifest.name, pluginDir, manifest.server);
                }
                catch (err) {
                    console.error(`[Plugins] Failed to start server for "${manifest.name}":`, err.message);
                }
            }
        }
        res.json({ success: true, plugin: manifest });
    }
    catch (err) {
        res.status(400).json({ error: 'Failed to install plugin', details: err.message });
    }
});
// POST /:name/update — Pull latest from git (restarts server if running)
router.post('/:name/update', async (req, res) => {
    try {
        const pluginName = req.params.name;
        if (!/^[a-zA-Z0-9_-]+$/.test(pluginName)) {
            return res.status(400).json({ error: 'Invalid plugin name' });
        }
        const wasRunning = isPluginRunning(pluginName);
        if (wasRunning) {
            await stopPluginServer(pluginName);
        }
        const manifest = await updatePluginFromGit(pluginName);
        // Restart server if it was running before the update
        if (wasRunning && manifest.server) {
            const pluginDir = getPluginDir(pluginName);
            if (pluginDir) {
                try {
                    await startPluginServer(pluginName, pluginDir, manifest.server);
                }
                catch (err) {
                    console.error(`[Plugins] Failed to restart server for "${pluginName}":`, err.message);
                }
            }
        }
        res.json({ success: true, plugin: manifest });
    }
    catch (err) {
        res.status(400).json({ error: 'Failed to update plugin', details: err.message });
    }
});
// ALL /:name/rpc/* — Proxy requests to plugin's server subprocess.
// Same path-to-regexp v8 avoidance trick as /:name/assets/* above — we
// use a RegExp directly so the router can't invoke path-to-regexp on
// the unnamed wildcard.
router.all(/^\/([a-zA-Z0-9_-]+)\/rpc\/(.*)$/, async (req, res) => {
    const pluginName = req.params[0];
    const rpcPath = req.params[1] || '';
    if (!/^[a-zA-Z0-9_-]+$/.test(pluginName)) {
        return res.status(400).json({ error: 'Invalid plugin name' });
    }
    let port = getPluginPort(pluginName);
    if (!port) {
        // Lazily start the plugin server if it exists and is enabled
        const plugins = scanPlugins();
        const plugin = plugins.find(p => p.name === pluginName);
        if (!plugin || !plugin.server) {
            return res.status(503).json({ error: 'Plugin server is not running' });
        }
        if (!plugin.enabled) {
            return res.status(503).json({ error: 'Plugin is disabled' });
        }
        const pluginDir = path.join(getPluginsDir(), plugin.dirName);
        try {
            port = await startPluginServer(pluginName, pluginDir, plugin.server);
        }
        catch (err) {
            return res.status(503).json({ error: 'Plugin server failed to start', details: err.message });
        }
    }
    // Inject configured secrets as headers
    const config = getPluginsConfig();
    const pluginConfig = config[pluginName] || {};
    const secrets = pluginConfig.secrets || {};
    const headers = {
        'content-type': req.headers['content-type'] || 'application/json',
    };
    // Add per-plugin user-configured secrets as X-Plugin-Secret-* headers
    for (const [key, value] of Object.entries(secrets)) {
        headers[`x-plugin-secret-${key.toLowerCase()}`] = String(value);
    }
    // Reconstruct query string
    const qs = req.url.includes('?') ? '?' + req.url.split('?').slice(1).join('?') : '';
    const options = {
        hostname: '127.0.0.1',
        port,
        path: `/${rpcPath}${qs}`,
        method: req.method,
        headers,
    };
    const proxyReq = http.request(options, (proxyRes) => {
        res.writeHead(proxyRes.statusCode, proxyRes.headers);
        proxyRes.pipe(res);
    });
    proxyReq.on('error', (err) => {
        if (!res.headersSent) {
            res.status(502).json({ error: 'Plugin server error', details: err.message });
        }
        else {
            res.end();
        }
    });
    // Forward body (already parsed by express JSON middleware, so re-stringify).
    // Check content-length to detect whether a body was actually sent, since
    // req.body can be falsy for valid payloads like 0, false, null, or {}.
    const hasBody = req.headers['content-length'] && parseInt(req.headers['content-length'], 10) > 0;
    if (hasBody && req.body !== undefined) {
        const bodyStr = JSON.stringify(req.body);
        proxyReq.setHeader('content-length', Buffer.byteLength(bodyStr));
        proxyReq.write(bodyStr);
    }
    proxyReq.end();
});
// DELETE /:name — Uninstall plugin (stops server first)
router.delete('/:name', async (req, res) => {
    try {
        const pluginName = req.params.name;
        // Validate name format to prevent path traversal
        if (!/^[a-zA-Z0-9_-]+$/.test(pluginName)) {
            return res.status(400).json({ error: 'Invalid plugin name' });
        }
        // Stop server and wait for the process to fully exit before deleting files
        if (isPluginRunning(pluginName)) {
            await stopPluginServer(pluginName);
        }
        await uninstallPlugin(pluginName);
        res.json({ success: true, name: pluginName });
    }
    catch (err) {
        res.status(400).json({ error: 'Failed to uninstall plugin', details: err.message });
    }
});
export default router;
//# sourceMappingURL=plugins.js.map