// `randomKey` regression tests. Pins three load-bearing properties:
//   1. Output is alphanumeric (62-char alphabet) — Idempotency-Key header
//      values must be ASCII-printable per RFC 9110 §5.5; non-printable
//      bytes from a raw CSPRNG would surface as transport errors.
//   2. Output length matches the requested length — the api-context call
//      site asks for 40, and a length mismatch would silently shorten /
//      lengthen the dedup-key surface area.
//   3. Output draws from `Crypto.getRandomBytesAsync` (the platform CSPRNG)
//      and not `Math.random` — the migration from the original loop to the
//      CSPRNG path is the whole reason this file exists.

const mockGetRandomBytesAsync = jest.fn(async (n: number) => {
  // Deterministic counter so the test asserts shape, not entropy.
  const out = new Uint8Array(n)
  for (let i = 0; i < n; i++) out[i] = i
  return out
})

jest.mock('expo-crypto', () => ({
  getRandomBytesAsync: (n: number) => mockGetRandomBytesAsync(n),
}))

type RandomKeyModule = typeof import('../src/lib/randomKey')

let randomKey: RandomKeyModule['randomKey']

beforeAll(() => {
  randomKey = (require('../src/lib/randomKey') as RandomKeyModule).randomKey
})

beforeEach(() => {
  mockGetRandomBytesAsync.mockClear()
})

const ALNUM = /^[A-Za-z0-9]+$/

describe('randomKey', () => {
  it('returns a string of the requested length', async () => {
    const k = await randomKey(40)
    expect(k.length).toBe(40)
  })

  it('returns an alphanumeric (ASCII-printable) string', async () => {
    const k = await randomKey(40)
    expect(ALNUM.test(k)).toBe(true)
  })

  it('sources entropy from expo-crypto getRandomBytesAsync', async () => {
    await randomKey(40)
    expect(mockGetRandomBytesAsync).toHaveBeenCalledTimes(1)
    expect(mockGetRandomBytesAsync).toHaveBeenCalledWith(40)
  })

  it('handles short and zero lengths without throwing', async () => {
    expect((await randomKey(0)).length).toBe(0)
    expect((await randomKey(1)).length).toBe(1)
  })

  it('produces different values across calls when entropy varies', async () => {
    let counter = 0
    mockGetRandomBytesAsync.mockImplementation(async (n: number) => {
      const out = new Uint8Array(n)
      for (let i = 0; i < n; i++) out[i] = (counter + i) % 256
      counter += n
      return out
    })
    const a = await randomKey(40)
    const b = await randomKey(40)
    expect(a).not.toBe(b)
  })
})
