// Component tests for the create-game form. The lobby API is mocked
// at module level; the GalaxyClient is replaced with a stub that does
// nothing (the test only asserts the createGame wrapper is invoked
// with the right shape).

import "fake-indexeddb/auto";
import { fireEvent, render, waitFor } from "@testing-library/svelte";
import {
	afterEach,
	beforeEach,
	describe,
	expect,
	test,
	vi,
} from "vitest";
import type { IDBPDatabase } from "idb";

import { i18n } from "../src/lib/i18n/index.svelte";
import { session } from "../src/lib/session-store.svelte";
import { type GalaxyDB, openGalaxyDB } from "../src/platform/store/idb";
import { IDBCache } from "../src/platform/store/idb-cache";
import { WebCryptoKeyStore } from "../src/platform/store/webcrypto-keystore";

const gotoSpy = vi.fn<(url: string) => Promise<void>>(async () => {});
vi.mock("$app/navigation", () => ({
	goto: (url: string) => gotoSpy(url),
}));

const createGameSpy = vi.fn();
vi.mock("../src/api/lobby", async () => {
	const actual = await vi.importActual<typeof import("../src/api/lobby")>(
		"../src/api/lobby",
	);
	return {
		...actual,
		createGame: (...args: unknown[]) => createGameSpy(...args),
	};
});

vi.mock("../src/lib/env", () => ({
	GATEWAY_BASE_URL: "http://gateway.test",
	GATEWAY_RESPONSE_PUBLIC_KEY: new Uint8Array(32).fill(0x55),
}));

vi.mock("../src/api/connect", () => ({
	createEdgeGatewayClient: vi.fn(() => ({})),
}));

vi.mock("../src/api/galaxy-client", () => {
	class FakeGalaxyClient {
		executeCommand = vi.fn(async () => ({
			resultCode: "ok",
			payloadBytes: new Uint8Array(),
		}));
	}
	return { GalaxyClient: FakeGalaxyClient };
});

vi.mock("../src/platform/core/index", () => ({
	loadCore: async () => ({
		signRequest: () => new Uint8Array(),
		verifyResponse: () => true,
		verifyEvent: () => true,
		verifyPayloadHash: () => true,
	}),
}));

let db: IDBPDatabase<GalaxyDB>;
let dbName: string;

beforeEach(async () => {
	dbName = `galaxy-ui-test-${crypto.randomUUID()}`;
	db = await openGalaxyDB(dbName);
	const store = {
		keyStore: new WebCryptoKeyStore(db),
		cache: new IDBCache(db),
	};
	session.resetForTests();
	session.setStoreLoaderForTests(async () => store);
	await session.init();
	await session.signIn("device-1");
	i18n.resetForTests("en");
	createGameSpy.mockReset();
	gotoSpy.mockReset();
});

afterEach(async () => {
	session.resetForTests();
	i18n.resetForTests("en");
	db.close();
	await new Promise<void>((resolve) => {
		const req = indexedDB.deleteDatabase(dbName);
		req.onsuccess = () => resolve();
		req.onerror = () => resolve();
		req.onblocked = () => resolve();
	});
});

async function importCreatePage(): Promise<typeof import("../src/routes/lobby/create/+page.svelte")> {
	return import("../src/routes/lobby/create/+page.svelte");
}

describe("lobby/create page", () => {
	test("submitting a valid form invokes createGame with the entered values and navigates back", async () => {
		createGameSpy.mockResolvedValue({
			gameId: "private-new",
			gameName: "First Contact",
			gameType: "private",
			status: "draft",
			ownerUserId: "user-1",
			minPlayers: 2,
			maxPlayers: 8,
			enrollmentEndsAt: new Date(),
			createdAt: new Date(),
			updatedAt: new Date(),
		});

		const Page = (await importCreatePage()).default;
		const ui = render(Page);

		await waitFor(() =>
			expect(ui.getByTestId("lobby-create-form")).toBeInTheDocument(),
		);

		await fireEvent.input(ui.getByTestId("lobby-create-game-name"), {
			target: { value: "First Contact" },
		});
		await fireEvent.input(ui.getByTestId("lobby-create-description"), {
			target: { value: "" },
		});
		await fireEvent.input(ui.getByTestId("lobby-create-turn-schedule"), {
			target: { value: "0 0 * * *" },
		});
		await fireEvent.input(ui.getByTestId("lobby-create-enrollment-ends-at"), {
			target: { value: "2026-06-01T12:00" },
		});

		await fireEvent.click(ui.getByTestId("lobby-create-submit"));

		await waitFor(() => {
			expect(createGameSpy).toHaveBeenCalledTimes(1);
			const call = createGameSpy.mock.calls[0]!;
			const input = call[1] as Record<string, unknown>;
			expect(input.gameName).toBe("First Contact");
			expect(input.turnSchedule).toBe("0 0 * * *");
			expect(input.minPlayers).toBe(2);
			expect(input.maxPlayers).toBe(8);
			expect(input.startGapHours).toBe(24);
			expect(input.startGapPlayers).toBe(2);
			expect(input.targetEngineVersion).toBe("v1");
			expect(input.enrollmentEndsAt).toBeInstanceOf(Date);
			expect(gotoSpy).toHaveBeenCalledWith("/lobby");
		});
	});

	test("submitting with an empty game name surfaces a validation error and does not call the API", async () => {
		const Page = (await importCreatePage()).default;
		const ui = render(Page);

		await waitFor(() =>
			expect(ui.getByTestId("lobby-create-form")).toBeInTheDocument(),
		);

		// turn_schedule starts populated with the default; clear game_name to trigger the error
		await fireEvent.input(ui.getByTestId("lobby-create-game-name"), {
			target: { value: "  " },
		});
		await fireEvent.input(ui.getByTestId("lobby-create-enrollment-ends-at"), {
			target: { value: "2026-06-01T12:00" },
		});
		await fireEvent.click(ui.getByTestId("lobby-create-submit"));

		await waitFor(() => {
			expect(ui.getByTestId("lobby-create-error")).toHaveTextContent(
				"game name must not be empty",
			);
			expect(createGameSpy).not.toHaveBeenCalled();
		});
	});

	test("cancel button navigates back to /lobby without calling the API", async () => {
		const Page = (await importCreatePage()).default;
		const ui = render(Page);

		await waitFor(() =>
			expect(ui.getByTestId("lobby-create-cancel")).toBeInTheDocument(),
		);
		await fireEvent.click(ui.getByTestId("lobby-create-cancel"));

		await waitFor(() => {
			expect(gotoSpy).toHaveBeenCalledWith("/lobby");
			expect(createGameSpy).not.toHaveBeenCalled();
		});
	});
});
