import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { test } from 'vitest';
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const subagentSrc = readFileSync(join(__dirname, "..", "index.ts"), "utf-8");
const scoutAgentSrc = readFileSync(
	join(__dirname, "..", "..", "..", "agents", "scout.md"),
	"utf-8",
);

test("subagent launcher resolves Node command specs instead of shelling through bash", () => {
	assert.match(subagentSrc, /function resolveSubagentLaunchSpec\(/);
	assert.match(
		subagentSrc,
		/command = process\.env\.SF_NODE_BIN \|\| process\.execPath/,
	);
	assert.doesNotMatch(subagentSrc, /bash -lc/);
});

test("normal subagent execution spawns the resolved Node command with argv array", () => {
	assert.match(
		subagentSrc,
		/spawn\(\s*launchSpec\.command,\s*launchSpec\.args,/,
	);
	assert.match(subagentSrc, /shell:\s*false/);
});

test("codebase_search subprocesses are tracked and killed on abort", () => {
	const scoutIdx = subagentSrc.indexOf('name: "codebase_search"');
	assert.ok(scoutIdx > 0, "codebase_search tool must be registered");
	const scoutSrc = subagentSrc.slice(scoutIdx);

	assert.match(scoutSrc, /liveSubagentProcesses\.add\(proc\)/);
	assert.match(scoutSrc, /liveSubagentProcesses\.delete\(proc\)/);
	assert.match(scoutSrc, /signal\.addEventListener\("abort", killProc, \{ once: true \}\)/);
	assert.match(scoutSrc, /proc\.kill\("SIGKILL"\)/);
});

test("codebase_search has human-facing rendering and a bounded timeout", () => {
	const scoutIdx = subagentSrc.indexOf('name: "codebase_search"');
	assert.ok(scoutIdx > 0, "codebase_search tool must be registered");
	const scoutSrc = subagentSrc.slice(scoutIdx);

	assert.match(scoutSrc, /renderCall\(args, theme\)/);
	assert.match(scoutSrc, /Code search is querying Sift/);
	assert.match(scoutSrc, /renderResult\(result, \{ expanded \}, theme\)/);
	assert.match(scoutSrc, /CODEBASE_SEARCH_TIMEOUT_MS/);
	assert.match(scoutSrc, /setTimeout\(\(\) => \{/);
	assert.match(scoutSrc, /Code search timed out after/);
});

test("codebase_search is documented as Sift backend, not scout role", () => {
	assert.doesNotMatch(subagentSrc, /name: "call_scout"/);
	assert.match(subagentSrc, /not named "scout"/);
	assert.match(subagentSrc, /`scout` is the explorer subagent role/);
	assert.match(subagentSrc, /resolveSiftBinary/);
	assert.match(subagentSrc, /buildCodebaseSearchArgs/);
	assert.match(subagentSrc, /install rupurt\/sift/);
});

test("scout agent can use Sift-backed codebase_search", () => {
	assert.match(scoutAgentSrc, /tools: .*codebase_search/);
	assert.match(scoutAgentSrc, /Use in-process `grep`, `find`, `ls`, and `lsp` before shelling out/);
	assert.match(scoutAgentSrc, /Sift-backed local retrieval tool/);
	assert.match(scoutAgentSrc, /You are still the scout role; Sift is one tool you can use\./);
});

test("cmux launcher writes only explicit environment patch, not the full process env", () => {
	assert.match(subagentSrc, /function writeNodeSubagentLauncher\(/);
	assert.match(
		subagentSrc,
		/const env = \{ \.\.\.process\.env, \.\.\.\$\{JSON\.stringify\(launchSpec\.envPatch\)\} \}/,
	);
	assert.doesNotMatch(subagentSrc, /JSON\.stringify\(launchSpec\.env\)/);
});
