/**
 * Password hashing — foundation-auth-rbac.
 *
 * Primary format comes from `@platform-modules/util/password`:
 * `pbkdf2sha512$<iter>$<saltB64>$<hashB64>`.
 *
 * Legacy SHA-256 hashes (`pbkdf2$<iter>$…`) still verify in place and are
 * marked for rehash on the next successful password flow.
 */
import {
  timingSafeEqual,
} from '@platform-modules/util/crypto'
import {
  hashPassword as platformHashPassword,
  verifyPassword as platformVerifyPassword,
  type DeriveBitsFn,
} from '@platform-modules/util/password'
import { asBufferSource } from './crypto'

const LEGACY_HASH = 'SHA-256'
const LEGACY_KEY_BYTES = 32

export type { DeriveBitsFn }

export type PasswordVerificationResult = {
  ok: boolean
  needsRehash: boolean
  rehashHash?: string
}

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

function fromB64(b64: string): Uint8Array {
  return new Uint8Array(
    atob(b64).split('').map(c => c.charCodeAt(0)),
  )
}

export 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: asBufferSource(salt), iterations, hash },
    keyMaterial,
    keyBytes * 8,
  )
}

export function isLegacyPasswordHash(stored: string): boolean {
  return stored.startsWith('pbkdf2$')
}

async function verifyLegacyPassword(
  plain: string,
  stored: string,
  derive: DeriveBitsFn,
): Promise<boolean> {
  const parts = stored.split('$')
  if (parts.length !== 4) return false

  const [, iterStr, saltB64, hashB64] = parts
  const iterations = Number(iterStr)
  if (!Number.isInteger(iterations) || iterations <= 0 || iterations > 100_000) return false

  let salt: Uint8Array
  try {
    salt = fromB64(saltB64!)
  } catch {
    return false
  }

  const bits = await derive(plain, salt, iterations, LEGACY_HASH, LEGACY_KEY_BYTES)
  return timingSafeEqual(toB64(bits), hashB64!)
}

export async function hashPassword(
  plain: string,
  opts?: { deriveBits?: DeriveBitsFn },
): Promise<string> {
  return platformHashPassword(plain, opts)
}

export async function verifyPasswordDetailed(
  plain: string,
  stored: string,
  opts?: { deriveBits?: DeriveBitsFn },
): Promise<PasswordVerificationResult> {
  const derive = opts?.deriveBits ?? pbkdf2Derive

  if (isLegacyPasswordHash(stored)) {
    const ok = await verifyLegacyPassword(plain, stored, derive)
    if (!ok) return { ok: false, needsRehash: false }

    return {
      ok: true,
      needsRehash: true,
      rehashHash: await platformHashPassword(plain, opts),
    }
  }

  const ok = await platformVerifyPassword(plain, stored, opts)
  return { ok, needsRehash: false }
}

export async function verifyPassword(
  plain: string,
  stored: string,
  opts?: { deriveBits?: DeriveBitsFn },
): Promise<boolean> {
  const result = await verifyPasswordDetailed(plain, stored, opts)
  return result.ok
}
