/**
 * Worker-side PBKDF2 deriver — routes derive to PasswordHashDO (30s CPU budget).
 * Falls back to inline pbkdf2Derive on any DO/secret failure.
 */
import { pbkdf2Derive, type DeriveBitsFn } from '@zync/auth'
import type { Env } from '@zync/types'

function toB64(bytes: Uint8Array | ArrayBuffer): string {
  const arr = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes)
  return btoa(String.fromCharCode(...arr))
}

function b64ToArrayBuffer(b64: string): ArrayBuffer {
  const bytes = new Uint8Array(atob(b64).split('').map(c => c.charCodeAt(0)))
  return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
}

export function makeDoDeriveBits(env: Env): DeriveBitsFn {
  return async (plain, salt, iterations, hash, keyBytes) => {
    if (!env.PWHASH_DO || !env.PWHASH_DO_SECRET) {
      return pbkdf2Derive(plain, salt, iterations, hash, keyBytes)
    }

    try {
      const shard = crypto.getRandomValues(new Uint32Array(1))[0]! % 16
      const id = env.PWHASH_DO.idFromName(`pwh-${shard}`)
      const stub = env.PWHASH_DO.get(id)
      const res = await stub.fetch('https://pwhash/derive', {
        method: 'POST',
        headers: {
          'content-type': 'application/json',
          'x-zync-pwhash': env.PWHASH_DO_SECRET,
        },
        body: JSON.stringify({
          plainB64: toB64(new TextEncoder().encode(plain)),
          saltB64: toB64(salt),
          iterations,
          hash,
          keyBytes,
        }),
      })

      if (!res.ok) {
        console.warn(`PasswordHashDO derive failed: HTTP ${res.status}`)
        return pbkdf2Derive(plain, salt, iterations, hash, keyBytes)
      }

      const data = (await res.json()) as { bitsB64?: string }
      if (typeof data.bitsB64 !== 'string') {
        console.warn('PasswordHashDO derive failed: missing bitsB64')
        return pbkdf2Derive(plain, salt, iterations, hash, keyBytes)
      }

      return b64ToArrayBuffer(data.bitsB64)
    } catch {
      console.warn('PasswordHashDO derive failed, falling back to inline PBKDF2')
      return pbkdf2Derive(plain, salt, iterations, hash, keyBytes)
    }
  }
}

export function withDoHash(env: Env): { deriveBits: DeriveBitsFn } {
  return { deriveBits: makeDoDeriveBits(env) }
}
