import crypto2 from 'crypto';
import { spawn } from 'child_process';
import CDP from 'chrome-remote-interface';
import net from 'net';
import { GBLog } from 'botlib';
import { CollectionUtil } from 'pragmatismo-io-framework';
import { GBServer } from '../../../../src/app.js';
import { DebuggerService } from '../DebuggerService.js';
import finalStream from 'final-stream';
import { GBLogEx } from '../../../core.gbapp/services/GBLogEx.js';
const waitUntil = condition => {
    if (condition()) {
        return Promise.resolve();
    }
    return new Promise(resolve => {
        const interval = setInterval(() => {
            if (!condition()) {
                return;
            }
            clearInterval(interval);
            resolve(0);
        }, 0);
    });
};
export const createVm2Pool = ({ min, max, ...limits }) => {
    limits = Object.assign({
        cpu: 100,
        memory: 2000,
        time: 4000
    }, limits);
    let limitError = null;
    const ref = crypto2.randomBytes(20).toString('hex');
    const kill = x => {
        spawn('sh', ['-c', `pkill -9 -f ${ref}`]);
    };
    let stderrCache = '';
    const run = async (code, scope) => {
        const childProcess = spawn('cpulimit', [
            '-ql',
            limits.cpu,
            '--',
            'node',
            `${limits.debug ? '--inspect=' + limits.debuggerPort : ''}`,
            `--experimental-fetch`,
            `--max-old-space-size=${limits.memory}`,
            limits.script,
            ref
        ], { cwd: limits.cwd, shell: false });
        childProcess.stdout.on('data', data => {
            childProcess['socket'] = childProcess['socket'] || data.toString().trim();
        });
        childProcess.stderr.on('data', data => {
            stderrCache = stderrCache + data.toString();
            if (stderrCache.includes('failed: address already in use')) {
                limitError = stderrCache;
                kill(process);
                GBServer.globals.debuggers[limits.botId].state = 0;
                GBServer.globals.debuggers[limits.botId].stateInfo = stderrCache;
            }
            else if (stderrCache.includes('FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory')) {
                limitError = 'code execution exceeed allowed memory';
                kill(process);
                GBServer.globals.debuggers[limits.botId].state = 0;
                GBServer.globals.debuggers[limits.botId].stateInfo = 'Fail';
            }
            else if (stderrCache.includes('Debugger attached.')) {
                GBLog.info(`BASIC: General Bots Debugger attached to Node .gbdialog process for ${limits.botId}.`);
            }
        });
        let socket = null;
        await waitUntil(() => childProcess['socket']);
        GBServer.globals.debuggers[limits.botId].childProcess = ref;
        // Only attach if called by debugger/run.
        if (GBServer.globals.debuggers[limits.botId]) {
            const debug = async () => {
                return new Promise((resolve, reject) => {
                    CDP(async (client) => {
                        const { Debugger, Runtime } = client;
                        try {
                            GBServer.globals.debuggers[limits.botId].client = client;
                            await client.Debugger.paused(async ({ callFrames, reason, hitBreakpoints }) => {
                                const frame = callFrames[0];
                                // Build variable list ignoring system variables of script.
                                const scopeObjectId = frame.scopeChain[2].object.objectId;
                                const variables = await Runtime.getProperties({ objectId: scopeObjectId });
                                let variablesText = '';
                                if (variables && variables.result) {
                                    await CollectionUtil.asyncForEach(variables.result, async (v) => {
                                        if (!DebuggerService.systemVariables.filter(x => x === v.name)[0]) {
                                            if (v.value.value) {
                                                variablesText = `${variablesText} \n ${v.name}: ${v.value.value}`;
                                            }
                                        }
                                    });
                                }
                                GBServer.globals.debuggers[limits.botId].scope = variablesText;
                                GBLogEx.info(min, `BASIC: Breakpoint variables: ${variablesText}`); // (zero-based)
                                // Processes breakpoint hits.
                                if (hitBreakpoints.length >= 1) {
                                    GBLog.info(`BASIC: Break at line ${frame.location.lineNumber + 1}`); // (zero-based)
                                    GBServer.globals.debuggers[limits.botId].state = 2;
                                    GBServer.globals.debuggers[limits.botId].stateInfo = 'Break';
                                }
                                else {
                                    GBLog.verbose(`BASIC: Configuring breakpoints if any for ${limits.botId}...`);
                                    // Waits for debugger and setup breakpoints.
                                    await CollectionUtil.asyncForEach(GBServer.globals.debuggers[limits.botId].breaks, async (brk) => {
                                        try {
                                            const { breakpointId } = await client.Debugger.setBreakpoint({
                                                location: {
                                                    scriptId: frame.location.scriptId,
                                                    lineNumber: brk
                                                }
                                            });
                                            GBLog.info(`BASIC break defined ${breakpointId} for ${limits.botId}`);
                                        }
                                        catch (error) {
                                            GBLog.info(`BASIC error defining ${brk} for ${limits.botId}. ${error}`);
                                        }
                                    });
                                    await client.Debugger.resume();
                                }
                            });
                            await client.Runtime.runIfWaitingForDebugger();
                            await client.Debugger.enable();
                            await client.Runtime.enable();
                            resolve(1);
                        }
                        catch (err) {
                            GBLog.error(err);
                            kill(childProcess);
                            GBServer.globals.debuggers[limits.botId].state = 0;
                            GBServer.globals.debuggers[limits.botId].stateInfo = 'Stopped';
                        }
                    }).on('error', err => {
                        console.error(err);
                        kill(childProcess);
                        GBServer.globals.debuggers[limits.botId].state = 0;
                        GBServer.globals.debuggers[limits.botId].stateInfo = 'Stopped';
                        reject(err);
                    });
                });
            };
            await debug();
        }
        socket = net.createConnection(childProcess['socket']);
        socket.write(JSON.stringify({ code, scope }) + '\n');
        const timer = setTimeout(() => {
            limitError = 'code execution took too long and was killed';
            kill(childProcess);
            GBServer.globals.debuggers[limits.botId].state = 0;
            GBServer.globals.debuggers[limits.botId].stateInfo = limitError;
        }, limits.time);
        try {
            let data = await finalStream(socket);
            data = JSON.parse(data);
            if (!data.length) {
                return null;
            }
            if (data.error) {
                throw new Error(data.error);
            }
            return data.result;
        }
        catch (error) {
            throw new Error(limitError || error);
        }
        finally {
            kill(childProcess);
            GBServer.globals.debuggers[limits.botId].state = 0;
            GBServer.globals.debuggers[limits.botId].stateInfo = 'Stopped';
            clearTimeout(timer);
        }
    };
    return {
        run
    };
};
//# sourceMappingURL=index.js.map