"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupLogStreaming = void 0;
const express_1 = require("express");
const pm2_1 = __importDefault(require("pm2"));
const fs_1 = __importDefault(require("fs"));
const remote_connection_1 = require("../utils/remote-connection");
const router = (0, express_1.Router)();
// This variable will hold references to active log streams
const activeStreams = {};
const activeRemoteStreams = {};
// Get active stream (or create a new one)
const getLogStream = (io, processId, logType) => {
    const streamKey = `${processId}-${logType}`;
    // If stream already exists, return it
    if (activeStreams[streamKey]) {
        return Promise.resolve(activeStreams[streamKey]);
    }
    return new Promise((resolve, reject) => {
        pm2_1.default.describe(processId, (err, processDesc) => {
            var _a, _b;
            if (err || !processDesc || processDesc.length === 0) {
                reject(new Error('Process not found'));
                return;
            }
            const logPath = (_b = (_a = processDesc[0]) === null || _a === void 0 ? void 0 : _a.pm2_env) === null || _b === void 0 ? void 0 : _b[`pm_${logType}_log_path`];
            if (!logPath || !fs_1.default.existsSync(logPath)) {
                reject(new Error(`Log file not found: ${logPath}`));
                return;
            }
            // Cross-platform tail using fs.watch — no Unix `tail` binary needed
            let position = fs_1.default.statSync(logPath).size; // start at EOF, don't replay history
            const watcher = fs_1.default.watch(logPath, (eventType) => {
                if (eventType !== 'change')
                    return;
                try {
                    const stat = fs_1.default.statSync(logPath);
                    // Handle log rotation / truncation
                    if (stat.size < position)
                        position = 0;
                    if (stat.size === position)
                        return;
                    const length = stat.size - position;
                    const buffer = Buffer.alloc(length);
                    const fd = fs_1.default.openSync(logPath, 'r');
                    fs_1.default.readSync(fd, buffer, 0, length, position);
                    fs_1.default.closeSync(fd);
                    position = stat.size;
                    const lines = buffer.toString('utf8').split('\n').filter((l) => l.trim() !== '');
                    lines.forEach((line) => {
                        io.to(streamKey).emit('log-line', { processId, logType, line });
                    });
                }
                catch (e) {
                    console.error('Error reading log file:', e);
                }
            });
            watcher.on('error', (e) => console.error(`Watcher error for ${streamKey}:`, e));
            // Expose a kill() so existing cleanup code works unchanged
            const streamObj = { kill: () => { watcher.close(); delete activeStreams[streamKey]; } };
            activeStreams[streamKey] = streamObj;
            resolve(streamObj);
        });
    });
};
// Get remote log stream (or create a new one)
const getRemoteLogStream = async (io, connectionId, processId) => {
    const streamKey = `${connectionId}-${processId}`;
    // If stream already exists, return it
    if (activeRemoteStreams[streamKey]) {
        return activeRemoteStreams[streamKey];
    }
    const connection = remote_connection_1.remoteConnectionManager.getConnection(connectionId);
    if (!connection) {
        throw new Error('Connection not found');
    }
    // Ensure connection is active before starting log stream
    if (!connection.isConnected()) {
        console.log(`Connection ${connectionId} not active, attempting to connect...`);
        try {
            await connection.connect();
            console.log(`Successfully connected to ${connectionId} for log streaming`);
        }
        catch (error) {
            throw new Error(`Failed to connect: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
    // Get process info using the multi-path fallback so pm2 is found regardless of PATH
    console.log(`Getting process info for: ${processId}`);
    const processInfoResult = await connection.executePM2Command('jlist');
    if (processInfoResult.code !== 0) {
        throw new Error(`Failed to get process list: ${processInfoResult.stderr}`);
    }
    // Extract JSON from output (pm2 jlist may prefix with non-JSON lines)
    let rawOutput = processInfoResult.stdout.trim();
    const startIdx = rawOutput.indexOf('[');
    const endIdx = rawOutput.lastIndexOf(']') + 1;
    if (startIdx !== -1 && endIdx > 0) {
        rawOutput = rawOutput.substring(startIdx, endIdx);
    }
    let processInfo;
    try {
        const processList = JSON.parse(rawOutput);
        console.log(`Found ${processList.length} processes`);
        processInfo = processList.find((proc) => proc.pm_id === parseInt(processId));
        if (!processInfo) {
            throw new Error(`Process with ID ${processId} not found`);
        }
        console.log(`Found process: ${processInfo.name} (ID: ${processInfo.pm_id})`);
    }
    catch (parseError) {
        console.error('Parse error:', parseError);
        throw new Error(`Failed to parse process list: ${parseError}`);
    }
    const processName = processInfo.name;
    // Create streams using pm2 logs instead of tail for better permission handling
    const streams = {};
    try {
        console.log(`Setting up pm2 logs stream for process: ${processName} (ID: ${processId})`);
        // Use the resolved pm2 invocation (cached from the jlist call above) so the
        // same shell/path that successfully ran pm2 jlist is reused here.
        const pm2LogsCommand = connection.buildPM2StreamCommand(`logs ${processId} --lines 0 --raw`);
        console.log(`About to create pm2 log stream with command: ${pm2LogsCommand}`);
        const logStream = await connection.createLogStream(pm2LogsCommand, true);
        console.log(`Successfully created pm2 logs stream for ${processName}`);
        logStream.on('data', (data) => {
            console.log(`Raw pm2 logs data received for ${processName}:`, data);
            const lines = data.toString().split('\n').filter((line) => line.trim() !== '');
            lines.forEach((line) => {
                // Skip PM2 startup messages, system messages, and errors
                if (line.trim() === '' ||
                    line.includes('PM2') ||
                    line.includes('---') ||
                    line.includes('watching') ||
                    line.includes('change detected') ||
                    line.includes('Runtime Edition') ||
                    line.includes('Production Process Manager') ||
                    line.includes('built-in Load Balancer') ||
                    line.includes('$ pm2') ||
                    line.includes('http://pm2.io') ||
                    line.includes('Start and Daemonize') ||
                    line.includes('Load Balance') ||
                    line.includes('Monitor in production') ||
                    line.includes('Make pm2 auto-boot') ||
                    line.includes('To go further') ||
                    line.includes('ENOENT') ||
                    line.includes('module_conf.json') ||
                    line.includes('pm2.log') ||
                    line.includes('node:fs:') ||
                    line.includes('at Object.') ||
                    line.includes('at Client.') ||
                    line.includes('at processTicksAndRejections') ||
                    line.includes('errno:') ||
                    line.includes('syscall:') ||
                    line.includes('Node.js v') ||
                    line.startsWith('    at ') ||
                    line.match(/^\s*\^?\s*$/) ||
                    line.match(/^[\s_\/\\|]+$/)) {
                    console.log(`Skipping PM2 system/error line: "${line}"`);
                    return;
                }
                // Parse PM2 log format: timestamp | app-name | message
                // PM2 logs typically come in format like: "2023-01-01T12:00:00: PM2 log: [TAILING]"
                // or just the raw log content depending on version
                let logType = 'stdout'; // Default to stdout
                let cleanLine = line;
                // Try to detect if this is stderr (PM2 usually prefixes with different indicators)
                if (line.includes('ERROR') || line.includes('error') || line.includes('stderr')) {
                    logType = 'stderr';
                }
                // Clean up the line by removing PM2 prefixes if present
                // PM2 log format can vary, but often includes timestamps and app names
                const logMatch = line.match(/^.*?\|\s*(.+)$/);
                if (logMatch) {
                    cleanLine = logMatch[1];
                }
                console.log(`Emitting remote-log-line for ${connectionId}-${processId} (${logType}):`, cleanLine);
                // Emit only to clients subscribed to this specific remote log stream
                io.to(streamKey).emit('remote-log-line', {
                    connectionId,
                    processId,
                    processName,
                    logType,
                    line: cleanLine
                });
            });
        });
        logStream.on('error', (error) => {
            console.error(`Error in pm2 logs stream for ${processName}:`, error);
            io.to(streamKey).emit('remote-log-error', {
                connectionId,
                processId,
                processName,
                error: error.message
            });
            // Clean up on error
            if (activeRemoteStreams[streamKey]) {
                delete activeRemoteStreams[streamKey];
            }
        });
        logStream.on('close', (code) => {
            console.log(`PM2 logs stream closed for ${processName} with code:`, code);
            // Notify clients that the stream has closed
            io.to(streamKey).emit('remote-log-closed', {
                connectionId,
                processId,
                processName,
                code
            });
            // Clean up the stream reference
            if (activeRemoteStreams[streamKey]) {
                delete activeRemoteStreams[streamKey];
            }
        });
        streams.combined = logStream;
    }
    catch (error) {
        console.error(`Failed to create pm2 logs stream for ${processName}:`, error);
        io.to(streamKey).emit('remote-log-error', {
            connectionId,
            processId,
            processName,
            error: `Failed to start log streaming: ${error}`
        });
    }
    // Store the streams
    activeRemoteStreams[streamKey] = streams;
    return streams;
};
// Setup socket.io handlers for log streaming
const setupLogStreaming = (io) => {
    io.on('connection', (socket) => {
        console.log('Client connected for log streaming');
        // Subscribe to log stream
        socket.on('subscribe-logs', async ({ processId, logType }) => {
            try {
                const streamKey = `${processId}-${logType}`;
                // Add socket to a room for this specific log stream
                socket.join(streamKey);
                console.log(`Client subscribed to logs: ${streamKey}`);
                // Get or create the log stream
                await getLogStream(io, processId, logType);
            }
            catch (error) {
                console.error('Error subscribing to logs:', error);
            }
        });
        // Unsubscribe from log stream
        socket.on('unsubscribe-logs', ({ processId, logType }) => {
            const streamKey = `${processId}-${logType}`;
            // Remove socket from the room
            socket.leave(streamKey);
            console.log(`Client unsubscribed from logs: ${streamKey}`);
            // If no more clients in this room, stop the stream
            const room = io.sockets.adapter.rooms.get(streamKey);
            if (!room || room.size === 0) {
                const stream = activeStreams[streamKey];
                if (stream) {
                    console.log(`Stopping log stream: ${streamKey}`);
                    stream.kill();
                    delete activeStreams[streamKey];
                }
            }
        });
        // Cleanup on disconnect
        socket.on('disconnect', () => {
            console.log('Client disconnected from log streaming');
        }); // Subscribe to remote log stream
        socket.on('subscribe-remote-logs', async ({ connectionId, processId }) => {
            try {
                console.log(`Subscribing to remote logs - Connection: ${connectionId}, Process: ${processId}`);
                const streamKey = `${connectionId}-${processId}`;
                // Add socket to a room for this specific log stream
                socket.join(streamKey);
                console.log(`Client subscribed to remote logs: ${streamKey}`);
                // Get or create the remote log stream
                await getRemoteLogStream(io, connectionId, processId);
            }
            catch (error) {
                console.error('Error subscribing to remote logs:', error);
                socket.emit('remote-log-error', {
                    connectionId,
                    processId,
                    error: error instanceof Error ? error.message : 'Unknown error'
                });
            }
        });
        // Unsubscribe from remote log stream
        socket.on('unsubscribe-remote-logs', ({ connectionId, processId }) => {
            const streamKey = `${connectionId}-${processId}`;
            // Remove socket from the room
            socket.leave(streamKey);
            console.log(`Client unsubscribed from remote logs: ${streamKey}`);
            // If no more clients in this room, stop the streams
            const room = io.sockets.adapter.rooms.get(streamKey);
            if (!room || room.size === 0) {
                const streams = activeRemoteStreams[streamKey];
                if (streams) {
                    console.log(`Stopping remote log streams: ${streamKey}`);
                    // Kill the log stream properly
                    if (streams.combined && typeof streams.combined.kill === 'function') {
                        streams.combined.kill();
                    }
                    // Also handle legacy stdout/stderr streams if they exist
                    if (streams.stdout && typeof streams.stdout.kill === 'function') {
                        streams.stdout.kill();
                    }
                    if (streams.stderr && typeof streams.stderr.kill === 'function') {
                        streams.stderr.kill();
                    }
                    delete activeRemoteStreams[streamKey];
                }
            }
        });
    });
};
exports.setupLogStreaming = setupLogStreaming;
exports.default = router;
