/**
 * The health strip on the Diagnostics page, through the real router.
 *
 * Its load contract is migrated whole from the deleted Overview status section:
 * a visible loading state before the first reading, an error row with Retry when
 * the first read fails, and a refetch failure that marks the conditions on
 * screen as the last reading rather than the current state. What is new is that
 * a condition explained on this page narrows this page instead of navigating.
 */

import { fireEvent, render, screen, waitFor, 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";
import { health } from "@/lib/healthFixture";
import type { Health } from "@/lib/types";

let healthBody: Health;
let healthFails: boolean;
let requested: string[];
/** Held open to keep a health request in flight while a test looks at the strip. */
let pendingHealth: Promise<void> | null;

function json(payload: unknown, status = 200): Response {
	return new Response(JSON.stringify(payload), { status, headers: { "content-type": "application/json" } });
}

beforeEach(() => {
	healthBody = health();
	healthFails = false;
	requested = [];
	pendingHealth = null;
	vi.stubGlobal(
		"fetch",
		vi.fn(async (input: RequestInfo | URL) => {
			const url = String(input);
			requested.push(url);
			if (url === "/api/health") {
				if (pendingHealth !== null) await pendingHealth;
				return healthFails ? json({ error: "health unavailable" }, 400) : json(healthBody);
			}
			if (url === "/api/version")
				return json({ version: "0.0.0-test", git_commit: "0000000", zig_version: "0.16.0", uptime_seconds: 1 });
			if (url.startsWith("/api/diagnostics"))
				return json({ events: [], next_before: null, active: { warnings: 0, errors: 0 } });
			return json({ error: "not stubbed" }, 404);
		}),
	);
});

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

function renderDiagnostics(path = "/diagnostics") {
	const queryClient = createQueryClient();
	const defaults = queryClient.getDefaultOptions();
	queryClient.setDefaultOptions({ ...defaults, queries: { ...defaults.queries, retry: false } });
	const router = createAppRouter(createMemoryHistory({ initialEntries: [path] }), queryClient);
	render(
		<AuthProvider>
			<QueryClientProvider client={queryClient}>
				<RouterProvider router={router} />
			</QueryClientProvider>
		</AuthProvider>,
	);
	return { router, queryClient };
}

function strip(): HTMLElement {
	return screen.getByRole("list", { name: "Current status" });
}

function fact(label: string): HTMLElement {
	// First match, not only match: a condition's label and the link it offers can
	// be the same word — Upstreams links to Upstreams — and the label comes first.
	const cell = within(strip()).getAllByText(label)[0];
	const item = cell.closest("li");
	if (item === null) throw new Error(`no health fact for ${label}`);
	return item;
}

test("the five conditions are stated in words, healthy ones without a way out", async () => {
	renderDiagnostics();
	await waitFor(() => expect(strip()).toBeTruthy());

	for (const [label, value] of [
		["Protection", "Active"],
		["Upstreams", "Available"],
		["Query history", "Recording"],
		["Diagnostics", "Recording"],
		["Storage", "OK"],
	] as const) {
		expect(within(fact(label)).getByText(value)).toBeTruthy();
	}
	expect(within(strip()).queryByRole("link")).toBeNull();
});

test("protection unavailable sends the reader to the sources, upstreams to the pool", async () => {
	healthBody = health({
		protection: { state: "unavailable", until: null },
		upstreams: { state: "unavailable", available: 0, total: 2 },
	});
	renderDiagnostics();
	await waitFor(() => expect(strip()).toBeTruthy());

	expect(within(fact("Protection")).getByRole("link", { name: "Blocklist sources" }).getAttribute("href")).toBe(
		"/configuration/protection?tab=sources",
	);
	expect(within(fact("Upstreams")).getByRole("link", { name: "Upstreams" }).getAttribute("href")).toBe(
		"/configuration/resolution?tab=upstreams",
	);
});

test("a losing query log narrows this page to the disk, a failed writer to the query log", async () => {
	healthBody = health({ query_history: { state: "losing", dropped_total: 4, last_drop_s: null } });
	const { router } = renderDiagnostics();
	await waitFor(() => expect(strip()).toBeTruthy());
	expect(within(fact("Query history")).getByText("4 queries dropped")).toBeTruthy();

	fireEvent.click(within(fact("Query history")).getByRole("link", { name: "Disk diagnostics" }));

	await waitFor(() => expect(router.state.location.search).toEqual({ component: "disk" }));
	await waitFor(() => expect(requested.some((url) => url.includes("component=disk"))).toBe(true));
});

test("a filter link drops a time window that would hide the episodes it points at", async () => {
	healthBody = health({ disk: { state: "critical", free_bytes: 0 } });
	const { router } = renderDiagnostics("/diagnostics?since=1000&until=2000&severity=error&state=resolved");
	await waitFor(() => expect(strip()).toBeTruthy());

	fireEvent.click(within(fact("Storage")).getByRole("link", { name: "Disk diagnostics" }));

	// Everything that could hide the episode goes with the bounds: `state=resolved`
	// would exclude the active disk episode this link exists to show, and an
	// `error` severity would exclude it whenever it is a warning.
	await waitFor(() => expect(router.state.location.search).toEqual({ component: "disk" }));
});

test("an unavailable diagnostics store explains itself and offers no link into itself", async () => {
	healthBody = health({ diagnostics: { state: "unavailable", active_warnings: 0, active_errors: 0 } });
	renderDiagnostics();
	await waitFor(() => expect(strip()).toBeTruthy());

	const row = fact("Diagnostics");
	expect(within(row).getByText(/not being recorded/)).toBeTruthy();
	expect(within(row).queryByRole("link")).toBeNull();
});

test("the strip says it is loading before the first reading, never empty conditions", async () => {
	// Through the route, which is the path that matters: the loader starts the
	// health request without waiting for it, so the page paints while the reading
	// is still in flight and the strip has to say so.
	let release = () => {};
	pendingHealth = new Promise<void>((resolve) => {
		release = resolve;
	});
	renderDiagnostics();

	expect(await screen.findByText("Loading status…")).toBeTruthy();
	// Scoped to the page: "Protection" is also a nav destination now.
	expect(within(document.querySelector("main") as HTMLElement).queryByText("Protection")).toBeNull();

	release();
	await waitFor(() => expect(strip()).toBeTruthy());
	expect(screen.queryByText("Loading status…")).toBeNull();
});

test("a failed first health read is an error row with Retry, not a healthy strip", async () => {
	healthFails = true;
	renderDiagnostics();

	await screen.findByText("health unavailable");
	expect(screen.queryByRole("list", { name: "Current status" })).toBeNull();
});

test("a reading that has gone stale says so rather than passing for current", async () => {
	vi.useFakeTimers({ shouldAdvanceTime: true });
	renderDiagnostics();
	await vi.waitFor(() => expect(strip()).toBeTruthy());
	expect(within(fact("Storage")).getByText("OK")).toBeTruthy();

	// The next poll fails. The conditions on screen are the last that arrived and
	// must not keep passing for the current state.
	healthFails = true;
	await vi.advanceTimersByTimeAsync(11_000);

	await vi.waitFor(() => expect(screen.getByText(/last reading that arrived/)).toBeTruthy());
	expect(within(fact("Storage")).getByText("OK")).toBeTruthy();

	// Recovery clears the caption rather than leaving the page permanently unsure.
	healthFails = false;
	await vi.advanceTimersByTimeAsync(11_000);
	await vi.waitFor(() => expect(screen.queryByText(/last reading that arrived/)).toBeNull());
});
