/**
 * Signed file proxy — expenses-module.
 *
 * GET /api/files/:token
 *
 * Serves an R2 object addressed by an HS256-signed token minted by
 * GET /api/expenses/:id/file. The token is a JWT carrying { key, tid } with a
 * 30-min exp. This route:
 *   1. Verifies the signature + exp under FILE_SIGNING_KEY (jose throws on tamper/expiry).
 *   2. Requires an authenticated session whose tenant matches the token's tid —
 *      so a leaked token cannot be replayed cross-tenant.
 *   3. Streams the R2 object (the key is never trusted from the client directly).
 */
import { Hono } from 'hono'
import { verifySignedToken } from '@zync/auth'
import type { AppEnv } from '../../types'
import { authMiddleware } from '../../middleware/auth'

export const filesRoutes = new Hono<AppEnv>()

filesRoutes.use('*', authMiddleware)

filesRoutes.get('/:token', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  let payload: Record<string, unknown>
  try {
    payload = await verifySignedToken(c.req.param('token'), c.env.FILE_SIGNING_KEY)
  } catch {
    // Invalid signature, malformed, or expired
    return c.json({ error: 'Invalid or expired token' }, 403)
  }
  const key = payload['key']
  const tid = payload['tid']
  if (typeof key !== 'string' || typeof tid !== 'string') {
    return c.json({ error: 'Invalid token' }, 403)
  }
  // Bind the token to the bearer's tenant — never trust the embedded tid alone.
  if (tid !== session.tid) {
    return c.json({ error: 'Forbidden' }, 403)
  }
  // Defense in depth: the key MUST be scoped to this tenant's R2 prefix.
  if (!key.startsWith(`${session.tid}/`)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const object = await c.env.STORAGE.get(key)
  if (!object) {
    return c.json({ error: 'Not found' }, 404)
  }

  const headers = new Headers()
  object.writeHttpMetadata(headers)
  headers.set('Content-Disposition', 'attachment')
  headers.set('X-Content-Type-Options', 'nosniff')
  headers.set('etag', object.httpEtag)
  headers.set('cache-control', 'private, max-age=1800')
  return new Response(object.body, { headers })
})
