import { act, fireEvent, screen, waitFor } from "@testing-library/react";
import { createAppRouter } from "@/routes";
import { validateGroupId, validateProtectionSearch, validateResolutionSearch } from "@/features/configuration/search";
import { DATABASE, renderPage, stubApi } from "./testFixtures";

/**
 * The configuration URL is the applied state: which page, which tab, which
 * group. A view of a page is a link, and the back button walks what the reader
 * chose rather than what the page corrected on their behalf.
 */

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

test("the six resource routes are gone, with no alias left behind", () => {
	const paths = new Set(Object.keys(createAppRouter().routesByPath));
	for (const gone of ["/groups", "/blocklists", "/rules", "/local-dns", "/upstreams", "/settings"]) {
		expect(paths.has(gone)).toBe(false);
	}
	for (const kept of ["/configuration/protection", "/configuration/resolution", "/configuration/system"]) {
		expect(paths.has(kept)).toBe(true);
	}
	// No landing route either: `/configuration` is not a page.
	expect(paths.has("/configuration")).toBe(false);
});

test("an unknown tab falls back to the page default rather than an empty panel", () => {
	expect(validateProtectionSearch({ tab: "upstreams" }).tab).toBe("groups");
	expect(validateProtectionSearch({}).tab).toBe("groups");
	expect(validateResolutionSearch({ tab: "sources" }).tab).toBe("upstreams");
	expect(validateResolutionSearch({ tab: "zones" }).tab).toBe("zones");
});

test("a group id is a positive integer or nothing", () => {
	expect(validateGroupId(3)).toBe(3);
	expect(validateGroupId(0)).toBeUndefined();
	expect(validateGroupId(-1)).toBeUndefined();
	expect(validateGroupId(1.5)).toBeUndefined();
	expect(validateGroupId("2")).toBeUndefined();
});

test("an unknown group falls back to the first one and rewrites the URL in place", async () => {
	stubApi(DATABASE);
	const router = await renderPage("/configuration/protection?group=99", "Protection");

	await waitFor(() => expect(router.state.location.search.group).toBe(1));
	await screen.findByRole("heading", { name: "default", level: 2 });
	// `replace`: the corrected address did not become a place to go back to.
	expect(router.history.length).toBe(1);
});

test("selecting a group pushes a history entry the back button walks", async () => {
	stubApi(DATABASE);
	const router = await renderPage("/configuration/protection", "Protection");
	await waitFor(() => expect(router.state.location.search.group).toBe(1));

	fireEvent.click(screen.getByRole("link", { name: "kids" }));
	await waitFor(() => expect(router.state.location.search.group).toBe(2));
	await screen.findByRole("heading", { name: "kids", level: 2 });

	act(() => router.history.back());
	await waitFor(() => expect(router.state.location.search.group).toBe(1));
});

test("the selected group survives a tab change, and the change is a history entry", async () => {
	stubApi(DATABASE);
	const router = await renderPage("/configuration/protection?group=2", "Protection");
	await screen.findByRole("heading", { name: "kids", level: 2 });

	fireEvent.click(screen.getByRole("tab", { name: "Sources" }));
	await screen.findByRole("button", { name: "Update now" });
	expect(router.state.location.search).toEqual({ tab: "sources", group: 2 });

	act(() => router.history.back());
	await waitFor(() => expect(router.state.location.search.tab).toBe("groups"));
	expect(router.state.location.search.group).toBe(2);
	await screen.findByRole("heading", { name: "kids", level: 2 });
});

test("the Resolution tab is URL state too", async () => {
	stubApi(DATABASE);
	const router = await renderPage("/configuration/resolution", "Resolution");

	fireEvent.click(screen.getByRole("tab", { name: "Forward zones" }));
	await waitFor(() => expect(router.state.location.search.tab).toBe("zones"));
	await screen.findByRole("button", { name: "Add zone" });
});

test("the loaders are started, not awaited: the page renders while a collection is in flight", async () => {
	// Groups never answers. If the loader were awaited the navigation would hang
	// and neither the heading nor the tabs would ever paint.
	stubApi(DATABASE, { responses: { "GET /api/groups": new Promise<Response>(() => {}) } });
	await renderPage("/configuration/protection", "Protection");

	expect(screen.getByRole("tab", { name: "Sources" })).toBeTruthy();
	expect(await screen.findByText("Loading…")).toBeTruthy();
});
