import { createLogger, env } from '@4runr/shared';
import { getPrismaClient } from '../db/prisma.js';
import { decryptString } from '../crypto/envelope.js';
import { assertSafeToolProxyHttpUrl } from '../security/outbound-url.js';
import { assertToolAllowed, getSigningSecret, signAgentToken, validateMintRequest, verifyAgentToken, } from '../security/agent-token.js';
import { requireAuth } from '../middleware/auth.js';
import { writeRateLimit, strictRateLimit } from '../middleware/rateLimit.js';
const routeLogger = createLogger('Gateway:ToolProxy');
function requestLogger(request) {
    return request.logger ?? routeLogger;
}
function isProductionNode() {
    return (process.env['NODE_ENV'] ?? env.NODE_ENV) === 'production';
}
async function loadToolCredential(tool, log) {
    try {
        const prisma = await getPrismaClient();
        const credential = await prisma.toolCredential.findFirst({
            where: {
                tool,
                isActive: true,
            },
        });
        if (!credential) {
            return { success: false, error: `No active credential found for tool: ${tool}` };
        }
        const kekBase64 = process.env['KEK_BASE64'];
        if (!kekBase64) {
            return { success: false, error: 'KEK not configured' };
        }
        const kek = Buffer.from(kekBase64, 'base64');
        try {
            const apiKey = decryptString(credential.encryptedCredential, kek);
            return { success: true, apiKey };
        }
        catch (decryptError) {
            const msg = decryptError instanceof Error ? decryptError.message : String(decryptError);
            log.error('Tool credential decryption failed', { tool, error: msg });
            return { success: false, error: 'Decryption failed' };
        }
    }
    catch (error) {
        const msg = error instanceof Error ? error.message : String(error);
        log.error('Tool credential load failed', { tool, error: msg });
        return { success: false, error: isProductionNode() ? 'Tool credential unavailable' : msg };
    }
}
const MAX_FETCH_BYTES = 512 * 1024;
async function callTool(tool, action, params, _apiKey) {
    const axios = (await import('axios')).default;
    switch (tool) {
        case 'serpapi':
            if (action === 'search') {
                return {
                    simulated: true,
                    message: 'SerpAPI is not integrated; this response is a stub for development only',
                    params,
                };
            }
            break;
        case 'http_fetch':
            if (action === 'get') {
                const rawUrl = params?.url;
                if (typeof rawUrl !== 'string' || !rawUrl.trim()) {
                    throw new Error('params.url must be a non-empty string');
                }
                assertSafeToolProxyHttpUrl(rawUrl);
                try {
                    const response = await axios.get(rawUrl, {
                        timeout: 5000,
                        maxRedirects: 0,
                        maxContentLength: MAX_FETCH_BYTES,
                        validateStatus: (status) => status >= 200 && status < 300,
                    });
                    return {
                        status: response.status,
                        data: response.data,
                    };
                }
                catch (error) {
                    const msg = error instanceof Error ? error.message : String(error);
                    throw new Error(`HTTP fetch failed: ${msg}`);
                }
            }
            break;
        default:
            throw new Error(`Tool not implemented: ${tool}`);
    }
    throw new Error(`Action not implemented: ${tool}.${action}`);
}
export async function toolProxyRoutes(fastify) {
    fastify.post('/api/tool-proxy', {
        preHandler: [strictRateLimit],
    }, async (request, reply) => {
        const log = requestLogger(request);
        try {
            const { agent_token, tool, action, params } = (request.body || {});
            if (!agent_token || !tool || !action || !params) {
                return reply.status(400).send({
                    error: 'Missing required fields',
                    required: ['agent_token', 'tool', 'action', 'params'],
                });
            }
            const tokenVerification = verifyAgentToken(agent_token);
            if (!tokenVerification.valid || !tokenVerification.data) {
                return reply.status(403).send({
                    error: 'Invalid token',
                    details: tokenVerification.error,
                });
            }
            const tokenData = tokenVerification.data;
            const allow = assertToolAllowed(tokenData, tool);
            if (!allow.allowed) {
                return reply.status(403).send({
                    error: 'Tool not permitted',
                    message: `Agent ${tokenData.agent_id} does not have access to ${tool}`,
                    allowedTools: allow.allowedTools,
                });
            }
            const toolsWithoutCredentials = ['http_fetch'];
            let apiKey;
            if (!toolsWithoutCredentials.includes(tool)) {
                const credentialResult = await loadToolCredential(tool, log);
                if (!credentialResult.success || !credentialResult.apiKey) {
                    return reply.status(503).send({
                        error: 'Tool not configured',
                        message: credentialResult.error || `No active credential for ${tool}`,
                    });
                }
                apiKey = credentialResult.apiKey;
            }
            const result = await callTool(tool, action, params, apiKey);
            return reply.status(200).send({
                success: true,
                tool,
                action,
                result,
                timestamp: new Date().toISOString(),
                agent_id: tokenData.agent_id,
            });
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            log.error('Tool proxy request failed', { error: msg });
            return reply.status(500).send({
                error: 'Proxy request failed',
                ...(isProductionNode() ? {} : { details: msg }),
            });
        }
    });
    fastify.post('/api/generate-agent-token', {
        preHandler: [requireAuth, writeRateLimit],
    }, async (request, reply) => {
        const log = requestLogger(request);
        try {
            const { secret, error: secretError } = getSigningSecret();
            if (!secret) {
                return reply.status(503).send({
                    error: 'signing_not_configured',
                    message: secretError,
                });
            }
            const mint = validateMintRequest((request.body || {}));
            if (!mint.ok) {
                return reply.status(400).send({
                    error: 'Invalid mint request',
                    message: mint.error,
                    ...(mint.details && { details: mint.details }),
                });
            }
            const { agent_id, tools, permissions, expiresInHours } = mint.data;
            const tokenPayload = {
                agent_id,
                tools,
                permissions,
                expires_at: new Date(Date.now() + expiresInHours * 3600000).toISOString(),
            };
            const signed = signAgentToken(tokenPayload, secret);
            if (!('token' in signed)) {
                return reply.status(503).send({
                    error: 'signing_failed',
                    message: signed.error,
                });
            }
            return reply.status(201).send({
                success: true,
                agent_token: signed.token,
                agent_id,
                tools,
                permissions,
                expires_at: tokenPayload.expires_at,
                permissions_enforced: false,
                usage: 'Include agent_token when calling POST /api/tool-proxy. Only tools[] is enforced on the proxy; permissions are reserved for a future release.',
            });
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            log.error('Agent token mint failed', { error: msg });
            return reply.status(500).send({
                error: 'Failed to generate token',
                ...(isProductionNode() ? {} : { details: msg }),
            });
        }
    });
}
//# sourceMappingURL=tool-proxy.js.map