import assert from "node:assert/strict";
import { test } from 'vitest';
import guardrails from "../../guardrails/index.ts";

type ToolCallResult = { block: true; reason: string } | undefined;
type Handler = (
	event: Record<string, unknown>,
	ctx: Record<string, unknown>,
) => Promise<ToolCallResult>;
type Choice = string | undefined | Promise<string | undefined>;

const ALLOW_ONCE = "✅ Allow this command once";
const DECLINE_ONCE = "⏭️  Decline this time (ask again later)";

function installGuardrails(): { toolCall: Handler } {
	const handlers = new Map<string, Handler[]>();
	const pi = {
		registerCommand: () => {},
		on: (event: string, handler: Handler) => {
			const list = handlers.get(event) ?? [];
			list.push(handler);
			handlers.set(event, list);
		},
	};

	guardrails(pi as any);

	const toolCall = handlers.get("tool_call")?.[0];
	assert.ok(toolCall, "guardrails must register a tool_call handler");
	return { toolCall };
}

function makeCtx(choices: Choice[]) {
	const selectCalls: Array<{ title: string; options: string[] }> = [];
	const notifications: Array<{ message: string; type?: string }> = [];

	const ctx = {
		hasUI: true,
		settingsManager: {
			getSettings: () => ({
				safeGit: {
					enabledByDefault: true,
					promptLevel: "medium",
				},
			}),
		},
		ui: {
			select: async (title: string, options: string[]) => {
				selectCalls.push({ title, options });
				return choices.length > 0 ? await choices.shift() : undefined;
			},
			confirm: async () => true,
			input: async () => undefined,
			notify: (message: string, type?: string) => {
				notifications.push({ message, type });
			},
			onTerminalInput: () => () => {},
			setStatus: () => {},
			setWorkingMessage: () => {},
			setWidget: () => {},
		},
	};

	return { ctx, selectCalls, notifications };
}

function mergeEvent(command: string): Record<string, unknown> {
	return {
		type: "tool_call",
		toolName: "bash",
		toolCallId: `tool-${command}`,
		input: { command },
	};
}

test("safe-git merge: once approval re-asks a later command without auto-declining no-answer", async () => {
	const { toolCall } = installGuardrails();
	const { ctx, selectCalls, notifications } = makeCtx([ALLOW_ONCE, undefined]);

	const approved = await toolCall(mergeEvent("git merge feature-a"), ctx);
	assert.equal(approved, undefined);

	const unanswered = await toolCall(mergeEvent("git merge feature-b"), ctx);
	assert.equal(
		selectCalls.length,
		2,
		"a different once-approved merge should be asked again",
	);
	assert.equal(unanswered?.block, true);
	assert.equal(
		unanswered?.reason,
		"Git merge approval not answered; command paused",
	);
	assert.doesNotMatch(unanswered?.reason ?? "", /declined by user/);
	assert.ok(
		notifications.some((entry) => entry.message === "Git merge approved once"),
	);
	assert.ok(
		notifications.some(
			(entry) =>
				entry.message === "Git merge approval not answered; command paused",
		),
	);
});

test("safe-git merge: duplicate in-flight asks share the same approval", async () => {
	const { toolCall } = installGuardrails();
	let resolveChoice: (choice: string | undefined) => void = () => {};
	const choice = new Promise<string | undefined>((resolve) => {
		resolveChoice = resolve;
	});
	const { ctx, selectCalls, notifications } = makeCtx([choice]);

	const first = toolCall(mergeEvent("git merge feature-race"), ctx);
	const second = toolCall(mergeEvent("git merge feature-race"), ctx);

	assert.equal(
		selectCalls.length,
		1,
		"concurrent duplicate gate should show only one prompt",
	);
	resolveChoice(ALLOW_ONCE);

	const [firstResult, secondResult] = await Promise.all([first, second]);
	assert.equal(firstResult, undefined);
	assert.equal(secondResult, undefined);
	assert.equal(selectCalls.length, 1);
	assert.equal(
		notifications.filter((entry) => entry.message === "Git merge approved once")
			.length,
		1,
	);
	assert.equal(
		notifications.some((entry) => entry.message.includes("declined by user")),
		false,
	);
});

test("safe-git merge: quick duplicate after once approval uses cached approval", async () => {
	const { toolCall } = installGuardrails();
	const { ctx, selectCalls, notifications } = makeCtx([ALLOW_ONCE]);

	const first = await toolCall(mergeEvent("git merge feature-cache"), ctx);
	const second = await toolCall(mergeEvent("git merge feature-cache"), ctx);

	assert.equal(first, undefined);
	assert.equal(second, undefined);
	assert.equal(
		selectCalls.length,
		1,
		"cached duplicate approval should not re-open the gate",
	);
	assert.ok(
		notifications.some(
			(entry) =>
				entry.message === "Git merge approval reused for duplicate request",
		),
	);
});

test("safe-git merge: explicit manual decline still blocks as declined by user", async () => {
	const { toolCall } = installGuardrails();
	const { ctx, notifications } = makeCtx([DECLINE_ONCE]);

	const declined = await toolCall(mergeEvent("git merge feature-decline"), ctx);

	assert.equal(declined?.block, true);
	assert.equal(declined?.reason, "Git merge declined by user");
	assert.ok(
		notifications.some((entry) => entry.message === "Git merge declined"),
	);
});
