#!/usr/bin/env node
import express from 'express';
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { createToolDefinitions } from "./tools.js";
import { setupRequestHandlers } from "./requestHandler.js";
import { Logger, RequestLoggingMiddleware } from "./logging/index.js";
import { MonitoringSystem } from "./monitoring/index.js";
export async function startHttpServer(port) {
    // Show immediate feedback that server is starting
    process.stdout.write('\n🚀 Starting Playwright MCP Server (HTTP Mode)...\n');
    // Initialize logger for HTTP mode (file only - cleaner console output)
    const logger = Logger.getInstance({
        level: 'info',
        format: 'json',
        outputs: ['file'], // File only - we have nice formatted console output below
        filePath: `${process.env.HOME || '/tmp'}/playwright-mcp-server-http.log`,
        maxFileSize: 10485760,
        maxFiles: 5
    });
    const loggingMiddleware = new RequestLoggingMiddleware(logger);
    // Initialize monitoring system
    const monitoringSystem = new MonitoringSystem({
        enabled: true,
        metricsInterval: 30000,
        healthCheckInterval: 60000,
        memoryThreshold: 80,
        responseTimeThreshold: 5000
    });
    const serverInfo = {
        name: "playwright-mcp",
        version: "1.0.11",
        capabilities: {
            resources: {},
            tools: {},
        }
    };
    // Create Express application
    const app = express();
    app.use(express.json());
    // Log all incoming requests for debugging
    app.use((req, res, next) => {
        logger.info('Incoming request', {
            method: req.method,
            path: req.path,
            url: req.url,
            query: req.query,
            headers: {
                accept: req.headers['accept'],
                contentType: req.headers['content-type']
            }
        });
        next();
    });
    // Store transports by session ID
    const transports = {};
    // Create tool definitions
    const TOOLS = createToolDefinitions();
    // Helper function to create a new MCP server instance
    const createMcpServer = () => {
        const server = new Server({
            name: serverInfo.name,
            version: serverInfo.version,
        }, {
            capabilities: serverInfo.capabilities,
        });
        // Setup request handlers
        setupRequestHandlers(server, TOOLS, monitoringSystem);
        return server;
    };
    // Helper function to handle SSE connection (DRY principle)
    const handleSseConnection = async (endpoint, messageEndpoint, req, res) => {
        logger.info('SSE connection request received', { endpoint });
        try {
            const transport = new SSEServerTransport(messageEndpoint, res);
            const sessionId = transport.sessionId;
            transports[sessionId] = transport;
            logger.info('Transport registered', {
                sessionId,
                endpoint,
                messageEndpoint,
                activeTransports: Object.keys(transports).length
            });
            res.on('close', () => {
                logger.info('SSE connection closed', { sessionId, endpoint });
                delete transports[sessionId];
                logger.info('Transport unregistered', {
                    sessionId,
                    activeTransports: Object.keys(transports).length
                });
            });
            const server = createMcpServer();
            await server.connect(transport);
            logger.info('SSE transport connected', { sessionId, endpoint });
        }
        catch (error) {
            logger.error('Error establishing SSE connection', error instanceof Error ? error : new Error(String(error)), { endpoint });
            if (!res.headersSent) {
                res.status(500).send('Failed to establish SSE connection');
            }
        }
    };
    // Helper function to handle POST messages (DRY principle)
    const handlePostMessage = async (endpoint, req, res) => {
        const sessionId = req.query.sessionId;
        logger.info('POST message received', {
            endpoint,
            sessionId: sessionId || 'missing',
            availableTransports: Object.keys(transports),
            activeTransports: Object.keys(transports).length
        });
        if (!sessionId) {
            res.status(400).json({
                jsonrpc: '2.0',
                error: {
                    code: -32000,
                    message: 'Bad Request: sessionId query parameter required',
                },
                id: null,
            });
            return;
        }
        const transport = transports[sessionId];
        if (!transport) {
            logger.warn('Message received for unknown session', {
                sessionId,
                endpoint,
                availableTransports: Object.keys(transports),
                transportExists: sessionId in transports
            });
            res.status(400).json({
                jsonrpc: '2.0',
                error: {
                    code: -32000,
                    message: 'Bad Request: No transport found for sessionId',
                },
                id: null,
            });
            return;
        }
        try {
            await transport.handlePostMessage(req, res, req.body);
        }
        catch (error) {
            logger.error('Error handling POST message', error instanceof Error ? error : new Error(String(error)), { sessionId, endpoint });
            if (!res.headersSent) {
                res.status(500).json({
                    jsonrpc: '2.0',
                    error: {
                        code: -32603,
                        message: 'Internal server error',
                    },
                    id: null,
                });
            }
        }
    };
    // Legacy SSE endpoint (for backward compatibility)
    app.get('/sse', (req, res) => handleSseConnection('/sse', '/messages', req, res));
    app.post('/messages', (req, res) => handlePostMessage('/messages', req, res));
    // Unified MCP endpoint (recommended)
    app.get('/mcp', (req, res) => handleSseConnection('/mcp', '/mcp', req, res));
    app.post('/mcp', (req, res) => handlePostMessage('/mcp', req, res));
    // Health check endpoint
    app.get('/health', (req, res) => {
        res.json({
            status: 'ok',
            version: serverInfo.version,
            activeSessions: Object.keys(transports).length,
        });
    });
    // Start the HTTP server
    // SECURITY: Bind to localhost only to prevent external access
    const host = '127.0.0.1';
    return new Promise((resolve, reject) => {
        const httpServer = app.listen(port, host, () => {
            logger.info(`Playwright MCP HTTP server listening on ${host}:${port}`, {
                host,
                port,
                endpoints: {
                    sse: `http://localhost:${port}/sse`,
                    messages: `http://localhost:${port}/messages`,
                    mcp: `http://localhost:${port}/mcp`,
                    health: `http://localhost:${port}/health`,
                }
            });
            console.log(`
==============================================
Playwright MCP Server (HTTP Mode)
==============================================
Listening: ${host}:${port} (localhost only)
Version: ${serverInfo.version}

SECURITY: Server is bound to localhost only.
          Not accessible from external networks.

ENDPOINTS:
- SSE Stream:     GET  http://localhost:${port}/sse
- Messages:       POST http://localhost:${port}/messages?sessionId=<id>
- MCP (unified):  GET  http://localhost:${port}/mcp
- MCP (unified):  POST http://localhost:${port}/mcp?sessionId=<id>
- Health Check:   GET  http://localhost:${port}/health

CLIENT CONFIGURATION:
{
  "mcpServers": {
    "playwright": {
      "url": "http://localhost:${port}/mcp",
      "type": "http"
    }
  }
}
==============================================
`);
            // Start monitoring system with dynamic port allocation
            monitoringSystem.startMetricsCollection(0).catch(error => {
                logger.warn('Failed to start monitoring HTTP server', {
                    error: error instanceof Error ? error.message : String(error)
                });
            });
            resolve();
        });
        httpServer.on('error', (error) => {
            logger.error('Failed to start HTTP server', error);
            reject(error);
        });
        // Graceful shutdown
        const shutdown = async () => {
            logger.info('Shutdown signal received');
            // Close all active transports
            for (const sessionId in transports) {
                try {
                    logger.info('Closing transport', { sessionId });
                    await transports[sessionId].close();
                    delete transports[sessionId];
                }
                catch (error) {
                    logger.error('Error closing transport', error instanceof Error ? error : new Error(String(error)), { sessionId });
                }
            }
            // Stop monitoring
            try {
                await monitoringSystem.stopMetricsCollection();
                logger.info('Monitoring system stopped');
            }
            catch (error) {
                logger.error('Error stopping monitoring system', error instanceof Error ? error : new Error(String(error)));
            }
            // Close HTTP server
            httpServer.close(() => {
                logger.info('HTTP server closed');
                process.exit(0);
            });
            // Force exit after timeout
            setTimeout(() => {
                logger.warn('Forced shutdown after timeout');
                process.exit(1);
            }, 5000);
        };
        process.on('SIGINT', shutdown);
        process.on('SIGTERM', shutdown);
    });
}
