import { act } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import PauseWidget, { formatRemaining } from "@/features/pause/PauseWidget";
import { createQueryClient } from "@/lib/queryClient";
import { queryKeys } from "@/lib/queries";
import type { PausePost, PauseState } from "@/lib/types";

let getState: PauseState;
let postBodies: PausePost[];
let postResponse: (body: PausePost) => PauseState;
let postFailure: (() => Response) | null;

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

beforeEach(() => {
	postBodies = [];
	postFailure = null;
	vi.stubGlobal(
		"fetch",
		vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
			const url = String(input);
			if (url !== "/api/pause") return jsonResponse({ error: "not stubbed" });
			if (init?.method === "POST") {
				const body = JSON.parse(String(init.body)) as PausePost;
				postBodies.push(body);
				if (postFailure !== null) return postFailure();
				return jsonResponse(postResponse(body));
			}
			return jsonResponse(getState);
		}),
	);
});

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

function renderWidget(client?: QueryClient) {
	render(
		<QueryClientProvider client={client ?? createQueryClient()}>
			<PauseWidget />
		</QueryClientProvider>,
	);
}

async function findPauseTrigger(): Promise<HTMLButtonElement> {
	await waitFor(() => {
		const button = screen.getByRole("button", { name: "Pause" }) as HTMLButtonElement;
		expect(button.disabled).toBe(false);
	});
	return screen.getByRole("button", { name: "Pause" }) as HTMLButtonElement;
}

test("unpaused: duration menu pauses with the picked duration_seconds", async () => {
	getState = { paused: false, until: null };
	postResponse = () => ({ paused: true, until: Math.floor(Date.now() / 1000) + 300 });
	renderWidget();

	const trigger = await findPauseTrigger();
	expect(trigger.getAttribute("aria-expanded")).toBe("false");

	fireEvent.click(trigger);
	expect(trigger.getAttribute("aria-expanded")).toBe("true");
	for (const label of ["60 seconds", "5 minutes", "30 minutes", "Indefinitely"]) {
		expect(screen.getByRole("button", { name: label })).toBeTruthy();
	}

	fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));
	await waitFor(() => expect(postBodies).toEqual([{ paused: true, duration_seconds: 300 }]));

	await screen.findByRole("button", { name: "Resume" });
	expect(screen.getByText(/^Paused \d+:\d{2}$/)).toBeTruthy();
});

test("indefinite pause sends no duration_seconds and renders without a countdown", async () => {
	getState = { paused: false, until: null };
	postResponse = () => ({ paused: true, until: null });
	renderWidget();

	fireEvent.click(await findPauseTrigger());
	fireEvent.click(screen.getByRole("button", { name: "Indefinitely" }));
	await waitFor(() => expect(postBodies).toEqual([{ paused: true }]));

	await screen.findByRole("button", { name: "Resume" });
	expect(screen.getByText("Paused")).toBeTruthy();
});

test("resume posts paused false and returns to the Pause button", async () => {
	getState = { paused: true, until: null };
	postResponse = () => ({ paused: false, until: null });
	renderWidget();

	fireEvent.click(await screen.findByRole("button", { name: "Resume" }));
	await waitFor(() => expect(postBodies).toEqual([{ paused: false }]));
	await screen.findByRole("button", { name: "Pause" });
});

test("timed pause counts down live", () => {
	vi.useFakeTimers();
	vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
	const nowSec = Math.floor(Date.now() / 1000);
	const client = createQueryClient();
	client.setQueryData(queryKeys.pause, { paused: true, until: nowSec + 90 });
	renderWidget(client);

	expect(screen.getByText("Paused 1:30")).toBeTruthy();
	act(() => {
		vi.advanceTimersByTime(2000);
	});
	expect(screen.getByText("Paused 1:28")).toBeTruthy();
});

test("escape closes the duration menu", async () => {
	getState = { paused: false, until: null };
	postResponse = () => getState;
	renderWidget();

	const trigger = await findPauseTrigger();
	fireEvent.click(trigger);
	expect(screen.getByRole("button", { name: "Indefinitely" })).toBeTruthy();
	fireEvent.keyDown(trigger, { key: "Escape" });
	expect(screen.queryByRole("button", { name: "Indefinitely" })).toBeNull();
});

test("failed pause with 429 shows a ticking retry countdown", async () => {
	vi.useFakeTimers({ shouldAdvanceTime: true });
	getState = { paused: false, until: null };
	postFailure = () =>
		new Response(JSON.stringify({ error: "rate limited" }), {
			status: 429,
			headers: { "content-type": "application/json", "Retry-After": "30" },
		});
	renderWidget();

	fireEvent.click(await findPauseTrigger());
	fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));

	const alert = await screen.findByRole("alert");
	expect(alert.textContent).toBe("Rate limited. Try again in 30s.");
	act(() => {
		vi.advanceTimersByTime(1000);
	});
	expect(alert.textContent).toBe("Rate limited. Try again in 29s.");
});

test("failed pause with 503 shows the degraded message", async () => {
	getState = { paused: false, until: null };
	postFailure = () =>
		new Response(JSON.stringify({ error: "unavailable" }), {
			status: 503,
			headers: { "content-type": "application/json" },
		});
	renderWidget();

	fireEvent.click(await findPauseTrigger());
	fireEvent.click(screen.getByRole("button", { name: "60 seconds" }));

	const alert = await screen.findByRole("alert");
	expect(alert.textContent).toBe("The server is starting or degraded. Try again shortly.");
});

test("a successful pause clears the previous mutation error", async () => {
	getState = { paused: false, until: null };
	postFailure = () =>
		new Response(JSON.stringify({ error: "unavailable" }), {
			status: 503,
			headers: { "content-type": "application/json" },
		});
	renderWidget();

	fireEvent.click(await findPauseTrigger());
	fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));
	await screen.findByRole("alert");

	postFailure = null;
	postResponse = () => ({ paused: true, until: Math.floor(Date.now() / 1000) + 300 });
	fireEvent.click(screen.getByRole("button", { name: "Pause" }));
	fireEvent.click(screen.getByRole("button", { name: "5 minutes" }));

	await screen.findByRole("button", { name: "Resume" });
	expect(screen.queryByRole("alert")).toBeNull();
});

test("formatRemaining renders m:ss and h:mm:ss and clamps at zero", () => {
	expect(formatRemaining(0)).toBe("0:00");
	expect(formatRemaining(-5)).toBe("0:00");
	expect(formatRemaining(59)).toBe("0:59");
	expect(formatRemaining(90)).toBe("1:30");
	expect(formatRemaining(3661)).toBe("1:01:01");
});
