/**
 * POST /api/auth/refresh — foundation-auth-rbac (Task 12).
 *
 * Reads the zync_refresh cookie, validates the stored row (not revoked, not
 * expired), rotates it (revoke old + insert new, atomically + audited in
 * rotateRefreshToken), enforces <=10 active tokens/user/tenant, and issues a
 * new zync_session + zync_refresh. Public route (refresh cookie IS the auth);
 * Origin is checked inline.
 */
import { Hono } from 'hono'
import { getCookie } from 'hono/cookie'
import {
  blocklistToken,
  buildSessionPayload,
  generateOpaqueToken,
  hashToken,
  recordSessionOnLogin,
  signSession,
  SESSION_TTL_SECONDS,
} from '@zync/auth'
import {
  countActiveRefreshTokens,
  createDb,
  getActiveRefreshTokenByHash,
  getMembershipView,
  getPermissionsForRole,
  revokeSessionByTokenHash,
  rotateRefreshToken,
} from '@zync/db/queries'
import type { TenantId, TenantTier, UserId } from '@zync/types'
import type { AppEnv } from '../../types'
import { REFRESH_COOKIE, SESSION_COOKIE } from '../../lib/cookies'
import { refreshExpiry, setRefreshCookie, setSessionCookie } from '../../lib/cookies'
import {
  loadTwoFactorOptsForTenant,
  parsePriorSessionFromRequest,
} from '../../lib/session-two-factor'
import { getUserVersion } from '../../middleware/user-version'
import { getAppOrigins } from '../../lib/origins'
const MAX_ACTIVE_TOKENS = 10

export const refreshRoute = new Hono<AppEnv>()

refreshRoute.post('/refresh', async (c) => {
  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)
  }

  const presented = getCookie(c, REFRESH_COOKIE)
  if (!presented) return c.json({ error: 'Unauthorized' }, 401)

  const db = createDb(c.env)
  const presentedHash = await hashToken(presented)
  const row = await getActiveRefreshTokenByHash(db, presentedHash)
  if (!row) return c.json({ error: 'Unauthorized' }, 401)

  const userId = row.userId as UserId
  const tenantId = row.tenantId as TenantId | null

  // Enforce the active-token cap before minting another.
  const active = await countActiveRefreshTokens(db, userId, tenantId)
  if (active > MAX_ACTIVE_TOKENS) {
    return c.json({ error: 'Too many active sessions' }, 429)
  }

  if (!tenantId) return c.json({ error: 'Unauthorized' }, 401)
  const m = await getMembershipView(db, userId, tenantId)
  if (!m || m.status !== 'active') return c.json({ error: 'Unauthorized' }, 401)

  const newPlain = generateOpaqueToken()
  const newHash = await hashToken(newPlain)
  const rotated = await rotateRefreshToken(db, {
    oldTokenHash: presentedHash,
    userId,
    tenantId,
    newTokenHash: newHash,
    expiresAt: refreshExpiry(),
    actorIp: c.req.header('CF-Connecting-IP') ?? null,
    requestId: c.req.header('CF-Ray') ?? null,
  })
  if (!rotated) return c.json({ error: 'Unauthorized' }, 401)

  const permissions = await getPermissionsForRole(db, m.roleId)
  const version = await getUserVersion(c.env, userId)
  const priorSession = await parsePriorSessionFromRequest(c)
  const twoFactor = await loadTwoFactorOptsForTenant(db, tenantId, priorSession)
  const payload = buildSessionPayload({
    user: { id: userId },
    tenant: { id: tenantId, tier: m.tier as TenantTier, slug: m.tenantSlug },
    role: m.role,
    permissions,
    version,
    type: 'user',
    enforce2fa: twoFactor.enforce2fa,
    twoFactorVerified: twoFactor.twoFactorVerified,
  })
  const accessToken = await signSession(payload, c.env.JWT_SECRET)
  const expiresAtDate = new Date(Date.now() + SESSION_TTL_SECONDS * 1000)

  const priorAccessToken = getCookie(c, SESSION_COOKIE)
  if (priorAccessToken) {
    const priorHash = await hashToken(priorAccessToken)
    const revoked = await revokeSessionByTokenHash(db, priorHash, 'user')
    if (revoked) {
      await blocklistToken(c.env.RATELIMIT_KV, revoked.tokenHash, revoked.expiresAt)
    }
  }

  await recordSessionOnLogin(db, {
    tenantId,
    userId,
    accessToken,
    userAgent: c.req.header('User-Agent'),
    ip: c.req.header('CF-Connecting-IP'),
    countryCode: c.req.header('CF-IPCountry'),
    expiresAt: expiresAtDate,
  })

  setSessionCookie(c, accessToken)
  setRefreshCookie(c, newPlain)
  return c.json({ expiresAt: Date.now() + SESSION_TTL_SECONDS * 1000 }, 200)
})
