/**
 * PasswordHashDO — PBKDF2 derive offload (foundation-auth-rbac).
 *
 * SQLite-backed DO for the 30s CPU budget. Pure compute: stores nothing,
 * logs no plaintext. Secret-gated via x-zync-pwhash + PWHASH_DO_SECRET.
 */
import { DurableObject } from 'cloudflare:workers'
import { timingSafeEqual } from '@zync/auth'
import type { Env } from '@zync/types'

import {
  decodeBase64Bytes,
  encodeBase64Bytes,
  type PasswordHashDeriveRequest,
  type PasswordHashHashRequest,
  type PasswordHashVerifyRequest,
} from '../do/password-hash'
import { hashPlatformPassword, verifyPlatformPasswordHash } from '../integrations/platform/auth-hash'

const ALLOWED_HASH = new Set(['SHA-512', 'SHA-256'])
const MAX_ITERATIONS = 1_000_000

async function pbkdf2Derive(
  plain: string,
  salt: Uint8Array,
  iterations: number,
  hash: string,
  keyBytes: number,
): Promise<ArrayBuffer> {
  const keyMaterial = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(plain),
    'PBKDF2',
    false,
    ['deriveBits'],
  )
  return crypto.subtle.deriveBits(
    { name: 'PBKDF2', salt: salt as BufferSource, iterations, hash },
    keyMaterial,
    keyBytes * 8,
  )
}

export class PasswordHashDO extends DurableObject<Env> {
  override async fetch(req: Request): Promise<Response> {
    const secret = this.env.PWHASH_DO_SECRET
    const header = req.headers.get('x-zync-pwhash')
    if (!header || !secret || !timingSafeEqual(header, secret)) {
      return new Response(null, { status: 403 })
    }

    const url = new URL(req.url)
    if (req.method !== 'POST') {
      return new Response(null, { status: 404 })
    }

    let body: Record<string, unknown>
    try {
      body = await req.json()
    } catch {
      return new Response(null, { status: 400 })
    }

    if (url.pathname === '/derive') {
      return this.handleDerive(body)
    }

    if (url.pathname === '/hash') {
      return this.handleHash(body)
    }

    if (url.pathname === '/verify') {
      return this.handleVerify(body)
    }

    return new Response(null, { status: 404 })
  }

  private async handleDerive(body: Record<string, unknown>): Promise<Response> {
    const { plainB64, saltB64, iterations, hash, keyBytes } = body as PasswordHashDeriveRequest

    if (
      typeof plainB64 !== 'string' ||
      typeof saltB64 !== 'string' ||
      !Number.isInteger(iterations) ||
      (iterations as number) <= 0 ||
      (iterations as number) > MAX_ITERATIONS ||
      typeof hash !== 'string' ||
      !ALLOWED_HASH.has(hash) ||
      (keyBytes !== 32 && keyBytes !== 64)
    ) {
      return new Response(null, { status: 400 })
    }

    const salt = decodeBase64Bytes(saltB64)
    const plainBytes = decodeBase64Bytes(plainB64)
    if (!salt || !plainBytes) {
      return new Response(null, { status: 400 })
    }

    const plain = new TextDecoder().decode(plainBytes)
    const bits = await pbkdf2Derive(plain, salt, iterations as number, hash, keyBytes as number)
    return Response.json({ bitsB64: encodeBase64Bytes(bits) })
  }

  private async handleHash(body: Record<string, unknown>): Promise<Response> {
    const { plainB64 } = body as PasswordHashHashRequest
    if (typeof plainB64 !== 'string') {
      return new Response(null, { status: 400 })
    }

    const plainBytes = decodeBase64Bytes(plainB64)
    if (!plainBytes) {
      return new Response(null, { status: 400 })
    }

    const storedHash = await hashPlatformPassword(new TextDecoder().decode(plainBytes), {
      deriveBits: pbkdf2Derive,
    })
    return Response.json({ storedHash })
  }

  private async handleVerify(body: Record<string, unknown>): Promise<Response> {
    const { plainB64, storedHash } = body as PasswordHashVerifyRequest
    if (typeof plainB64 !== 'string' || typeof storedHash !== 'string') {
      return new Response(null, { status: 400 })
    }

    const plainBytes = decodeBase64Bytes(plainB64)
    if (!plainBytes) {
      return new Response(null, { status: 400 })
    }

    const result = await verifyPlatformPasswordHash(
      new TextDecoder().decode(plainBytes),
      storedHash,
      { deriveBits: pbkdf2Derive },
    )
    return Response.json(result)
  }
}
