import { describe, expect, it, vi } from 'vitest'
import { encodeBase64UrlBytes } from 'relay-contracts'
import {
  base64UrlToBuffer,
  bufferToBase64Url,
  createWebAuthnClient,
  WebAuthnError,
} from '../src/webauthn'

function ab(bytes: number[]): ArrayBuffer {
  return new Uint8Array(bytes).buffer
}

const assertionCred = {
  id: 'cred-1',
  type: 'public-key',
  rawId: ab([1, 2, 3]),
  response: {
    clientDataJSON: ab([4, 5]),
    authenticatorData: ab([6]),
    signature: ab([7, 8]),
    userHandle: null,
  },
}

describe('webauthn wrapper (T7)', () => {
  it('base64url ↔ ArrayBuffer round-trip is lossless', () => {
    const bytes = [0, 1, 250, 255, 128, 64]
    const b64u = bufferToBase64Url(ab(bytes))
    expect(new Uint8Array(base64UrlToBuffer(b64u))).toEqual(new Uint8Array(bytes))
  })

  it('authenticate maps the assertion to base64url fields', async () => {
    const container = {
      get: vi.fn(async () => assertionCred),
      create: vi.fn(),
    } as unknown as CredentialsContainer
    const wa = createWebAuthnClient(container)
    const res = await wa.authenticate({ challenge: ab([9]) } as PublicKeyCredentialRequestOptions)
    expect(res.rawId).toBe(encodeBase64UrlBytes(new Uint8Array([1, 2, 3])))
    expect(res.response.signature).toBe(encodeBase64UrlBytes(new Uint8Array([7, 8])))
    expect(res.response.userHandle).toBeNull()
  })

  it('a user-cancelled prompt (NotAllowedError) maps to WebAuthnError("cancelled")', async () => {
    const container = {
      get: vi.fn(async () => {
        throw Object.assign(new Error('dismissed'), { name: 'NotAllowedError' })
      }),
      create: vi.fn(),
    } as unknown as CredentialsContainer
    const wa = createWebAuthnClient(container)
    await expect(
      wa.authenticate({ challenge: ab([1]) } as PublicKeyCredentialRequestOptions),
    ).rejects.toMatchObject({ name: 'WebAuthnError', kind: 'cancelled' })
  })

  it('throws WebAuthnError("unsupported") when no credentials container is available', () => {
    expect(() => createWebAuthnClient(undefined)).toThrow(WebAuthnError)
  })

  it('does not leak credential material to console', async () => {
    const spy = vi.spyOn(console, 'log').mockImplementation(() => {})
    const container = {
      get: vi.fn(async () => assertionCred),
      create: vi.fn(),
    } as unknown as CredentialsContainer
    await createWebAuthnClient(container).authenticate({
      challenge: ab([1]),
    } as PublicKeyCredentialRequestOptions)
    expect(spy).not.toHaveBeenCalled()
    spy.mockRestore()
  })
})
