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

const RESPONSES: Record<string, unknown> = {
	"/api/rules": {
		rules: [
			{
				id: 1,
				group_id: 1,
				group: "Default",
				pattern: "ads.example.com",
				kind: "exact",
				action: "block",
				created_at: 1700000000,
			},
			{
				id: 2,
				group_id: 2,
				group: "Kids",
				pattern: "*.cdn.example.com",
				kind: "wildcard",
				action: "allow",
				created_at: 1700000100,
			},
		],
	},
	"/api/version": { version: "0.0.0-test", git_commit: "0000000", zig_version: "0.16.0", uptime_seconds: 1 },
};

// The API orders groups by name, so the id-1 default is not always first.
let groups: { id: number; name: string; safe_search: boolean }[];

beforeEach(() => {
	groups = [
		{ id: 1, name: "Default", safe_search: false },
		{ id: 2, name: "Kids", safe_search: true },
	];
	vi.stubGlobal(
		"fetch",
		vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
			const url = String(input);
			if (url === "/api/rules" && init?.method === "POST") {
				return new Response(JSON.stringify({ error: "rate limited" }), {
					status: 429,
					headers: { "content-type": "application/json", "Retry-After": "5" },
				});
			}
			const payload = url === "/api/groups" ? { groups } : RESPONSES[url];
			if (payload === undefined) return new Response(JSON.stringify({ error: "not stubbed" }), { status: 404 });
			return new Response(JSON.stringify(payload), {
				status: 200,
				headers: { "content-type": "application/json" },
			});
		}),
	);
});

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

function renderRulesRoute() {
	const queryClient = createQueryClient();
	const router = createAppRouter(createMemoryHistory({ initialEntries: ["/rules"] }), queryClient);
	render(
		<AuthProvider>
			<QueryClientProvider client={queryClient}>
				<RouterProvider router={router} />
			</QueryClientProvider>
		</AuthProvider>,
	);
}

test("renders the rule table and the create form with contract enums", async () => {
	renderRulesRoute();
	await screen.findByRole("heading", { name: "Rules" });

	const table = within(screen.getByRole("table"));
	expect(table.getByText("ads.example.com")).toBeTruthy();
	expect(table.getByText("*.cdn.example.com")).toBeTruthy();
	expect(table.getByText("block")).toBeTruthy();
	expect(table.getByText("allow")).toBeTruthy();
	expect(table.getByText("Kids")).toBeTruthy();
	expect(screen.getAllByRole("button", { name: "Delete" })).toHaveLength(2);

	const kindSelect = screen.getByLabelText("Kind") as HTMLSelectElement;
	expect(Array.from(kindSelect.options).map((o) => o.value)).toEqual(["exact", "wildcard"]);

	const actionSelect = screen.getByLabelText("Action") as HTMLSelectElement;
	expect(Array.from(actionSelect.options).map((o) => o.value)).toEqual(["allow", "block"]);

	const groupSelect = screen.getByLabelText("Group") as HTMLSelectElement;
	expect(Array.from(groupSelect.options).map((o) => o.textContent)).toEqual(["Default", "Kids"]);
});

test("rule create shows a countdown when rate limited with Retry-After", async () => {
	renderRulesRoute();
	await screen.findByRole("heading", { name: "Rules" });

	fireEvent.change(screen.getByLabelText("Pattern"), { target: { value: "ads.example.net" } });
	fireEvent.click(screen.getByRole("button", { name: "Create rule" }));

	const alert = await screen.findByRole("alert");
	expect(alert.textContent).toBe("Rate limited. Try again in 5s.");
});

test("the group select preselects the id-1 default, not the alphabetically first group", async () => {
	groups = [
		{ id: 5, name: "Attic", safe_search: false },
		{ id: 1, name: "Default", safe_search: false },
	];
	renderRulesRoute();
	await screen.findByRole("heading", { name: "Rules" });

	const groupSelect = screen.getByLabelText("Group") as HTMLSelectElement;
	expect(Array.from(groupSelect.options).map((o) => o.textContent)).toEqual(["Attic", "Default"]);
	expect(groupSelect.value).toBe("1");
});

test("the group select falls back to the first group when the default is absent", async () => {
	groups = [
		{ id: 5, name: "Attic", safe_search: false },
		{ id: 7, name: "Basement", safe_search: false },
	];
	renderRulesRoute();
	await screen.findByRole("heading", { name: "Rules" });

	expect((screen.getByLabelText("Group") as HTMLSelectElement).value).toBe("5");
});
