import type { ReactNode } from "react";
import { act, fireEvent, render, renderHook, screen, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider, type QueryClient as QC } from "@tanstack/react-query";
import ConfirmDialog from "./ConfirmDialog";
import { useCrudForm } from "./useCrudForm";

interface Row {
	id: number;
	name: string;
}

function setup() {
	const created: string[] = [];
	const updated: { id: number; input: string }[] = [];
	const removed: number[] = [];
	const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: false } } });
	const wrapper = ({ children }: { children: ReactNode }) => (
		<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
	);
	const hook = renderHook(
		() =>
			useCrudForm<Row, string>({
				create: (_qc: QC) => ({
					mutationFn: async (input: string) => {
						created.push(input);
					},
				}),
				update: (_qc: QC) => ({
					mutationFn: async (vars: { id: number; input: string }) => {
						updated.push(vars);
					},
				}),
				remove: (_qc: QC) => ({
					mutationFn: async (id: number) => {
						removed.push(id);
					},
				}),
				confirmDelete: (row) => `Delete "${row.name}"?`,
			}),
		{ wrapper },
	);
	return { hook, created, updated, removed };
}

/**
 * The delete path is only finished when a real dialog drives it, so this
 * harness wires the hook's pending-delete state to the component operators
 * actually see (ruling 5). `window.confirm` no longer exists to stub.
 */
function DeleteHarness({ onRemoved }: { onRemoved: (id: number) => void }) {
	const { onDelete, pendingDelete, confirmPendingDelete, cancelPendingDelete } = useCrudForm<Row, string>({
		create: (_qc: QC) => ({ mutationFn: async () => undefined }),
		update: (_qc: QC) => ({ mutationFn: async () => undefined }),
		remove: (_qc: QC) => ({
			mutationFn: async (id: number) => {
				onRemoved(id);
			},
		}),
		confirmDelete: (row) => `Delete "${row.name}"?`,
	});
	return (
		<>
			<button type="button" onClick={() => onDelete({ id: 3, name: "gamma" })}>
				Delete gamma
			</button>
			<ConfirmDialog
				isOpen={pendingDelete !== null}
				title="Delete row"
				message={pendingDelete?.message ?? ""}
				confirmLabel="Delete"
				onConfirm={confirmPendingDelete}
				onCancel={cancelPendingDelete}
			/>
		</>
	);
}

function renderDeleteHarness() {
	const removed: number[] = [];
	const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: false } } });
	render(
		<QueryClientProvider client={queryClient}>
			<DeleteHarness onRemoved={(id) => removed.push(id)} />
		</QueryClientProvider>,
	);
	return removed;
}

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

test("the create form submits through the create mutation and closes", async () => {
	const { hook, created, updated } = setup();
	act(() => hook.result.current.openForm({ mode: "create" }));
	expect(hook.result.current.form).toEqual({ mode: "create" });

	act(() => hook.result.current.onSubmit("alpha"));
	await waitFor(() => expect(hook.result.current.form).toBeNull());
	expect(created).toEqual(["alpha"]);
	expect(updated).toEqual([]);
});

test("the edit form submits the row id through the update mutation", async () => {
	const { hook, created, updated } = setup();
	act(() => hook.result.current.openForm({ mode: "edit", entity: { id: 7, name: "beta" } }));

	act(() => hook.result.current.onSubmit("beta2"));
	await waitFor(() => expect(hook.result.current.form).toBeNull());
	expect(updated).toEqual([{ id: 7, input: "beta2" }]);
	expect(created).toEqual([]);
});

test("submitting with no form open does nothing", () => {
	const { hook, created, updated } = setup();
	act(() => hook.result.current.onSubmit("ignored"));
	expect(created).toEqual([]);
	expect(updated).toEqual([]);
});

test("openForm clears a stale create error so the reopened form starts clean", async () => {
	const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: false } } });
	const wrapper = ({ children }: { children: ReactNode }) => (
		<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
	);
	const hook = renderHook(
		() =>
			useCrudForm<Row, string>({
				create: (_qc: QC) => ({ mutationFn: () => Promise.reject(new Error("boom")) }),
				update: (_qc: QC) => ({ mutationFn: async () => undefined }),
				remove: (_qc: QC) => ({ mutationFn: async () => undefined }),
				confirmDelete: () => "?",
			}),
		{ wrapper },
	);

	act(() => hook.result.current.openForm({ mode: "create" }));
	act(() => hook.result.current.onSubmit("alpha"));
	await waitFor(() => expect(hook.result.current.create.error).not.toBeNull());
	expect(hook.result.current.form).toEqual({ mode: "create" });

	act(() => hook.result.current.openForm({ mode: "create" }));
	await waitFor(() => expect(hook.result.current.create.error).toBeNull());
});

test("onDelete opens the confirmation instead of removing the row", () => {
	const { hook, removed } = setup();
	act(() => hook.result.current.onDelete({ id: 3, name: "gamma" }));

	expect(hook.result.current.pendingDelete).toEqual({ entity: { id: 3, name: "gamma" }, message: 'Delete "gamma"?' });
	expect(removed).toEqual([]);
});

test("cancelling the confirmation dialog leaves the row alone", async () => {
	const removed = renderDeleteHarness();
	fireEvent.click(screen.getByRole("button", { name: "Delete gamma" }));

	const dialog = await screen.findByRole("alertdialog");
	expect(dialog.textContent).toContain('Delete "gamma"?');

	fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
	await waitFor(() => expect(screen.queryByRole("alertdialog")).toBeNull());
	expect(removed).toEqual([]);
});

test("confirming the dialog removes the row by id", async () => {
	const removed = renderDeleteHarness();
	fireEvent.click(screen.getByRole("button", { name: "Delete gamma" }));
	await screen.findByRole("alertdialog");

	fireEvent.click(screen.getByRole("button", { name: "Delete" }));
	await waitFor(() => expect(removed).toEqual([3]));
	expect(screen.queryByRole("alertdialog")).toBeNull();
});

test("closeForm closes whichever form is open", () => {
	const { hook } = setup();
	act(() => hook.result.current.openForm({ mode: "edit", entity: { id: 1, name: "delta" } }));
	act(() => hook.result.current.closeForm());
	expect(hook.result.current.form).toBeNull();
});
