import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { createRequire } from "node:module";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, test } from "vitest";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);

// Load the native addon directly
const addonDir = path.resolve(
	__dirname,
	"..",
	"..",
	"..",
	"..",
	"rust-engine",
	"addon",
);
const platformTag = `${process.platform}-${process.arch}`;
const candidates = [
	path.join(addonDir, `forge_engine.${platformTag}.node`),
	path.join(addonDir, "forge_engine.dev.node"),
];

let native;
for (const candidate of candidates) {
	try {
		native = require(candidate);
		break;
	} catch {
		// try next
	}
}

if (!native) {
	console.error(
		"Native addon not found. Run `npm run build:native -w @singularity-forge/native` first.",
	);
	process.exit(1);
}

describe("native ps: listDescendants()", () => {
	test("returns an array for the current process", () => {
		const descendants = native.listDescendants(process.pid);
		assert.ok(Array.isArray(descendants));
	});

	test("returns empty array for non-existent PID", () => {
		// PID 2147483647 is extremely unlikely to exist
		const descendants = native.listDescendants(2147483647);
		assert.ok(Array.isArray(descendants));
		assert.equal(descendants.length, 0);
	});

	test("finds child processes", {
		skip: "proc_listchildpids unreliable on macOS — needs sysctl KERN_PROC implementation",
	}, async () => {
		const child = spawn("sh", ["-c", "sleep 30 & sleep 30 & wait"], {
			stdio: "ignore",
		});

		await new Promise((resolve) => setTimeout(resolve, 500));

		try {
			const descendants = native.listDescendants(child.pid);
			assert.ok(
				descendants.length > 0,
				`expected descendants of sh (pid ${child.pid}), got: ${JSON.stringify(descendants)}`,
			);
		} finally {
			native.killTree(child.pid, 9);
		}
	});
});

describe("native ps: killTree()", () => {
	test("kills a process and its children", async () => {
		// Spawn a shell that spawns a sleep subprocess
		const child = spawn("sh", ["-c", "sleep 60"], { stdio: "ignore" });

		await new Promise((resolve) => setTimeout(resolve, 100));

		const killed = native.killTree(child.pid, 9);
		assert.ok(killed >= 1, `should kill at least 1 process, killed: ${killed}`);

		// Verify the child is actually dead
		await new Promise((resolve) => {
			child.on("exit", resolve);
			// Timeout safety — if already exited, resolve immediately
			setTimeout(resolve, 500);
		});
	});

	test("returns 0 for non-existent PID", () => {
		const killed = native.killTree(2147483647, 9);
		assert.equal(killed, 0);
	});
});

describe("native ps: processGroupId()", () => {
	test("returns a number for the current process", () => {
		const pgid = native.processGroupId(process.pid);
		if (process.platform === "win32") {
			assert.equal(pgid, null);
		} else {
			assert.equal(typeof pgid, "number");
			assert.ok(pgid > 0);
		}
	});

	test("returns null for non-existent PID", () => {
		const pgid = native.processGroupId(2147483647);
		assert.equal(pgid, null);
	});
});

describe("native ps: killProcessGroup()", () => {
	test("returns false for non-existent process group", () => {
		const result = native.killProcessGroup(2147483647, 15);
		assert.equal(result, false);
	});
});
