// Copyright (C) 2026 Hudson River Trading LLC <opensource@hudson-trading.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import type express from 'express';
import {type Router} from 'express';

import type {ApiHandler} from '../handlers/api.js';
import type {CompileHandler} from '../handlers/compile.js';
import {cors} from '../handlers/middleware.js';
import {logger} from '../logger.js';
import type {StorageBase} from '../storage/index.js';
import {registerAsmDocsTool} from './tools/asm-docs.js';
import {registerCompileTool} from './tools/compile.js';
import {registerCompilersTool} from './tools/compilers.js';
import {registerLanguagesTool} from './tools/languages.js';
import {registerLibrariesTool} from './tools/libraries.js';
import {registerShortlinkTools} from './tools/shortlinks.js';

function createMcpServer(
    compileHandler: CompileHandler,
    apiHandler: ApiHandler,
    storageHandler: StorageBase,
    baseUrl: string,
    req: express.Request,
): McpServer {
    const server = new McpServer(
        {
            name: 'compiler-explorer',
            version: apiHandler.release.releaseBuildNumber || apiHandler.release.gitReleaseName || 'dev',
        },
        {
            capabilities: {
                tools: {},
            },
        },
    );

    registerCompileTool(server, compileHandler, apiHandler);
    registerCompilersTool(server, apiHandler);
    registerLanguagesTool(server, apiHandler);
    registerLibrariesTool(server, apiHandler);
    registerAsmDocsTool(server);
    // req is the original /mcp POST request and is captured by tool callbacks
    // (notably the shortlink tool, which threads it into storeItem for S3 audit
    // metadata). This is safe in stateless mode (sessionIdGenerator: undefined):
    // each POST is a complete cycle and `await transport.handleRequest(...)`
    // resolves before the handler exits, so req is valid throughout the closure
    // lifetime. If sessions or streaming are ever added, re-evaluate whether the
    // tools should snapshot req fields up-front instead.
    registerShortlinkTools(server, storageHandler, apiHandler, baseUrl, req);

    return server;
}

export function setupMcpEndpoint(
    router: Router,
    compileHandler: CompileHandler,
    apiHandler: ApiHandler,
    storageHandler: StorageBase,
): void {
    // express.json() is installed globally on the router by setupBasicRoutes (server-config.ts)
    // with the configured bodyParserLimit, so by the time we get here req.body is already parsed.
    router.post('/mcp', cors, async (req, res) => {
        try {
            const baseUrl = `${req.protocol}://${req.get('host')}`;
            const server = createMcpServer(compileHandler, apiHandler, storageHandler, baseUrl, req);
            const transport = new StreamableHTTPServerTransport({
                sessionIdGenerator: undefined,
            });
            await server.connect(transport);
            await transport.handleRequest(req, res, req.body);
        } catch (e) {
            logger.error('MCP request error:', e);
            if (!res.headersSent) {
                res.status(500).json({
                    jsonrpc: '2.0',
                    error: {code: -32603, message: 'Internal server error'},
                    id: null,
                });
            }
        }
    });

    router.options('/mcp', cors, (_req, res) => {
        // The shared `cors` middleware sets Allow-Origin and Allow-Headers but NOT
        // Allow-Methods, which a browser preflight needs to permit POST with
        // Content-Type: application/json. Add it here so browser-based MCP clients
        // can call the endpoint cross-origin.
        res.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
        res.set('Access-Control-Max-Age', '86400');
        res.sendStatus(200);
    });

    router.get('/mcp', cors, (_req, res) => {
        res.status(405).set('Allow', 'POST, OPTIONS').send('Method Not Allowed');
    });

    router.delete('/mcp', cors, (_req, res) => {
        res.status(405).set('Allow', 'POST, OPTIONS').send('Method Not Allowed');
    });

    logger.info('MCP endpoint registered at /mcp');
}
