const { describe, test, beforeEach, afterEach } = require("node:test");
const assert = require("node:assert");
const childProcess = require("child_process");
const { DOWN, UP } = require("../../src/util");

/**
 * Load the PM2 monitor after swapping child_process.execFile.
 * @param {typeof childProcess.execFile} execFileStub Stub implementation.
 * @returns {import("../../server/monitor-types/pm2").PM2MonitorType} A PM2 monitor instance wired to the stub.
 */
function createMonitorType(execFileStub) {
    childProcess.execFile = execFileStub;
    delete require.cache[require.resolve("../../server/util/pm2")];
    delete require.cache[require.resolve("../../server/monitor-types/pm2")];
    const { PM2MonitorType } = require("../../server/monitor-types/pm2");
    return new PM2MonitorType();
}

describe("PM2MonitorType", () => {
    let monitorType;
    let heartbeat;
    let originalExecFile;

    beforeEach(() => {
        monitorType = null;
        heartbeat = {
            status: DOWN,
            msg: "",
        };
        originalExecFile = childProcess.execFile;
    });

    afterEach(() => {
        childProcess.execFile = originalExecFile;
    });

    test("check() returns UP for an online PM2 process", async () => {
        monitorType = createMonitorType((command, args, options, callback) => {
            callback(
                null,
                JSON.stringify([
                    {
                        pm_id: 0,
                        name: "api",
                        pm2_env: {
                            status: "online",
                        },
                    },
                ]),
                ""
            );
        });

        await monitorType.check(
            {
                system_service_name: "api",
            },
            heartbeat
        );

        assert.strictEqual(heartbeat.status, UP);
        assert.ok(heartbeat.msg.includes("online"));
    });

    test("check() matches PM2 process by name when pm_id changes", async () => {
        monitorType = createMonitorType((command, args, options, callback) => {
            callback(
                null,
                JSON.stringify([
                    {
                        // PM2 may assign a different id when a process is deleted and recreated.
                        pm_id: 7,
                        name: "api",
                        pm2_env: {
                            status: "online",
                        },
                    },
                ]),
                ""
            );
        });

        await monitorType.check(
            {
                system_service_name: "api",
            },
            heartbeat
        );

        assert.strictEqual(heartbeat.status, UP);
        assert.ok(heartbeat.msg.includes("online"));
    });

    test("check() returns DOWN for a stopped PM2 process", async () => {
        monitorType = createMonitorType((command, args, options, callback) => {
            callback(
                null,
                JSON.stringify([
                    {
                        pm_id: 0,
                        name: "api",
                        pm2_env: {
                            status: "stopped",
                        },
                    },
                ]),
                ""
            );
        });

        await assert.rejects(
            monitorType.check(
                {
                    system_service_name: "api",
                },
                heartbeat
            ),
            /stopped/
        );

        assert.strictEqual(heartbeat.status, DOWN);
    });
});
