/** Tests for the `settings` WS-RPC client (project settings read/write). */
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ProjectSettings } from "./settings";
import { settingsApi } from "./settings";
import { installRpcMock } from "./__test_utils__/mockRpcWebSocket";

afterEach(() => {
	vi.restoreAllMocks();
});

const defaultProjectSettings: ProjectSettings = {
	default_qa: "server",
	default_coder_model: null,
	max_coders: null,
	max_retries: 2,
	base_branch: null,
	rate_limit_notifications: true,
	timezone: null,
	rendezvous: null,
	watcher_sweep_interval_secs: 60,
	watcher_done_retention_secs: 14400,
};

describe("settingsApi", () => {
	describe("getProjectSettings", () => {
		it("dispatches settings.get_project RPC and returns project settings", async () => {
			const rpc = installRpcMock();
			rpc.respond("settings.get_project", defaultProjectSettings);

			const result = await settingsApi.getProjectSettings();

			expect(rpc.calls).toEqual([
				{ method: "settings.get_project", params: {} },
			]);
			expect(result).toEqual(defaultProjectSettings);
		});

		it("surfaces RPC errors visibly", async () => {
			const rpc = installRpcMock();
			rpc.respondError("settings.get_project", "no project open", "INTERNAL");

			await expect(settingsApi.getProjectSettings()).rejects.toThrow(
				"no project open",
			);
		});
	});

	describe("putProjectSettings", () => {
		it("dispatches settings.put_project RPC with settings", async () => {
			const updated = { ...defaultProjectSettings, default_qa: "agent" };
			const rpc = installRpcMock();
			rpc.respond("settings.put_project", updated);

			const result = await settingsApi.putProjectSettings(updated);

			expect(rpc.calls).toEqual([
				{ method: "settings.put_project", params: updated },
			]);
			expect(result.default_qa).toBe("agent");
		});

		it("throws on validation error from RPC", async () => {
			const rpc = installRpcMock();
			rpc.respondError(
				"settings.put_project",
				"Invalid default_qa value",
				"INVALID",
			);
			await expect(
				settingsApi.putProjectSettings({
					...defaultProjectSettings,
					default_qa: "invalid",
				}),
			).rejects.toThrow("Invalid default_qa value");
		});
	});

	describe("getEditorCommand", () => {
		it("dispatches settings.get_editor RPC and returns editor settings", async () => {
			const rpc = installRpcMock();
			const expected = { editor_command: "zed" };
			rpc.respond("settings.get_editor", expected);

			const result = await settingsApi.getEditorCommand();

			expect(rpc.calls).toEqual([
				{ method: "settings.get_editor", params: {} },
			]);
			expect(result).toEqual(expected);
		});

		it("returns null editor_command when not configured", async () => {
			const rpc = installRpcMock();
			rpc.respond("settings.get_editor", { editor_command: null });

			const result = await settingsApi.getEditorCommand();
			expect(result.editor_command).toBeNull();
		});
	});

	describe("setEditorCommand", () => {
		it("dispatches settings.put_editor RPC with command", async () => {
			const rpc = installRpcMock();
			rpc.respond("settings.put_editor", { editor_command: "zed" });

			const result = await settingsApi.setEditorCommand("zed");

			expect(rpc.calls).toEqual([
				{
					method: "settings.put_editor",
					params: { editor_command: "zed" },
				},
			]);
			expect(result).toEqual({ editor_command: "zed" });
		});

		it("dispatches settings.put_editor with null to clear", async () => {
			const rpc = installRpcMock();
			rpc.respond("settings.put_editor", { editor_command: null });

			const result = await settingsApi.setEditorCommand(null);

			expect(rpc.calls).toEqual([
				{
					method: "settings.put_editor",
					params: { editor_command: null },
				},
			]);
			expect(result.editor_command).toBeNull();
		});
	});

	describe("openFile", () => {
		it("dispatches settings.open_file RPC with path and line", async () => {
			const rpc = installRpcMock();
			rpc.respond("settings.open_file", { ok: true });

			const result = await settingsApi.openFile("src/main.rs", 42);

			expect(rpc.calls).toEqual([
				{
					method: "settings.open_file",
					params: { path: "src/main.rs", line: 42 },
				},
			]);
			expect(result).toEqual({ success: true });
		});

		it("dispatches settings.open_file with null line when omitted", async () => {
			const rpc = installRpcMock();
			rpc.respond("settings.open_file", { ok: true });

			await settingsApi.openFile("src/main.rs");

			expect(rpc.calls).toEqual([
				{
					method: "settings.open_file",
					params: { path: "src/main.rs", line: null },
				},
			]);
		});
	});

	describe("error handling", () => {
		it("surfaces RPC errors for getEditorCommand", async () => {
			const rpc = installRpcMock();
			rpc.respondError("settings.get_editor", "store unavailable", "INTERNAL");

			await expect(settingsApi.getEditorCommand()).rejects.toThrow(
				"store unavailable",
			);
		});

		it("surfaces RPC errors for setEditorCommand", async () => {
			const rpc = installRpcMock();
			rpc.respondError("settings.put_editor", "Forbidden", "FORBIDDEN");

			await expect(settingsApi.setEditorCommand("code")).rejects.toThrow(
				"Forbidden",
			);
		});
	});
});
