import assert from "node:assert/strict";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "vitest";
import guardrails from "../../guardrails/index.js";
import {
	classifyChatCommand,
	consumeRouteTraceForCommand,
	registerChatCommandRouter,
	routeChatCommand,
} from "../chat-command-router.js";
import {
	BASE_RUNTIME_COMMANDS,
	DIRECT_SF_COMMAND_NAMES,
	getSfArgumentCompletions,
	getSfTopLevelCommandCompletions,
	PRIMARY_COMMANDS,
	PUBLIC_DIRECT_COMMANDS,
} from "../commands/catalog.js";
import { showHelp } from "../commands/handlers/core.js";
import { registerSFCommands } from "../commands/index.js";
import { queryJournal } from "../journal.js";

test("direct SF command surface registers callable verbs without legacy sf namespace", () => {
	const registered = [];
	const pi = {
		registerCommand(name, options) {
			registered.push({ name, options });
		},
	};

	registerSFCommands(pi);
	const names = registered.map((entry) => entry.name).sort();

	assert.ok(names.includes("autonomous"));
	assert.ok(names.includes("next"));
	assert.ok(names.includes("discuss"));
	assert.ok(names.includes("schedule"));
	assert.ok(names.includes("doctor"));
	assert.ok(names.includes("status"));
	assert.ok(names.includes("ship"));
	assert.ok(names.includes("plan"));
	assert.ok(names.includes("todo"));
	assert.ok(names.includes("stop"));
	assert.equal(names.includes("model"), false);
	assert.ok(names.includes("permission-profile"));
	assert.ok(!names.includes("sf"));
	assert.deepEqual(names, [...DIRECT_SF_COMMAND_NAMES].sort());
});

test("visible_sf_commands_are_registered_unless_core_runtime_owned", () => {
	const registered = [];
	registerSFCommands({
		registerCommand(name, options) {
			registered.push({ name, options });
		},
	});
	const registeredNames = new Set(registered.map((entry) => entry.name));
	const missing = [...PUBLIC_DIRECT_COMMANDS].filter(
		(name) => !registeredNames.has(name) && !BASE_RUNTIME_COMMANDS.has(name),
	);

	assert.deepEqual(missing, []);
	assert.equal(
		registeredNames.has("stop"),
		true,
		"/stop is SF autonomous control in print/extension command paths",
	);
	assert.equal(
		BASE_RUNTIME_COMMANDS.has("model"),
		true,
		"/model remains core runtime-owned",
	);
});

test("top_level_completions_keep_platform_owned_product_paths_visible", () => {
	const labels = getSfArgumentCompletions("").map((entry) => entry.label);

	assert.ok(labels.includes("model"));
	assert.equal(labels.includes("mode"), false);
	assert.equal(labels.includes("permission-profile"), false);
});

test("primary_command_tier_is_the_five_command_memory_model", () => {
	assert.deepEqual([...PRIMARY_COMMANDS].sort(), [
		"autonomous",
		"discuss",
		"help",
		"next",
		"quick",
	]);
});

test("help_keyword_routes_natural_language_to_public_commands", () => {
	const messages = [];
	showHelp(
		{
			ui: {
				notify(message) {
					messages.push(message);
				},
			},
		},
		"I want to see my queue",
	);

	assert.equal(messages.length, 1);
	assert.match(messages[0], /\/queue\b/);
	assert.doesNotMatch(messages[0], /\/parallel\b/);
});

test("chat_command_router_maps_clear_chat_intent_to_public_workflow_commands", () => {
	assert.equal(routeChatCommand("I want to see my queue"), "/queue");
	assert.equal(routeChatCommand("start autonomous mode"), "/autonomous");
	assert.equal(routeChatCommand("let's discuss the architecture"), "/discuss");
	assert.equal(routeChatCommand("help me fix login bug"), null);
	assert.equal(routeChatCommand("add a pause button"), null);
	assert.equal(routeChatCommand("pause autonomous mode"), "/pause");
	assert.equal(routeChatCommand("fix the login bug"), null);
	assert.equal(routeChatCommand("/queue"), null);

	assert.deepEqual(classifyChatCommand("I want to see my queue"), {
		input: "I want to see my queue",
		command: "queue",
		args: "",
		routedText: "/queue",
		confidence: "high",
		outcome: "routed",
		nextAction: "execute /queue",
	});
});

test("chat_command_router_emits_journal_evidence_for_routed_intent", async () => {
	const dir = mkdtempSync(join(tmpdir(), "sf-chat-route-"));
	try {
		let inputHandler;
		registerChatCommandRouter({
			on(event, handler) {
				if (event === "input") inputHandler = handler;
			},
		});

		const result = await inputHandler(
			{ text: "I want to see my queue", images: undefined },
			{ cwd: dir },
		);
		const entries = queryJournal(dir, { eventType: "chat-intent-routed" });

		assert.equal(result.action, "transform");
		assert.match(result.text, /^\[sf-route:chat-intent:[^\]]+\] \/queue$/);
		assert.equal(entries.length, 1);
		assert.equal(entries[0].data.command, "queue");
		assert.equal(entries[0].data.outcome, "routed");
		assert.equal(entries[0].data.nextAction, "execute /queue");

		const pending = consumeRouteTraceForCommand("queue");
		assert.equal(pending.traceId, entries[0].flowId);
		assert.equal(pending.input, "I want to see my queue");
		assert.equal(pending.routedText, "/queue");
	} finally {
		rmSync(dir, { recursive: true, force: true });
	}
});

test("chat_command_router_does_not_route_extension_generated_input", async () => {
	let inputHandler;
	registerChatCommandRouter({
		on(event, handler) {
			if (event === "input") inputHandler = handler;
		},
	});

	const result = await inputHandler(
		{ text: "I want to see my queue", images: undefined, source: "extension" },
		{ cwd: process.cwd() },
	);

	assert.deepEqual(result, { action: "continue" });
});

test("direct command completions strip the already typed command name", () => {
	assert.deepEqual(getSfTopLevelCommandCompletions("autonomous", "--"), [
		{
			value: "--verbose",
			label: "--verbose",
			description: "Show detailed execution output",
		},
		{
			value: "--debug",
			label: "--debug",
			description: "Enable debug logging",
		},
	]);
});

test("extension_manifest_hides_advanced_control_axes_from_public_sf_surface", () => {
	const manifest = JSON.parse(
		readFileSync(
			join(
				process.cwd(),
				"src/resources/extensions/sf/extension-manifest.json",
			),
			"utf8",
		),
	);
	const commands = manifest.provides?.commands ?? [];
	assert.equal(commands.includes("permission-profile"), false);
	assert.equal(commands.includes("mode"), false);
	assert.equal(commands.includes("control"), false);
	assert.equal(commands.includes("model-mode"), false);
	assert.equal(commands.includes("trust"), false);
});

test("human_facing_cli_help_when_describing_sf_surfaces_uses_direct_commands", () => {
	const sourceFiles = ["src/help-text.ts", "src/cli.ts", "src/loader.ts"];
	for (const sourceFile of sourceFiles) {
		const source = readFileSync(join(process.cwd(), sourceFile), "utf8");
		assert.equal(
			source.includes("Machine surface for /sf"),
			false,
			`${sourceFile} must describe headless as the direct-command machine surface`,
		);
		assert.equal(
			source.includes("Run /sf autonomous"),
			false,
			`${sourceFile} must not advertise /sf autonomous`,
		);
		assert.equal(
			source.includes("Manage: /sf schedule list"),
			false,
			`${sourceFile} must not advertise the removed slash namespace`,
		);
	}
});

test("guardrails_registers_safegit_as_slash_command_surface", () => {
	const commands = new Map();
	const pi = {
		registerCommand(name, options) {
			commands.set(name, options);
		},
		on() {},
	};

	guardrails(pi);

	for (const commandName of [
		"safegit",
		"safegit-level",
		"safegit-status",
		"yolo",
	]) {
		const command = commands.get(commandName);
		assert.equal(typeof command?.handler, "function");
		assert.match(command.description, new RegExp(`/${commandName}\\b`));
		assert.match(command.description, /Slash command/);
	}
});
