/**
 * Core auth middleware — foundation-auth-rbac (Task 10).
 *
 * Responsibilities, in order:
 *   1. Origin allow-list on state-mutating methods (POST/PATCH/PUT/DELETE) — 403
 *      runs BEFORE any auth work (CSRF defence-in-depth alongside SameSite).
 *   2. Extract the access token (zync_session cookie, else Authorization: Bearer).
 *   3. KV blocklist check (`session:revoked:{tokenHash}` + legacy `blocklist:{tokenHash}`) — 401.
 *   4. verifySession (HS256) — 401 on missing/invalid/expired.
 *   5. user-version revocation — 401 if token.v < current user_version.
 *   6. Populate c.set('session') and c.set('db').
 */
import type { MiddlewareHandler } from 'hono'
import { parseSessionCookie } from '@zync/auth'
import { createDb } from '@zync/db/queries'
import type { AppEnv } from '../types'
import { dispatchSessionAuthentication } from '../lib/auth-write-do'
import type { SessionExecutionContext } from '../lib/session-authentication'
import { getAppOrigins } from '../lib/origins'

const MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])

function getExecutionContext(c: Parameters<MiddlewareHandler<AppEnv>>[0]): SessionExecutionContext | undefined {
  try {
    return c.executionCtx
  } catch {
    return undefined
  }
}

function extractToken(c: Parameters<MiddlewareHandler<AppEnv>>[0]): string | null {
  const cookie = parseSessionCookie(c.req.header('Cookie'))
  if (cookie) return cookie
  const auth = c.req.header('Authorization')
  if (auth && auth.startsWith('Bearer ')) return auth.slice('Bearer '.length).trim()
  return null
}

export const authMiddleware: MiddlewareHandler<AppEnv> = async (c, next) => {
  // 1. Origin check on mutating methods.
  if (MUTATING.has(c.req.method)) {
    const origin = c.req.header('Origin')
    const allowedOrigins = getAppOrigins({
      environment: (c.env as { ENVIRONMENT?: string }).ENVIRONMENT,
    })
    if (!origin || !allowedOrigins.has(origin)) {
      return c.json({ error: 'Forbidden: invalid Origin' }, 403)
    }
  }

  // Always provide a per-request db handle to downstream handlers.
  c.set('db', createDb(c.env))

  // 2. Extract token.
  const token = extractToken(c)
  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const authentication = await dispatchSessionAuthentication(c.env, token, getExecutionContext(c))
  if (!authentication.ok) {
    return c.json(
      { error: authentication.error, ...(authentication.code ? { code: authentication.code } : {}) },
      authentication.status,
    )
  }

  c.set('session', authentication.session)
  c.set('accessTokenHash', authentication.accessTokenHash)
  if (authentication.sessionId) c.set('sessionId', authentication.sessionId)
  return next()
}
