// WebCryptoKeyStore unit tests under JSDOM. Uses Node 22's WebCrypto
// (Ed25519 has been stable since Node 20) and `fake-indexeddb/auto`
// for storage. The "simulated reload" case closes the database and
// reopens it under the same name to prove the persisted keypair
// still signs after the connection round-trips.

import "fake-indexeddb/auto";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import type { IDBPDatabase } from "idb";
import { WebCryptoKeyStore } from "../src/platform/store/webcrypto-keystore";
import { openGalaxyDB, type GalaxyDB } from "../src/platform/store/idb";

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

beforeEach(async () => {
	dbName = `galaxy-ui-test-${crypto.randomUUID()}`;
	db = await openGalaxyDB(dbName);
});

afterEach(async () => {
	db.close();
	indexedDB.deleteDatabase(dbName);
});

describe("WebCryptoKeyStore", () => {
	test("generate produces a 32-byte raw Ed25519 public key", async () => {
		const ks = new WebCryptoKeyStore(db);
		const keypair = await ks.generate();
		expect(keypair.publicKey).toBeInstanceOf(Uint8Array);
		expect(keypair.publicKey.length).toBe(32);
	});

	test("generate-then-load returns the same public key and a working signer", async () => {
		const ks = new WebCryptoKeyStore(db);
		const fresh = await ks.generate();
		const loaded = await ks.load();
		expect(loaded).not.toBeNull();
		expect(Array.from(loaded!.publicKey)).toEqual(Array.from(fresh.publicKey));

		const canonical = new TextEncoder().encode("canonical-bytes");
		const sigA = await fresh.sign(canonical);
		const sigB = await loaded!.sign(canonical);
		// Ed25519 is deterministic: identical (key, message) ⇒ identical
		// signature bytes. This proves the loaded handle is the same
		// signing key as the freshly generated one without ever
		// touching the private bytes.
		expect(Array.from(sigA)).toEqual(Array.from(sigB));
	});

	test("produced signature verifies under a third-party public key import", async () => {
		const ks = new WebCryptoKeyStore(db);
		const keypair = await ks.generate();
		const canonical = new TextEncoder().encode("verify-me");
		const signature = await keypair.sign(canonical);
		expect(signature.length).toBe(64);

		const verifyKey = await crypto.subtle.importKey(
			"raw",
			keypair.publicKey as BufferSource,
			{ name: "Ed25519" },
			false,
			["verify"],
		);
		const ok = await crypto.subtle.verify(
			{ name: "Ed25519" },
			verifyKey,
			signature as BufferSource,
			canonical as BufferSource,
		);
		expect(ok).toBe(true);
	});

	test("survives a simulated page reload", async () => {
		const ks1 = new WebCryptoKeyStore(db);
		const generated = await ks1.generate();
		const canonical = new TextEncoder().encode("reload-canonical");
		const sigBefore = await generated.sign(canonical);

		db.close();
		db = await openGalaxyDB(dbName);
		const ks2 = new WebCryptoKeyStore(db);
		const reloaded = await ks2.load();
		expect(reloaded).not.toBeNull();
		expect(Array.from(reloaded!.publicKey)).toEqual(
			Array.from(generated.publicKey),
		);
		const sigAfter = await reloaded!.sign(canonical);
		expect(Array.from(sigAfter)).toEqual(Array.from(sigBefore));
	});

	test("clear empties the slot", async () => {
		const ks = new WebCryptoKeyStore(db);
		await ks.generate();
		await ks.clear();
		expect(await ks.load()).toBeNull();
	});

	test("load on a fresh database returns null", async () => {
		const ks = new WebCryptoKeyStore(db);
		expect(await ks.load()).toBeNull();
	});

	test("generate after clear yields a different keypair", async () => {
		const ks = new WebCryptoKeyStore(db);
		const first = await ks.generate();
		await ks.clear();
		const second = await ks.generate();
		expect(Array.from(second.publicKey)).not.toEqual(
			Array.from(first.publicKey),
		);
	});
});
