import type { ReactNode } from "react";
import { act, renderHook, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider, type QueryClient as QC } from "@tanstack/react-query";
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 };
}

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("delete asks for confirmation and only removes when confirmed", async () => {
	const { hook, removed } = setup();
	const confirm = vi.fn(() => false);
	vi.stubGlobal("confirm", confirm);

	act(() => hook.result.current.onDelete({ id: 3, name: "gamma" }));
	expect(confirm).toHaveBeenCalledWith('Delete "gamma"?');
	expect(removed).toEqual([]);

	confirm.mockReturnValue(true);
	act(() => hook.result.current.onDelete({ id: 3, name: "gamma" }));
	await waitFor(() => expect(removed).toEqual([3]));
});

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();
});
