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 node;
    let mockPort;
    let serial;
    let continueStartup;
    let mockController;
    let mockNode;
    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: [mockNode],
        } = await prepareMocks(mockPort, serial, {
            capabilities: controllerCapabilities,
            securityKeys: driver.options.securityKeys,
        }, [
            {
                id: 2,
                capabilities: nodeCapabilities,
            },
        ]));
        if (customSetup) {
            await customSetup(driver, mockController, mockNode);
        }
        return new Promise((resolve) => {
            driver.once("driver ready", () => {
                // Test code goes here
                const onReady = () => {
                    if (clearMessageStatsBeforeTest) {
                        mockNode.clearReceivedControllerFrames();
                        mockNode.clearSentControllerFrames();
                        mockController.clearReceivedHostMessages();
                    }
                    process.nextTick(resolve);
                };
                node = driver.controller.nodes.getOrThrow(mockNode.id);
                if (options.additionalDriverOptions?.testingHooks
                    ?.skipNodeInterview) {
                    onReady();
                }
                else {
                    node.once("ready", onReady);
                }
            });
            if (options.additionalDriverOptions?.bootloaderMode === "stay"
                || options.additionalDriverOptions?.bootloaderMode === "allow") {
                driver.once("bootloader ready", () => {
                    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, node, mockController, mockNode);
    }, 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=integrationTestSuite.js.map