import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent, type AgentMessage } from "@singularity-forge/agent-core";
import { afterEach, beforeEach, describe, it } from "vitest";
import { AgentSession } from "./agent-session.js";
import { AuthStorage } from "./auth-storage.js";
import { ModelRegistry } from "./model-registry.js";
import { DefaultResourceLoader } from "./resource-loader.js";
import { SessionManager } from "./session-manager.js";
import { SettingsManager } from "./settings-manager.js";

let testDir: string;

async function createSession() {
	const agentDir = join(testDir, "agent-home");
	const authStorage = AuthStorage.inMemory({});
	const modelRegistry = new ModelRegistry(
		authStorage,
		join(agentDir, "models.json"),
	);
	const settingsManager = SettingsManager.inMemory();
	const resourceLoader = new DefaultResourceLoader({
		cwd: testDir,
		agentDir,
		settingsManager,
		noExtensions: true,
		noPromptTemplates: true,
		noThemes: true,
	});
	await resourceLoader.reload();

	return new AgentSession({
		agent: new Agent(),
		sessionManager: SessionManager.inMemory(testDir),
		settingsManager,
		cwd: testDir,
		resourceLoader,
		modelRegistry,
	});
}

describe("AgentSession custom message queueing", () => {
	beforeEach(() => {
		testDir = mkdtempSync(join(tmpdir(), "agent-session-custom-message-"));
	});

	afterEach(() => {
		rmSync(testDir, { recursive: true, force: true });
	});

	it("queues triggerTurn custom messages as steering when the agent is already processing", async () => {
		const session = await createSession();
		const agent = (session as any).agent as Agent & {
			prompt: (message: AgentMessage) => Promise<void>;
			steer: (message: AgentMessage) => void;
		};
		const steered: AgentMessage[] = [];
		agent.prompt = async () => {
			throw new Error(
				"Agent is already processing a prompt. Please wait for it to finish before sending another message.",
			);
		};
		agent.steer = (message) => {
			steered.push(message);
		};

		await session.sendCustomMessage(
			{
				customType: "sf-test",
				content: "continue the active run",
				display: false,
			},
			{ triggerTurn: true },
		);

		assert.equal(steered.length, 1);
		assert.equal(steered[0]?.role, "custom");
		assert.equal((steered[0] as any).customType, "sf-test");
	});

	it("preserves explicit followUp delivery when triggerTurn races with active processing", async () => {
		const session = await createSession();
		const agent = (session as any).agent as Agent & {
			prompt: (message: AgentMessage) => Promise<void>;
			followUp: (message: AgentMessage) => void;
		};
		const followUps: AgentMessage[] = [];
		agent.prompt = async () => {
			throw new Error(
				"Agent is already processing a prompt. Please wait for it to finish before sending another message.",
			);
		};
		agent.followUp = (message) => {
			followUps.push(message);
		};

		await session.sendCustomMessage(
			{
				customType: "sf-test",
				content: "after the current run",
				display: false,
			},
			{ triggerTurn: true, deliverAs: "followUp" },
		);

		assert.equal(followUps.length, 1);
		assert.equal(followUps[0]?.role, "custom");
		assert.equal((followUps[0] as any).content, "after the current run");
	});

	it("executes_slash_command_after_input_hook_transforms_chat_text", async () => {
		const session = await createSession();
		const agent = (session as any).agent as Agent & {
			prompt: (message: AgentMessage) => Promise<void>;
		};
		let agentPrompted = false;
		let commandArgs: string | undefined;
		agent.prompt = async () => {
			agentPrompted = true;
		};
		(session as any)._extensionRunner = {
			hasHandlers: (event: string) => event === "input",
			emitInput: async () => ({
				action: "transform",
				text: "/queue",
				images: undefined,
			}),
			getCommand: (name: string) =>
				name === "queue"
					? {
							name: "queue",
							handler: async (args: string) => {
								commandArgs = args;
							},
						}
					: undefined,
			createCommandContext: () => ({}),
			emitError: () => undefined,
		};

		await session.prompt("show my queue");

		assert.equal(commandArgs, "");
		assert.equal(agentPrompted, false);
	});

	it("executes_slash_command_after_input_hook_adds_route_trace_marker", async () => {
		const session = await createSession();
		const agent = (session as any).agent as Agent & {
			prompt: (message: AgentMessage) => Promise<void>;
		};
		let agentPrompted = false;
		let commandArgs: string | undefined;
		agent.prompt = async () => {
			agentPrompted = true;
		};
		(session as any)._extensionRunner = {
			hasHandlers: (event: string) => event === "input",
			emitInput: async () => ({
				action: "transform",
				text: "[sf-route:chat-intent:test] /queue",
				images: undefined,
			}),
			getCommand: (name: string) =>
				name === "queue"
					? {
							name: "queue",
							handler: async (args: string) => {
								commandArgs = args;
							},
						}
					: undefined,
			createCommandContext: () => ({}),
			emitError: () => undefined,
		};

		await session.prompt("show my queue");

		assert.equal(commandArgs, "");
		assert.equal(agentPrompted, false);
	});
});
