import { fs } from "@zwave-js/core/bindings/fs/node";
import { copyFilesRecursive, noop } from "@zwave-js/shared";
import { wait } from "alcalzone-shared/async";
import crypto from "node:crypto";
import fsp from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "vitest";
import { prepareDriver, prepareMocks } from "./integrationTestSuiteShared.js";
function suite(name, options, modifier) {
    const { controllerCapabilities, nodeCapabilities, customSetup, testBody, debug = false, provisioningDirectory, clearMessageStatsBeforeTest = true, additionalDriverOptions, } = options;
    let driver;
    let mockPort;
    let serial;
    let continueStartup;
    let mockController;
    let mockNodes;
    let nodes;
    const cacheDir = path.join(os.tmpdir(), `zjs_test_cache_${crypto.randomBytes(4).toString("hex")}`);
    async function prepareTest() {
        if (debug) {
            console.log(`Running integration test in directory ${cacheDir}`);
        }
        // Make sure every test is starting fresh
        await fsp.rm(cacheDir, { recursive: true, force: true }).catch(noop);
        await fsp.mkdir(cacheDir, { recursive: true });
        // And potentially provision the cache
        if (provisioningDirectory) {
            await copyFilesRecursive(fs, provisioningDirectory, cacheDir);
        }
        ({ driver, continueStartup, mockPort, serial } = await prepareDriver(cacheDir, debug, additionalDriverOptions));
        ({ mockController, mockNodes } = await prepareMocks(mockPort, serial, {
            capabilities: controllerCapabilities,
            securityKeys: driver.options.securityKeys,
        }, 
        // TODO: This isn't ideal as it requires us to provide the
        // node capabilities in addition to the provisioning directory
        nodeCapabilities));
        if (customSetup) {
            await customSetup(driver, mockController, mockNodes);
        }
        // Wait for all nodes to be ready
        return new Promise((resolve) => {
            driver.once("driver ready", async () => {
                const promises = [];
                nodes = [];
                for (const mockNode of mockNodes) {
                    const node = driver.controller.nodes.getOrThrow(mockNode.id);
                    nodes.push(node);
                    const onReady = (resolve) => {
                        if (clearMessageStatsBeforeTest) {
                            mockNode.clearReceivedControllerFrames();
                            mockNode.clearSentControllerFrames();
                        }
                        process.nextTick(resolve);
                    };
                    promises.push(new Promise((resolve) => {
                        if (options.additionalDriverOptions?.testingHooks
                            ?.skipNodeInterview) {
                            onReady(resolve);
                        }
                        else {
                            node.once("ready", () => onReady(resolve));
                        }
                    }));
                }
                await Promise.all(promises);
                if (clearMessageStatsBeforeTest) {
                    mockController.clearReceivedHostMessages();
                }
                process.nextTick(resolve);
            });
            continueStartup();
        });
    }
    // Integration tests need to run in serial, or they might block the serial port on CI
    const fn = modifier === "only"
        ? test.sequential.only
        : modifier === "skip"
            ? test.sequential.skip
            : test.sequential;
    fn(name, async (t) => {
        t.onTestFinished(async () => {
            // Give everything a chance to settle before destroying the driver.
            await wait(100);
            await driver.destroy();
            if (!debug) {
                await fsp.rm(cacheDir, { recursive: true, force: true })
                    .catch(noop);
            }
        });
        await prepareTest();
        await testBody(t, driver, nodes, mockController, mockNodes);
    }, 30000);
}
/** Performs an integration test with a real driver using a mock controller and one mock node */
export const integrationTest = ((name, options) => {
    suite(name, options);
});
integrationTest.only = (name, options) => {
    suite(name, options, "only");
};
integrationTest.skip = (name, options) => {
    suite(name, options, "skip");
};
//# sourceMappingURL=integrationTestSuiteMulti.js.map