/**
 * stream-adapter-permissions.test.ts — Permission handler tests for claude-code-cli.
 *
 * Covers:
 * - buildBashPermissionPattern (unit)
 * - createClaudeCodeCanUseToolHandler always-allow paths:
 *   - Bash tool: suggestions present vs absent
 *   - Non-Bash tool: empty suggestions fallback (persists toolName-scoped rule)
 *   - Non-Bash tool: non-empty suggestions (pass-through)
 *
 * Behaviour contracts:
 * - always-allow for non-Bash tool with no suggestions must return updatedPermissions
 *   so the SDK can persist the choice (fix: non-Bash always-allow silent failure)
 * - always-allow for Bash must include ruleContent derived from the command pattern
 */

import { describe, expect, it, vi } from "vitest";
import {
	buildBashPermissionPattern,
	createClaudeCodeCanUseToolHandler,
} from "../stream-adapter.js";

// ─── buildBashPermissionPattern unit tests ────────────────────────────────

describe("buildBashPermissionPattern", () => {
	it("simple_command_returns_wildcard_pattern", () => {
		expect(buildBashPermissionPattern("ls -la")).toBe("Bash(ls:*)");
	});

	it("git_command_includes_subcommand", () => {
		expect(buildBashPermissionPattern("git push origin main")).toBe(
			"Bash(git push:*)",
		);
	});

	it("gh_command_includes_two_subcommand_levels", () => {
		expect(buildBashPermissionPattern("gh pr list")).toBe("Bash(gh pr list:*)");
	});

	it("compound_command_extracts_meaningful_operation", () => {
		// cd is passthrough; meaningful operation is gh pr create
		const result = buildBashPermissionPattern("cd /repo && gh pr create");
		expect(result).toBe("Bash(gh pr create:*)");
	});
});

// ─── Helpers ──────────────────────────────────────────────────────────────

function makeSignal(aborted = false): AbortSignal {
	const ctrl = new AbortController();
	if (aborted) ctrl.abort();
	return ctrl.signal;
}

function makeUi(selectChoices: string[]) {
	let callIndex = 0;
	return {
		select: vi.fn(async () => selectChoices[callIndex++]),
		notify: vi.fn(),
	};
}

function makeOptions(overrides: Record<string, unknown> = {}) {
	return {
		toolUseID: "tu-001",
		signal: makeSignal(),
		suggestions: undefined as unknown,
		title: undefined as unknown,
		description: undefined as unknown,
		...overrides,
	};
}

// ─── createClaudeCodeCanUseToolHandler ────────────────────────────────────

describe("createClaudeCodeCanUseToolHandler", () => {
	it("returns_undefined_when_no_ui", () => {
		expect(createClaudeCodeCanUseToolHandler(undefined)).toBeUndefined();
	});

	it("allow_once_returns_allow_without_updatedPermissions", async () => {
		const ui = makeUi(["Allow"]);
		const handler = createClaudeCodeCanUseToolHandler(ui)!;
		const result = await handler(
			"AskUserQuestion",
			{ questions: ["Name?"] },
			makeOptions() as never,
		);
		expect(result.behavior).toBe("allow");
		expect(
			(result as { updatedPermissions?: unknown }).updatedPermissions,
		).toBeUndefined();
	});

	it("deny_returns_deny_behavior", async () => {
		const ui = makeUi(["Deny"]);
		const handler = createClaudeCodeCanUseToolHandler(ui)!;
		const result = await handler("AskUserQuestion", {}, makeOptions() as never);
		expect(result.behavior).toBe("deny");
	});

	it("always_allow_non_bash_empty_suggestions_returns_toolname_rule", async () => {
		// Core contract: non-Bash "Always Allow" with no SDK suggestions must
		// produce updatedPermissions so the choice persists. Without this fix,
		// the handler returned behavior:"allow" with no updatedPermissions, and
		// the SDK silently forgot the choice on the next call.
		const ui = makeUi(["Always Allow"]);
		const handler = createClaudeCodeCanUseToolHandler(ui)!;
		const result = await handler(
			"AskUserQuestion",
			{ questions: ["Name?"] },
			makeOptions({ suggestions: [] }) as never,
		);
		expect(result.behavior).toBe("allow");
		const perms = (result as { updatedPermissions?: unknown[] })
			.updatedPermissions;
		expect(Array.isArray(perms)).toBe(true);
		expect(perms!.length).toBeGreaterThan(0);
		const rule = perms![0] as {
			type: string;
			rules: Array<{ toolName: string }>;
			behavior: string;
			destination: string;
		};
		expect(rule.type).toBe("addRules");
		expect(rule.behavior).toBe("allow");
		expect(rule.destination).toBe("localSettings");
		// Rule must scope to the tool name, not a specific input hash
		expect(rule.rules[0].toolName).toBe("AskUserQuestion");
		expect(Object.keys(rule.rules[0])).not.toContain("ruleContent");
	});

	it("always_allow_non_bash_with_sdk_suggestions_passes_them_through", async () => {
		// When the SDK already provides suggestions, pass them through unchanged.
		const sdkSuggestions = [
			{
				type: "addRules",
				rules: [{ toolName: "AskUserQuestion", ruleContent: "some-content" }],
				behavior: "allow",
				destination: "localSettings",
			},
		];
		const ui = makeUi(["Always Allow"]);
		const handler = createClaudeCodeCanUseToolHandler(ui)!;
		const result = await handler(
			"AskUserQuestion",
			{ questions: ["What?"] },
			makeOptions({ suggestions: sdkSuggestions }) as never,
		);
		expect(result.behavior).toBe("allow");
		const perms = (result as { updatedPermissions?: unknown[] })
			.updatedPermissions;
		expect(perms).toEqual(sdkSuggestions);
	});

	it("always_allow_bash_no_suggestions_builds_bash_rule", async () => {
		// For Bash with no suggestions, a rule with ruleContent must be built.
		const ui = makeUi(["Always Allow", "Bash(ls:*)"]);
		const handler = createClaudeCodeCanUseToolHandler(ui)!;
		const result = await handler(
			"Bash",
			{ command: "ls -la" },
			makeOptions({ suggestions: [] }) as never,
		);
		expect(result.behavior).toBe("allow");
		const perms = (result as { updatedPermissions?: unknown[] })
			.updatedPermissions;
		expect(Array.isArray(perms)).toBe(true);
		const rule = perms![0] as {
			type: string;
			rules: Array<{ toolName: string; ruleContent: string }>;
		};
		expect(rule.type).toBe("addRules");
		expect(rule.rules[0].toolName).toBe("Bash");
		expect(typeof rule.rules[0].ruleContent).toBe("string");
		expect(rule.rules[0].ruleContent.length).toBeGreaterThan(0);
	});

	it("aborted_signal_returns_deny", async () => {
		const ui = makeUi([]);
		const handler = createClaudeCodeCanUseToolHandler(ui)!;
		const result = await handler(
			"AskUserQuestion",
			{},
			makeOptions({ signal: makeSignal(true) }) as never,
		);
		expect(result.behavior).toBe("deny");
	});
});
