/**
 * WebAuthn/Passkey Utilities
 *
 * Shared passkey functionality for CounterMeasure applications.
 * Handles WebAuthn registration and authentication flows.
 */

export interface WebAuthnRegistrationOptions {
  publicKey: PublicKeyCredentialCreationOptions
}

export interface WebAuthnAuthenticationOptions {
  publicKey: PublicKeyCredentialRequestOptions
}

export interface RegistrationCredential {
  id: string
  rawId: string
  type: string
  response: {
    clientDataJSON: string
    attestationObject: string
  }
}

export interface AuthenticationCredential {
  id: string
  rawId: string
  type: string
  response: {
    clientDataJSON: string
    authenticatorData: string
    signature: string
    userHandle?: string | undefined
  }
}

/**
 * Convert a base64url string to Uint8Array.
 */
export function base64urlToUint8Array(base64url: string): Uint8Array {
  const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/')
  const padLen = (4 - (base64.length % 4)) % 4
  const padded = base64 + '='.repeat(padLen)
  const binary = atob(padded)
  const bytes = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i)
  }
  return bytes
}

/**
 * Convert a Uint8Array to base64url string.
 */
export function uint8ArrayToBase64url(array: Uint8Array): string {
  const binary = String.fromCharCode(...array)
  const base64 = btoa(binary)
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
}

/**
 * Convert ArrayBuffer to base64url string.
 */
export function arrayBufferToBase64url(buffer: ArrayBuffer): string {
  return uint8ArrayToBase64url(new Uint8Array(buffer))
}

/**
 * Check if WebAuthn is supported in the current browser.
 */
export function isWebAuthnSupported(): boolean {
  if (typeof window === 'undefined') {
    return false
  }
  return (
    typeof window.PublicKeyCredential !== 'undefined' &&
    typeof window.PublicKeyCredential === 'function'
  )
}

/**
 * Check if platform authenticator (biometrics, Windows Hello, etc.) is available.
 */
export async function isPlatformAuthenticatorAvailable(): Promise<boolean> {
  if (!isWebAuthnSupported()) {
    return false
  }
  try {
    return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  } catch {
    return false
  }
}

/**
 * Prepare registration options from the server response.
 */
export function prepareRegistrationOptions(
  options: WebAuthnRegistrationOptions
): PublicKeyCredentialCreationOptions {
  const publicKey = options.publicKey

  const result: PublicKeyCredentialCreationOptions = {
    ...publicKey,
    challenge: base64urlToUint8Array(publicKey.challenge as unknown as string)
      .buffer as ArrayBuffer,
    user: {
      ...publicKey.user,
      id: base64urlToUint8Array(publicKey.user.id as unknown as string).buffer as ArrayBuffer,
    },
  }

  if (publicKey.excludeCredentials) {
    result.excludeCredentials = publicKey.excludeCredentials.map(cred => ({
      ...cred,
      id: base64urlToUint8Array(cred.id as unknown as string).buffer as ArrayBuffer,
    }))
  }

  return result
}

/**
 * Prepare authentication options from the server response.
 */
export function prepareAuthenticationOptions(
  options: WebAuthnAuthenticationOptions
): PublicKeyCredentialRequestOptions {
  const publicKey = options.publicKey

  const result: PublicKeyCredentialRequestOptions = {
    ...publicKey,
    challenge: base64urlToUint8Array(publicKey.challenge as unknown as string)
      .buffer as ArrayBuffer,
  }

  if (publicKey.allowCredentials) {
    result.allowCredentials = publicKey.allowCredentials.map(cred => ({
      ...cred,
      id: base64urlToUint8Array(cred.id as unknown as string).buffer as ArrayBuffer,
    }))
  }

  return result
}

/**
 * Format registration credential for sending to the server.
 */
export function formatRegistrationCredential(
  credential: PublicKeyCredential
): RegistrationCredential {
  const response = credential.response as AuthenticatorAttestationResponse

  return {
    id: credential.id,
    rawId: arrayBufferToBase64url(credential.rawId),
    type: credential.type,
    response: {
      clientDataJSON: arrayBufferToBase64url(response.clientDataJSON),
      attestationObject: arrayBufferToBase64url(response.attestationObject),
    },
  }
}

/**
 * Format authentication credential for sending to the server.
 */
export function formatAuthenticationCredential(
  credential: PublicKeyCredential
): AuthenticationCredential {
  const response = credential.response as AuthenticatorAssertionResponse

  return {
    id: credential.id,
    rawId: arrayBufferToBase64url(credential.rawId),
    type: credential.type,
    response: {
      clientDataJSON: arrayBufferToBase64url(response.clientDataJSON),
      authenticatorData: arrayBufferToBase64url(response.authenticatorData),
      signature: arrayBufferToBase64url(response.signature),
      userHandle: response.userHandle ? arrayBufferToBase64url(response.userHandle) : undefined,
    },
  }
}

/**
 * Register a new passkey.
 */
export async function registerPasskey(
  options: WebAuthnRegistrationOptions
): Promise<RegistrationCredential> {
  if (!isWebAuthnSupported()) {
    throw new Error('WebAuthn is not supported in this browser')
  }

  const preparedOptions = prepareRegistrationOptions(options)
  const credential = (await navigator.credentials.create({
    publicKey: preparedOptions,
  })) as PublicKeyCredential | null

  if (!credential) {
    throw new Error('Failed to create credential')
  }

  return formatRegistrationCredential(credential)
}

/**
 * Authenticate with a passkey.
 */
export async function authenticateWithPasskey(
  options: WebAuthnAuthenticationOptions
): Promise<AuthenticationCredential> {
  if (!isWebAuthnSupported()) {
    throw new Error('WebAuthn is not supported in this browser')
  }

  const preparedOptions = prepareAuthenticationOptions(options)
  const credential = (await navigator.credentials.get({
    publicKey: preparedOptions,
  })) as PublicKeyCredential | null

  if (!credential) {
    throw new Error('Failed to get credential')
  }

  return formatAuthenticationCredential(credential)
}
