// SessionStore unit tests under JSDOM with `fake-indexeddb` and Node
// 22's WebCrypto. Each case wires a fresh `SessionStore` against a
// per-test IndexedDB name, so persistence behaviour is observable
// across cases without bleed and without touching the production
// `dbConnection()` cache.

import "fake-indexeddb/auto";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import type { IDBPDatabase } from "idb";
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";
import type { Store } from "../src/platform/store/index";
import { SessionStore } from "../src/lib/session-store.svelte";

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

beforeEach(async () => {
	dbName = `galaxy-ui-test-${crypto.randomUUID()}`;
	db = await openGalaxyDB(dbName);
	store = {
		keyStore: new WebCryptoKeyStore(db),
		cache: new IDBCache(db),
	};
});

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

function newSessionStore(): SessionStore {
	const s = new SessionStore();
	s.setStoreLoaderForTests(async () => store);
	return s;
}

describe("SessionStore.init", () => {
	test("settles to anonymous when no device-session id is persisted", async () => {
		const session = newSessionStore();
		await session.init();
		expect(session.status).toBe("anonymous");
		expect(session.deviceSessionId).toBeNull();
		expect(session.keypair).not.toBeNull();
		expect(session.keypair!.publicKey.length).toBe(32);
	});

	test("settles to authenticated when a device-session id is persisted", async () => {
		const first = newSessionStore();
		await first.init();
		await first.signIn("dev-1");
		expect(first.status).toBe("authenticated");

		// Simulate a fresh page load: a new SessionStore against the
		// same IndexedDB picks up the previously persisted session.
		const second = newSessionStore();
		await second.init();
		expect(second.status).toBe("authenticated");
		expect(second.deviceSessionId).toBe("dev-1");
		expect(second.keypair).not.toBeNull();
	});

	test("flips status to unsupported when WebCrypto Ed25519 is missing", async () => {
		const session = newSessionStore();
		session.setSupportProbeForTests(async () => false);
		await session.init();
		expect(session.status).toBe("unsupported");
		expect(session.keypair).toBeNull();
	});

	test("init is idempotent", async () => {
		const session = newSessionStore();
		await Promise.all([session.init(), session.init(), session.init()]);
		expect(session.status).toBe("anonymous");
	});
});

describe("SessionStore.signIn / signOut", () => {
	test("signIn persists the device-session id and updates status", async () => {
		const session = newSessionStore();
		await session.init();
		await session.signIn("dev-2");
		expect(session.deviceSessionId).toBe("dev-2");
		expect(session.status).toBe("authenticated");

		const reload = newSessionStore();
		await reload.init();
		expect(reload.deviceSessionId).toBe("dev-2");
	});

	test("signOut('user') wipes id, regenerates keypair, returns to anonymous", async () => {
		const session = newSessionStore();
		await session.init();
		const firstPublicKey = Array.from(session.keypair!.publicKey);
		await session.signIn("dev-3");
		await session.signOut("user");
		expect(session.deviceSessionId).toBeNull();
		expect(session.status).toBe("anonymous");
		expect(session.keypair).not.toBeNull();
		const secondPublicKey = Array.from(session.keypair!.publicKey);
		expect(secondPublicKey).not.toEqual(firstPublicKey);
	});

	test("signOut('revoked') has the same observable post-state as 'user'", async () => {
		const session = newSessionStore();
		await session.init();
		await session.signIn("dev-4");
		await session.signOut("revoked");
		expect(session.status).toBe("anonymous");
		expect(session.deviceSessionId).toBeNull();
	});

	test("signIn before init throws", async () => {
		const local = new SessionStore();
		await expect(local.signIn("x")).rejects.toThrow(/before init/);
	});

	test("signOut before init throws", async () => {
		const local = new SessionStore();
		await expect(local.signOut("user")).rejects.toThrow(/before init/);
	});
});
