/*
 * Libre WebUI
 * Copyright (C) 2025 Kroonen AI, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import express from 'express';
import multer from 'multer';
import path from 'path';
import fs from 'fs';
import rateLimit from 'express-rate-limit';
import pluginService from '../services/pluginService.js';
import pluginCredentialsService from '../services/pluginCredentialsService.js';
import pluginVariablesService from '../services/pluginVariablesService.js';
import { getErrorMessage, } from '../types/index.js';
const router = express.Router();
// Rate limiting for plugin operations
const pluginRateLimit = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // Limit each IP to 100 requests per windowMs
    message: {
        success: false,
        error: 'Too many plugin requests from this IP, please try again later.',
    },
});
// More restrictive rate limiting for upload operations
const uploadRateLimit = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 10, // Limit each IP to 10 uploads per windowMs
    message: {
        success: false,
        error: 'Too many upload requests from this IP, please try again later.',
    },
});
// Helper function to safely clean up uploaded files
const safeCleanupFile = (filePath, tempDir) => {
    try {
        const resolvedPath = path.resolve(filePath);
        const resolvedTempDir = path.resolve(tempDir);
        // Ensure the file is within the temp directory
        if (resolvedPath.startsWith(resolvedTempDir) &&
            fs.existsSync(resolvedPath)) {
            fs.unlinkSync(resolvedPath);
        }
    }
    catch (error) {
        console.error('Failed to cleanup file:', error);
    }
};
// Configure multer for file uploads
const upload = multer({
    dest: 'temp/',
    fileFilter: (req, file, cb) => {
        const allowedTypes = ['.json', '.zip'];
        const fileExt = path.extname(file.originalname).toLowerCase();
        if (allowedTypes.includes(fileExt)) {
            cb(null, true);
        }
        else {
            cb(new Error('Only .json and .zip files are allowed'));
        }
    },
    limits: {
        fileSize: 10 * 1024 * 1024, // 10MB limit
    },
});
// Get all plugins
router.get('/', async (req, res) => {
    try {
        const plugins = pluginService.getAllPlugins();
        res.json({
            success: true,
            data: plugins,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to load plugins'),
        });
    }
});
// Get active plugins
router.get('/active', async (req, res) => {
    try {
        const activePlugins = pluginService.getActivePlugins();
        res.json({
            success: true,
            data: activePlugins,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to get active plugins'),
        });
    }
});
// Get active plugin (alternative route)
router.get('/active/current', async (req, res) => {
    try {
        const activePlugin = pluginService.getActivePlugin();
        res.json({
            success: true,
            data: activePlugin,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to get active plugin'),
        });
    }
});
// Get plugin status
router.get('/status/all', async (req, res) => {
    try {
        const status = pluginService.getPluginStatus();
        res.json({
            success: true,
            data: status,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to get plugin status'),
        });
    }
});
// Get a specific plugin
router.get('/:id', async (req, res) => {
    try {
        const id = req.params.id;
        const plugin = pluginService.getPlugin(id);
        if (!plugin) {
            res.status(404).json({
                success: false,
                error: 'Plugin not found',
            });
            return;
        }
        res.json({
            success: true,
            data: plugin,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to get plugin'),
        });
    }
});
// Upload and install a plugin
router.post('/upload', uploadRateLimit, upload.single('plugin'), async (req, res) => {
    const tempDir = path.resolve('temp/');
    try {
        if (!req.file) {
            res.status(400).json({
                success: false,
                error: 'No file uploaded',
            });
            return;
        }
        // Validate file path is within temp directory
        const resolvedFilePath = path.resolve(req.file.path);
        if (!resolvedFilePath.startsWith(tempDir)) {
            safeCleanupFile(req.file.path, tempDir);
            res.status(400).json({
                success: false,
                error: 'Invalid file path',
            });
            return;
        }
        const fileExt = path.extname(req.file.originalname).toLowerCase();
        let pluginData;
        if (fileExt === '.json') {
            // Handle JSON file with safe path handling
            const fileContent = fs.readFileSync(resolvedFilePath, 'utf8');
            pluginData = JSON.parse(fileContent);
        }
        else if (fileExt === '.zip') {
            // Handle ZIP file (for future extension)
            safeCleanupFile(req.file.path, tempDir);
            res.status(400).json({
                success: false,
                error: 'ZIP file support is not implemented yet',
            });
            return;
        }
        else {
            // Clean up invalid file type
            safeCleanupFile(req.file.path, tempDir);
            res.status(400).json({
                success: false,
                error: 'Unsupported file type',
            });
            return;
        }
        // Clean up uploaded file after reading
        safeCleanupFile(req.file.path, tempDir);
        // Install the plugin
        const plugin = pluginService.importPlugin(pluginData);
        res.json({
            success: true,
            data: plugin,
        });
    }
    catch (error) {
        // Clean up uploaded file if it exists
        if (req.file) {
            safeCleanupFile(req.file.path, tempDir);
        }
        const errorMessage = getErrorMessage(error, 'Failed to upload plugin');
        if (errorMessage.includes('already exists')) {
            res.status(409).json({
                success: false,
                error: errorMessage,
            });
        }
        else if (errorMessage.includes('Invalid')) {
            res.status(400).json({
                success: false,
                error: errorMessage,
            });
        }
        else {
            res.status(500).json({
                success: false,
                error: errorMessage,
            });
        }
    }
});
// Install plugin from JSON data (alternative to file upload)
router.post('/install', pluginRateLimit, async (req, res) => {
    try {
        const pluginData = req.body;
        const plugin = pluginService.installPlugin(pluginData);
        res.json({
            success: true,
            data: plugin,
        });
    }
    catch (error) {
        const errorMessage = getErrorMessage(error, 'Failed to install plugin');
        if (errorMessage.includes('already exists')) {
            res.status(409).json({
                success: false,
                error: errorMessage,
            });
        }
        else if (errorMessage.includes('Invalid')) {
            res.status(400).json({
                success: false,
                error: errorMessage,
            });
        }
        else {
            res.status(500).json({
                success: false,
                error: errorMessage,
            });
        }
    }
});
// Update a plugin
router.put('/:id', async (req, res) => {
    try {
        const id = req.params.id;
        const updates = req.body;
        // Ensure the ID matches
        if (updates.id && updates.id !== id) {
            res.status(400).json({
                success: false,
                error: 'Plugin ID in body does not match URL parameter',
            });
            return;
        }
        updates.id = id;
        const plugin = pluginService.installPlugin(updates);
        res.json({
            success: true,
            data: plugin,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to update plugin'),
        });
    }
});
// Delete a plugin
router.delete('/:id', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        const success = pluginService.deletePlugin(id);
        if (!success) {
            res.status(404).json({
                success: false,
                error: 'Plugin not found',
            });
            return;
        }
        res.json({
            success: true,
            data: true,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to delete plugin'),
        });
    }
});
// Activate a plugin
router.post('/activate/:id', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        const success = pluginService.activatePlugin(id);
        res.json({
            success: true,
            data: success,
        });
    }
    catch (error) {
        const errorMessage = getErrorMessage(error, 'Failed to activate plugin');
        if (errorMessage.includes('not found')) {
            res.status(404).json({
                success: false,
                error: errorMessage,
            });
        }
        else {
            res.status(500).json({
                success: false,
                error: errorMessage,
            });
        }
    }
});
// Discover available models from a plugin's API
router.post('/discover/:id', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        const models = await pluginService.discoverModels(id);
        res.json({
            success: true,
            data: models,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to discover models'),
        });
    }
});
// Deactivate a specific plugin
router.post('/deactivate/:id', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        const success = pluginService.deactivatePlugin(id);
        res.json({
            success: true,
            data: success,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to deactivate plugin'),
        });
    }
});
// Deactivate all plugins (legacy)
router.post('/deactivate', pluginRateLimit, async (req, res) => {
    try {
        const success = pluginService.deactivatePlugin();
        res.json({
            success: true,
            data: success,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to deactivate plugin'),
        });
    }
});
// Export plugin
router.get('/:id/export', async (req, res) => {
    try {
        const id = req.params.id;
        const plugin = pluginService.exportPlugin(id);
        if (!plugin) {
            res.status(404).json({
                success: false,
                error: 'Plugin not found',
            });
            return;
        }
        // Remove runtime properties for export
        const exportData = {
            ...plugin,
            active: undefined,
            created_at: undefined,
            updated_at: undefined,
        };
        res.setHeader('Content-Type', 'application/json');
        res.setHeader('Content-Disposition', `attachment; filename="${id}.json"`);
        res.send(JSON.stringify(exportData, null, 2));
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to export plugin'),
        });
    }
});
// ============================================
// Plugin Credentials Endpoints
// ============================================
// Get all credentials for current user (API keys are masked)
router.get('/credentials/all', async (req, res) => {
    try {
        // Get userId from auth context (defaults to 'default' for single-user mode)
        const userId = req.userId || 'default';
        const credentials = pluginCredentialsService.getCredentials(userId);
        res.json({
            success: true,
            data: credentials,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to get plugin credentials'),
        });
    }
});
// Set API key for a plugin
router.post('/:id/credentials', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        const { api_key } = req.body;
        if (!api_key || typeof api_key !== 'string') {
            res.status(400).json({
                success: false,
                error: 'API key is required',
            });
            return;
        }
        // Verify plugin exists
        const plugin = pluginService.getPlugin(id);
        if (!plugin) {
            res.status(404).json({
                success: false,
                error: 'Plugin not found',
            });
            return;
        }
        // Get userId from auth context
        const userId = req.userId || 'default';
        const success = pluginCredentialsService.setApiKey(id, api_key, userId);
        if (success) {
            res.json({
                success: true,
                data: true,
            });
        }
        else {
            res.status(500).json({
                success: false,
                error: 'Failed to save API key',
            });
        }
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to set API key'),
        });
    }
});
// Delete API key for a plugin
router.delete('/:id/credentials', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        // Verify plugin exists
        const plugin = pluginService.getPlugin(id);
        if (!plugin) {
            res.status(404).json({
                success: false,
                error: 'Plugin not found',
            });
            return;
        }
        // Get userId from auth context
        const userId = req.userId || 'default';
        const success = pluginCredentialsService.deleteApiKey(id, userId);
        res.json({
            success: true,
            data: success,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to delete API key'),
        });
    }
});
// Check if API key exists for a plugin (without revealing the key)
router.get('/:id/credentials/check', async (req, res) => {
    try {
        const id = req.params.id;
        // Verify plugin exists
        const plugin = pluginService.getPlugin(id);
        if (!plugin) {
            res.status(404).json({
                success: false,
                error: 'Plugin not found',
            });
            return;
        }
        // Get userId from auth context
        const userId = req.userId || 'default';
        const hasKey = pluginCredentialsService.hasApiKey(id, plugin.auth.key_env, userId);
        res.json({
            success: true,
            data: hasKey,
        });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to check API key'),
        });
    }
});
// ============================================================================
// Plugin Variables (Valves)
// ============================================================================
// Get variable values for a plugin
router.get('/:id/variables', async (req, res) => {
    try {
        const id = req.params.id;
        const plugin = pluginService.getPlugin(id);
        if (!plugin) {
            res.status(404).json({ success: false, error: 'Plugin not found' });
            return;
        }
        if (!plugin.variables || plugin.variables.length === 0) {
            res.json({ success: true, data: {} });
            return;
        }
        const userId = req.userId || 'default';
        const variables = pluginVariablesService.getVariables(id, plugin.variables, userId, true // forDisplay - mask sensitive values
        );
        res.json({ success: true, data: variables });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to get plugin variables'),
        });
    }
});
// Set variable values for a plugin
router.put('/:id/variables', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        const { variables } = req.body;
        if (!variables || typeof variables !== 'object') {
            res
                .status(400)
                .json({ success: false, error: 'Variables object is required' });
            return;
        }
        const plugin = pluginService.getPlugin(id);
        if (!plugin) {
            res.status(404).json({ success: false, error: 'Plugin not found' });
            return;
        }
        if (!plugin.variables || plugin.variables.length === 0) {
            res.status(400).json({
                success: false,
                error: 'Plugin has no configurable variables',
            });
            return;
        }
        // Validate variable values against schema
        const schemaMap = new Map(plugin.variables.map(v => [v.name, v]));
        const validated = {};
        for (const [key, value] of Object.entries(variables)) {
            const def = schemaMap.get(key);
            if (!def)
                continue; // Ignore unknown variables
            // Type validation
            if (def.type === 'number') {
                const num = Number(value);
                if (isNaN(num)) {
                    res.status(400).json({
                        success: false,
                        error: `Variable "${key}" must be a number`,
                    });
                    return;
                }
                if (def.min !== undefined && num < def.min) {
                    res.status(400).json({
                        success: false,
                        error: `Variable "${key}" must be >= ${def.min}`,
                    });
                    return;
                }
                if (def.max !== undefined && num > def.max) {
                    res.status(400).json({
                        success: false,
                        error: `Variable "${key}" must be <= ${def.max}`,
                    });
                    return;
                }
                validated[key] = num;
            }
            else if (def.type === 'boolean') {
                validated[key] = value === true || value === 'true';
            }
            else if (def.type === 'select') {
                if (def.options && !def.options.includes(String(value))) {
                    res.status(400).json({
                        success: false,
                        error: `Variable "${key}" must be one of: ${def.options.join(', ')}`,
                    });
                    return;
                }
                validated[key] = String(value);
            }
            else {
                // String type
                const str = String(value);
                if (str.length > 2048) {
                    res.status(400).json({
                        success: false,
                        error: `Variable "${key}" exceeds maximum length of 2048 characters`,
                    });
                    return;
                }
                // Validate URL format for endpoint variables
                if (key === 'endpoint' && str.length > 0) {
                    try {
                        new URL(str);
                    }
                    catch {
                        res.status(400).json({
                            success: false,
                            error: `Variable "${key}" must be a valid URL`,
                        });
                        return;
                    }
                }
                validated[key] = str;
            }
        }
        const userId = req.userId || 'default';
        const success = pluginVariablesService.setVariables(id, validated, plugin.variables, userId);
        if (success) {
            res.json({ success: true, data: true });
        }
        else {
            res
                .status(500)
                .json({ success: false, error: 'Failed to save plugin variables' });
        }
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to set plugin variables'),
        });
    }
});
// Reset all variable values for a plugin (back to defaults)
router.delete('/:id/variables', pluginRateLimit, async (req, res) => {
    try {
        const id = req.params.id;
        const plugin = pluginService.getPlugin(id);
        if (!plugin) {
            res.status(404).json({ success: false, error: 'Plugin not found' });
            return;
        }
        const userId = req.userId || 'default';
        const success = pluginVariablesService.deletePluginVariables(id, userId);
        res.json({ success: true, data: success });
    }
    catch (error) {
        res.status(500).json({
            success: false,
            error: getErrorMessage(error, 'Failed to reset plugin variables'),
        });
    }
});
export default router;
//# sourceMappingURL=plugins.js.map