import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
import type { Mock } from "vitest";
import { AuthProvider } from "@/auth/store";
import { createQueryClient } from "@/lib/queryClient";
import { createAppRouter } from "@/routes";

const GROUPS = {
	groups: [
		{ id: 1, name: "default", safe_search: false },
		{ id: 2, name: "kids", safe_search: true },
	],
};

const BLOCKLISTS = {
	blocklists: [
		{
			id: 1,
			url: "https://example.com/ads.txt",
			name: "Ads",
			enabled: true,
			is_suggested: false,
			last_updated: null,
			domain_count: 100,
			wildcard_count: 0,
			skipped_regex_count: 0,
			checksum: null,
		},
		{
			id: 2,
			url: "https://example.com/malware.txt",
			name: "Malware",
			enabled: true,
			is_suggested: false,
			last_updated: null,
			domain_count: 50,
			wildcard_count: 0,
			skipped_regex_count: 0,
			checksum: null,
		},
	],
};

const VERSION = { version: "0.0.0-test", git_commit: "0000000", zig_version: "0.16.0", uptime_seconds: 1 };

const BASE = {
	"GET /api/groups": GROUPS,
	"GET /api/blocklists": BLOCKLISTS,
	"GET /api/version": VERSION,
	"GET /api/groups/2/sources": { source_ids: [1] },
	"PUT /api/groups/2/sources": { source_ids: [1, 2] },
};

function stubFetch(map: Record<string, unknown>) {
	vi.stubGlobal(
		"fetch",
		vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
			const key = `${init?.method ?? "GET"} ${String(input)}`;
			const payload = map[key];
			if (payload === undefined) {
				return new Response(JSON.stringify({ error: `not stubbed: ${key}` }), { status: 404 });
			}
			return new Response(JSON.stringify(payload), {
				status: 200,
				headers: { "content-type": "application/json" },
			});
		}),
	);
}

async function renderGroupsPage(map: Record<string, unknown>) {
	stubFetch(map);
	const queryClient = createQueryClient();
	const router = createAppRouter(createMemoryHistory({ initialEntries: ["/groups"] }), queryClient);
	render(
		<AuthProvider>
			<QueryClientProvider client={queryClient}>
				<RouterProvider router={router} />
			</QueryClientProvider>
		</AuthProvider>,
	);
	await screen.findByRole("heading", { name: "Groups" });
}

function groupRow(name: string): HTMLElement {
	const row = screen.getByText(name).closest("li");
	if (row === null) throw new Error(`no row for group ${name}`);
	return row;
}

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

test("lists groups; the default group blocks rename and delete client-side", async () => {
	await renderGroupsPage(BASE);

	const defaultRow = groupRow("default");
	expect((within(defaultRow).getByRole("button", { name: "Rename" }) as HTMLButtonElement).disabled).toBe(true);
	expect((within(defaultRow).getByRole("button", { name: "Delete" }) as HTMLButtonElement).disabled).toBe(true);
	expect(within(defaultRow).getByText("The default group cannot be renamed or deleted.")).toBeTruthy();

	const kidsRow = groupRow("kids");
	expect((within(kidsRow).getByRole("button", { name: "Rename" }) as HTMLButtonElement).disabled).toBe(false);
	expect((within(kidsRow).getByRole("button", { name: "Delete" }) as HTMLButtonElement).disabled).toBe(false);
	expect((within(kidsRow).getByRole("checkbox", { name: "Safe search" }) as HTMLInputElement).checked).toBe(true);
});

test("expanding sources loads the set, toggling saves the full set via PUT", async () => {
	await renderGroupsPage(BASE);

	const kidsRow = groupRow("kids");
	fireEvent.click(within(kidsRow).getByRole("button", { name: "Sources" }));

	const ads = (await within(kidsRow).findByRole("checkbox", { name: "Ads" })) as HTMLInputElement;
	const malware = within(kidsRow).getByRole("checkbox", { name: "Malware" }) as HTMLInputElement;
	expect(ads.checked).toBe(true);
	expect(malware.checked).toBe(false);

	const save = within(kidsRow).getByRole("button", { name: "Save sources" }) as HTMLButtonElement;
	expect(save.disabled).toBe(true);

	fireEvent.click(malware);
	expect(save.disabled).toBe(false);
	fireEvent.click(save);

	await waitFor(() => expect(save.disabled).toBe(true));
	const calls = (fetch as unknown as Mock).mock.calls as [RequestInfo | URL, RequestInit | undefined][];
	const put = calls.find(([, init]) => init?.method === "PUT");
	expect(put).toBeTruthy();
	expect(String(put![0])).toBe("/api/groups/2/sources");
	expect(JSON.parse(String(put![1]?.body))).toEqual({ source_ids: [1, 2] });
	expect(malware.checked).toBe(true);
});
