import { fireEvent, render, screen } 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";

/** Wall clock at import; the upstream fixtures date their failures against it. */
const NOW_S = Math.floor(Date.now() / 1000);

const RESPONSES: Record<string, unknown> = {
	"/api/stats?period=24h": {
		period: "24h",
		since: 0,
		until: 86400,
		queries: 1000,
		blocked: 250,
		cached: 100,
		clients: 7,
		avg_response_time_us: 2345,
		coverage: { complete: true, available_since: 0 },
	},
	"/api/stats/timeseries?period=24h": {
		period: "24h",
		since: 0,
		until: 86400,
		bucket_seconds: 1800,
		buckets: [
			{ ts: 0, queries: 60, blocked: 20, cached: 10 },
			{ ts: 1800, queries: 40, blocked: 0, cached: 0 },
			{ ts: 3600, queries: 0, blocked: 0, cached: 0 },
		],
		coverage: { complete: true, available_since: 0 },
	},
	"/api/stats?period=1h": {
		period: "1h",
		since: 0,
		until: 3600,
		queries: 12,
		blocked: 3,
		cached: 0,
		clients: 2,
		avg_response_time_us: null,
		coverage: { complete: true, available_since: 0 },
	},
	"/api/stats/timeseries?period=1h": {
		period: "1h",
		since: 0,
		until: 3600,
		bucket_seconds: 60,
		buckets: [],
		coverage: { complete: true, available_since: 0 },
	},
	"/api/health": {
		status: "degraded",
		disk: {
			state: "warn",
			free_bytes: 400 * 1024 * 1024,
			db_bytes: 12 * 1024 * 1024,
			log_bytes: 2048,
			sample_failures: 0,
		},
		upstreams: { available: 1, total: 2 },
		queries_dropped: 5,
		writer_failed: false,
		refreshes_gated: 0,
		snapshot_generation: 3,
		diagnostics: { state: "recording", active_warnings: 1, active_errors: 0 },
	},
	"/api/upstream/health?period=24h": {
		period: "24h",
		since: NOW_S - 86_400,
		until: NOW_S,
		available: 1,
		total: 2,
		complete: true,
		upstreams: [
			{
				url: "https://dns.example/dns-query",
				enabled: true,
				available: false,
				period: {
					attempts: 100,
					successes: 90,
					failures: 10,
					success_rate: 0.9,
					// 3h30m before the fixture's now, far from a unit boundary.
					last_failure_at: NOW_S - 12_600,
					last_failure_error: "timeout",
				},
			},
			{
				url: "udp://9.9.9.9:53",
				enabled: true,
				available: true,
				period: {
					attempts: 100,
					successes: 100,
					failures: 0,
					success_rate: 1,
					last_failure_at: null,
					last_failure_error: null,
				},
			},
		],
	},
	"/api/upstream/health?period=1h": {
		period: "1h",
		since: NOW_S - 3600,
		until: NOW_S,
		available: 1,
		total: 1,
		complete: true,
		upstreams: [
			{
				url: "https://dns.example/dns-query",
				enabled: true,
				available: true,
				period: {
					attempts: 7,
					successes: 6,
					failures: 1,
					success_rate: 6 / 7,
					last_failure_at: NOW_S - 300,
					last_failure_error: "timeout",
				},
			},
		],
	},
	"/api/version": { version: "0.0.0-test", git_commit: "0000000", zig_version: "0.16.0", uptime_seconds: 1 },
};

// Endpoints forced to fail with a 4xx, which the query client does not retry.
let failing: Set<string>;

beforeEach(() => {
	failing = new Set();
	vi.stubGlobal(
		"fetch",
		vi.fn(async (input: RequestInfo | URL) => {
			const url = String(input);
			if (failing.has(url)) {
				return new Response(JSON.stringify({ error: "upstream health unavailable" }), {
					status: 400,
					headers: { "content-type": "application/json" },
				});
			}
			const payload = 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 renderDashboard() {
	const queryClient = createQueryClient();
	const router = createAppRouter(createMemoryHistory({ initialEntries: ["/"] }), queryClient);
	render(
		<AuthProvider>
			<QueryClientProvider client={queryClient}>
				<RouterProvider router={router} />
			</QueryClientProvider>
		</AuthProvider>,
	);
}

test("dashboard renders stats, chart, disk card, upstream table and health banners", async () => {
	renderDashboard();
	await screen.findByRole("heading", { name: "Dashboard" });

	expect(screen.getByText("1,000")).toBeTruthy();
	expect(screen.getByText("250")).toBeTruthy();
	expect(screen.getByText("25.0%")).toBeTruthy();
	expect(screen.getByText("7")).toBeTruthy();
	expect(screen.getByText("2.3 ms")).toBeTruthy();

	expect(screen.getByRole("img", { name: /queries over time/i })).toBeTruthy();
	expect(screen.getByText("Blocked", { selector: "li" })).toBeTruthy();

	expect(screen.getByText("Storage now")).toBeTruthy();
	expect(screen.getByText("warn")).toBeTruthy();
	expect(screen.getAllByText("400.0 MiB").length).toBeGreaterThan(0);
	expect(screen.getByText("12.0 MiB")).toBeTruthy();
	expect(screen.getByText("2.0 KiB")).toBeTruthy();

	const alerts = screen.getAllByRole("alert");
	expect(alerts.some((alert) => /disk space low/i.test(alert.textContent ?? ""))).toBe(true);
	expect(alerts.some((alert) => /5 queries dropped/i.test(alert.textContent ?? ""))).toBe(true);

	expect(screen.getByText("https://dns.example/dns-query")).toBeTruthy();
	expect(screen.getByText("90.0%")).toBeTruthy();
	expect(screen.getByText("100.0%")).toBeTruthy();
	expect(screen.getByText("1/2 available")).toBeTruthy();
});

test("live state is labeled on its own card, not by a section that disowns the picker", async () => {
	renderDashboard();
	await screen.findByRole("heading", { name: "Dashboard" });

	expect(screen.getByText("Storage now")).toBeTruthy();
	expect(screen.queryByRole("region", { name: "Right now" })).toBeNull();
	expect(screen.queryByText("Right now")).toBeNull();
	expect(screen.queryByText("Snapshot state; the period above does not apply.")).toBeNull();
});

test("the period picker rescopes the upstream table", async () => {
	renderDashboard();
	await screen.findByRole("heading", { name: "Dashboard" });
	await screen.findByText("90.0%");

	fireEvent.click(screen.getByRole("button", { name: "1h" }));

	await screen.findByText("85.7%");
	expect(screen.getByRole("columnheader", { name: "Selected period · 1h" })).toBeTruthy();
	expect(screen.queryByText("90.0%")).toBeNull();
});

test("period picker refetches stats and shows the empty chart state", async () => {
	renderDashboard();
	await screen.findByRole("heading", { name: "Dashboard" });

	fireEvent.click(screen.getByRole("button", { name: "1h" }));

	await screen.findByText("12");
	expect(screen.getByRole("button", { name: "1h" }).getAttribute("aria-pressed")).toBe("true");
	expect(screen.getByRole("button", { name: "24h" }).getAttribute("aria-pressed")).toBe("false");
	await screen.findByText("No queries in this period.");
	expect(screen.getByText("—", { selector: "span" })).toBeTruthy();
});

test("one failing endpoint degrades its own widget on cold navigation", async () => {
	failing.add("/api/upstream/health?period=24h");
	renderDashboard();
	await screen.findByRole("heading", { name: "Dashboard" });

	// The page renders; only the upstream widget carries the error.
	await screen.findByText("upstream health unavailable");
	expect(screen.queryByText("Something went wrong")).toBeNull();
	expect(screen.queryByText("Request failed (400)")).toBeNull();

	expect(screen.getByText("1,000")).toBeTruthy();
	expect(screen.getByRole("img", { name: /queries over time/i })).toBeTruthy();
	expect(screen.getByText("Storage now")).toBeTruthy();
	expect(screen.queryByText("https://dns.example/dns-query")).toBeNull();
});
