// Verifies the Phase 6 storage layer end-to-end in real browser
// engines: a freshly generated device keypair persists across a page
// reload, signs deterministically with the same private key after the
// reload, and is wiped by `clearDeviceSession` so the next load
// generates a different keypair. The live-gateway round-trip is
// covered by Phase 7's e2e once the email-code login flow lands;
// this spec deliberately stops at the storage boundary.

import { expect, test, type Page } from "@playwright/test";

interface DebugSnapshot {
	publicKey: number[];
	deviceSessionId: string | null;
}

import type {
	MapCameraSnapshot,
	MapPickStateSnapshot,
	MapPrimitiveSnapshot,
} from "../../src/lib/debug-surface.svelte";

// Mirrors the surface mounted by `routes/__debug/store/+page.svelte`.
// Other Playwright specs (`game-shell.spec.ts`, `order-composer.spec.ts`,
// `cargo-routes.spec.ts`) reuse the global declaration below, so this
// interface lists every helper any spec calls — not only those
// exercised by this file.
interface DebugSurface {
	ready: true;
	loadSession(): Promise<DebugSnapshot>;
	clearSession(): Promise<void>;
	signWithDevice(message: number[]): Promise<number[]>;
	setDeviceSessionId(id: string): Promise<void>;
	verifyWithStoredPublicKey(
		message: number[],
		signature: number[],
	): Promise<boolean>;
	seedOrderDraft(
		gameId: string,
		commands: ReadonlyArray<{
			kind: "placeholder";
			id: string;
			label: string;
		}>,
	): Promise<void>;
	clearOrderDraft(gameId: string): Promise<void>;
	getMapPrimitives(): readonly MapPrimitiveSnapshot[];
	getMapPickState(): MapPickStateSnapshot;
	getMapCamera(): MapCameraSnapshot | null;
}

declare global {
	interface Window {
		__galaxyDebug?: DebugSurface;
	}
}

const CANONICAL = Array.from(new TextEncoder().encode("phase-6-canonical"));

async function bootDebugPage(page: Page): Promise<void> {
	await page.goto("/__debug/store");
	await expect(page.getByTestId("debug-store-ready")).toBeVisible();
	await page.waitForFunction(() => window.__galaxyDebug?.ready === true);
}

test("device keypair survives reload and produces verifiable signatures", async ({
	page,
}) => {
	await bootDebugPage(page);
	// Wipe any leftover state from a previous Playwright run that
	// shared the same browser profile.
	await page.evaluate(() => window.__galaxyDebug!.clearSession());

	const first = await page.evaluate(async (canonical) => {
		const sess = await window.__galaxyDebug!.loadSession();
		const signature = await window.__galaxyDebug!.signWithDevice(canonical);
		return { publicKey: sess.publicKey, signature };
	}, CANONICAL);
	expect(first.publicKey.length).toBe(32);
	expect(first.signature.length).toBe(64);

	await page.reload();
	await expect(page.getByTestId("debug-store-ready")).toBeVisible();
	await page.waitForFunction(() => window.__galaxyDebug?.ready === true);

	const second = await page.evaluate(
		async ({ canonical, firstSig }) => {
			const sess = await window.__galaxyDebug!.loadSession();
			const fresh = await window.__galaxyDebug!.signWithDevice(canonical);
			// Signatures produced before the reload must verify under the
			// post-reload public key. A pre-reload signature only verifies
			// when the persisted private key is identical to the original.
			const prevVerifies =
				await window.__galaxyDebug!.verifyWithStoredPublicKey(
					canonical,
					firstSig,
				);
			const freshVerifies =
				await window.__galaxyDebug!.verifyWithStoredPublicKey(
					canonical,
					fresh,
				);
			return {
				publicKey: sess.publicKey,
				signature: fresh,
				prevVerifies,
				freshVerifies,
			};
		},
		{ canonical: CANONICAL, firstSig: first.signature },
	);
	expect(second.publicKey).toEqual(first.publicKey);
	expect(second.prevVerifies).toBe(true);
	expect(second.freshVerifies).toBe(true);
});

test("clearDeviceSession forces a fresh keypair on next load", async ({
	page,
}) => {
	await bootDebugPage(page);
	await page.evaluate(() => window.__galaxyDebug!.clearSession());
	const before = await page.evaluate(async () => {
		const sess = await window.__galaxyDebug!.loadSession();
		return sess.publicKey;
	});

	await page.evaluate(() => window.__galaxyDebug!.clearSession());
	const after = await page.evaluate(async () => {
		const sess = await window.__galaxyDebug!.loadSession();
		return sess.publicKey;
	});

	expect(after).not.toEqual(before);
});

test("setDeviceSessionId is observable through loadDeviceSession", async ({
	page,
}) => {
	await bootDebugPage(page);
	await page.evaluate(() => window.__galaxyDebug!.clearSession());
	const before = await page.evaluate(async () => {
		const sess = await window.__galaxyDebug!.loadSession();
		return sess.deviceSessionId;
	});
	expect(before).toBeNull();

	await page.evaluate(() => window.__galaxyDebug!.setDeviceSessionId("dev-1"));
	const after = await page.evaluate(async () => {
		const sess = await window.__galaxyDebug!.loadSession();
		return sess.deviceSessionId;
	});
	expect(after).toBe("dev-1");
});
