/**
 * The Clients pages under a real router: both of them read the same list, and
 * the detail route is reached by deep link as often as by click, so the tests
 * drive the router rather than the components.
 */

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

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

export const CLIENTS = {
	clients: [
		{
			id: 1,
			ip: "192.168.1.10",
			name: "laptop",
			learned_name: "laptop-1.lan",
			group_id: 1,
			group: "default",
			hand_edited: true,
			first_seen: 1700000000,
			last_seen: 1700003600,
		},
		{
			id: 2,
			ip: "192.168.1.11",
			name: "",
			learned_name: "kids-tablet.lan",
			group_id: 2,
			group: "kids",
			hand_edited: false,
			first_seen: 1700000000,
			last_seen: 1700007200,
		},
	],
};

export const PREFIXES = {
	client_prefixes: [{ id: 1, prefix: "192.168.1.0/24", group_id: 2, group: "kids", priority: 100 }],
};

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

export const DATABASE = { authority: "database", path: null, reconciled_at: null, restart_pending: false };
export const MANAGED_FILE = {
	authority: "managed_file",
	path: "/etc/nxdns/config.zon",
	reconciled_at: 1754899200,
	restart_pending: false,
};

export const BASE: Record<string, unknown> = {
	"GET /api/clients": CLIENTS,
	"GET /api/client-prefixes": PREFIXES,
	"GET /api/groups": GROUPS,
	"GET /api/version": VERSION,
	"GET /api/config/status": DATABASE,
};

/** A request that never settles, so its query stays pending for the whole test. */
export const NEVER = Symbol("never");

export function stubFetch(map: Record<string, unknown>): ReturnType<typeof vi.fn> {
	const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
		const key = `${init?.method ?? "GET"} ${String(input)}`;
		const payload = map[key];
		if (payload === NEVER) return new Promise<Response>(() => {});
		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" },
		});
	});
	vi.stubGlobal("fetch", fetchMock);
	return fetchMock;
}

export function renderAt(path: string, map: Record<string, unknown>) {
	const fetchMock = stubFetch(map);
	const queryClient = createQueryClient();
	const router = createAppRouter(createMemoryHistory({ initialEntries: [path] }), queryClient);
	render(
		<AuthProvider>
			<QueryClientProvider client={queryClient}>
				<RouterProvider router={router} />
			</QueryClientProvider>
		</AuthProvider>,
	);
	return { router, fetchMock, queryClient };
}

export async function renderClientsPage(map: Record<string, unknown> = BASE) {
	const handles = renderAt("/clients", map);
	await screen.findByRole("heading", { name: "Clients" });
	return handles;
}

/**
 * Turns configuration authority under a mounted page, the way the status
 * query's own poll would: the stub reads the map on each call, so replacing the
 * entry and refetching is the whole transition. `undefined` stands for a failed
 * status — the stub answers 404 for a key it does not hold.
 */
export async function setConfigStatus(
	map: Record<string, unknown>,
	queryClient: QueryClient,
	status: unknown,
): Promise<void> {
	map["GET /api/config/status"] = status;
	await act(async () => {
		await queryClient.refetchQueries({ queryKey: queryKeys.configStatus });
		// The cache lands before its observers are notified — react-query defers
		// that notification — so the render pass needs one more turn inside act.
		await new Promise((resolve) => setTimeout(resolve, 0));
	});
}
