import { describe, it, expect, afterEach, vi } from 'vitest';
import { generateId } from './id';

const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

describe('generateId', () => {
	afterEach(() => {
		vi.unstubAllGlobals();
	});

	it('U-01: returns a UUID v4-shaped string when crypto.randomUUID is available', () => {
		const id = generateId();
		expect(id).toMatch(UUID_V4);
	});

	it('U-02: falls back when crypto.randomUUID is undefined (insecure-context case)', () => {
		// Simulate an HTTP LAN origin: window.crypto exists but randomUUID is not exposed.
		const baseCrypto = globalThis.crypto;
		vi.stubGlobal('crypto', {
			getRandomValues: baseCrypto.getRandomValues.bind(baseCrypto)
			// no randomUUID
		});

		const id = generateId();
		expect(id).toMatch(UUID_V4);
	});

	it('U-03: every call returns a unique value (uniqueness under fallback)', () => {
		const baseCrypto = globalThis.crypto;
		vi.stubGlobal('crypto', {
			getRandomValues: baseCrypto.getRandomValues.bind(baseCrypto)
		});

		const ids = new Set(Array.from({ length: 50 }, generateId));
		expect(ids.size).toBe(50);
	});

	it('U-04: prefers crypto.randomUUID when present (does not drop into fallback)', () => {
		const spy = vi.fn(() => '11111111-1111-4111-8111-111111111111');
		vi.stubGlobal('crypto', {
			randomUUID: spy,
			getRandomValues: globalThis.crypto.getRandomValues.bind(globalThis.crypto)
		});

		const id = generateId();
		expect(spy).toHaveBeenCalledTimes(1);
		expect(id).toBe('11111111-1111-4111-8111-111111111111');
	});
});
