import type {HonoRequest} from 'hono'
import {HTTPException} from 'hono/http-exception'

import {blobStore} from './blob-store'
import {InvalidImageError, type ProcessedImage} from './image'
import {readStreamCapped, StreamOverflowError} from './stream'

// shared flow for image uploads (avatar, crumb image): capped body read,
// re-encode with a 400 on invalid input, store the result, and best-effort
// cleanup of the object it replaced

// read an inbound body capped at max, rejecting oversize uploads even when the
// client omits or understates content-length
export const readUploadBody = async (
  req: HonoRequest,
  max: number,
): Promise<Uint8Array> => {
  const declared = Number(req.header('content-length') ?? 0)
  if (declared > max)
    throw new HTTPException(413, {message: 'too_large'})
  try {
    return await readStreamCapped(
      req.raw.body?.getReader(),
      max,
      'reject',
    )
  } catch (err) {
    if (err instanceof StreamOverflowError)
      throw new HTTPException(413, {message: 'too_large'})
    throw err
  }
}

// read, re-encode, and store an uploaded image, returning its blob ref
export const storeUploadedImage = async (
  req: HonoRequest,
  {
    max,
    process,
    key,
  }: {
    max: number
    process: (bytes: Uint8Array) => Promise<ProcessedImage>
    key: (hash: string) => string
  },
): Promise<string> => {
  const body = await readUploadBody(req, max)
  let processed: ProcessedImage
  try {
    processed = await process(body)
  } catch (err) {
    if (err instanceof InvalidImageError)
      throw new HTTPException(400, {message: err.message})
    throw err
  }
  const ref = key(processed.hash)
  await blobStore.put(ref, processed.bytes, processed.contentType)
  return ref
}

// best-effort cleanup of a replaced/removed image object; never fail the request
export const removeReplacedImage = async (
  previousKey: string | null,
  newKey: string | null,
) => {
  if (!previousKey || previousKey === newKey) return
  try {
    await blobStore.delete(previousKey)
  } catch (err) {
    console.warn('failed to delete previous image object', {
      previousKey,
      err,
    })
  }
}
