import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { settingsApi } from "./settings";

const mockFetch = vi.fn();

beforeEach(() => {
	vi.stubGlobal("fetch", mockFetch);
});

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

function okResponse(body: unknown) {
	return new Response(JSON.stringify(body), {
		status: 200,
		headers: { "Content-Type": "application/json" },
	});
}

function errorResponse(status: number, text: string) {
	return new Response(text, { status });
}

describe("settingsApi", () => {
	describe("getEditorCommand", () => {
		it("sends GET to /settings/editor and returns editor settings", async () => {
			const expected = { editor_command: "zed" };
			mockFetch.mockResolvedValueOnce(okResponse(expected));

			const result = await settingsApi.getEditorCommand();

			expect(mockFetch).toHaveBeenCalledWith(
				"/api/settings/editor",
				expect.objectContaining({
					headers: expect.objectContaining({
						"Content-Type": "application/json",
					}),
				}),
			);
			expect(result).toEqual(expected);
		});

		it("returns null editor_command when not configured", async () => {
			const expected = { editor_command: null };
			mockFetch.mockResolvedValueOnce(okResponse(expected));

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

		it("uses custom baseUrl when provided", async () => {
			mockFetch.mockResolvedValueOnce(okResponse({ editor_command: "code" }));

			await settingsApi.getEditorCommand("http://localhost:4000/api");

			expect(mockFetch).toHaveBeenCalledWith(
				"http://localhost:4000/api/settings/editor",
				expect.anything(),
			);
		});
	});

	describe("setEditorCommand", () => {
		it("sends PUT to /settings/editor with command body", async () => {
			const expected = { editor_command: "zed" };
			mockFetch.mockResolvedValueOnce(okResponse(expected));

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

			expect(mockFetch).toHaveBeenCalledWith(
				"/api/settings/editor",
				expect.objectContaining({
					method: "PUT",
					body: JSON.stringify({ editor_command: "zed" }),
				}),
			);
			expect(result).toEqual(expected);
		});

		it("sends PUT with null to clear the editor command", async () => {
			const expected = { editor_command: null };
			mockFetch.mockResolvedValueOnce(okResponse(expected));

			const result = await settingsApi.setEditorCommand(null);

			expect(mockFetch).toHaveBeenCalledWith(
				"/api/settings/editor",
				expect.objectContaining({
					method: "PUT",
					body: JSON.stringify({ editor_command: null }),
				}),
			);
			expect(result.editor_command).toBeNull();
		});

		it("uses custom baseUrl when provided", async () => {
			mockFetch.mockResolvedValueOnce(okResponse({ editor_command: "vim" }));

			await settingsApi.setEditorCommand("vim", "http://localhost:4000/api");

			expect(mockFetch).toHaveBeenCalledWith(
				"http://localhost:4000/api/settings/editor",
				expect.objectContaining({ method: "PUT" }),
			);
		});
	});

	describe("error handling", () => {
		it("throws with response body text on non-ok response", async () => {
			mockFetch.mockResolvedValueOnce(errorResponse(400, "Bad Request"));

			await expect(settingsApi.getEditorCommand()).rejects.toThrow(
				"Bad Request",
			);
		});

		it("throws with status code message when response body is empty", async () => {
			mockFetch.mockResolvedValueOnce(errorResponse(500, ""));

			await expect(settingsApi.getEditorCommand()).rejects.toThrow(
				"Request failed (500)",
			);
		});

		it("throws on setEditorCommand error", async () => {
			mockFetch.mockResolvedValueOnce(errorResponse(403, "Forbidden"));

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