/**
 * User session routes — session-security (wave-13).
 * Mounted at /api/user/sessions.
 *
 * GET  /           → list calling user's active sessions
 * POST /keepalive  → touch last_active_at for current session
 * DELETE /:id      → revoke a specific session (own)
 * DELETE /         → revoke all other sessions (keep current)
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import {
  getSessionByTokenHash,
  listUserSessions,
  revokeSession,
  revokeOtherUserSessions,
  touchSession,
} from '@zync/db/queries'
import { blocklistRevokedTokens, timingSafeEqual } from '@zync/auth'
import { logAuditEvent } from '@zync/db/queries'

export const sessionsRoute = new Hono<AppEnv>()

sessionsRoute.use('*', authMiddleware)

// ── POST /api/user/sessions/keepalive ─────────────────────────────────────────

sessionsRoute.post('/keepalive', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !(session as { tid?: string }).tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const accessTokenHash = c.get('accessTokenHash')
  if (!accessTokenHash) return c.json({ error: 'Unauthorized' }, 401)

  const db = c.get('db')
  const row = await getSessionByTokenHash(db, accessTokenHash)
  if (!row || row.revokedAt) return c.json({ error: 'Unauthorized' }, 401)

  await touchSession(db, row.tenantId, row.id)
  return c.json({ ok: true }, 200)
})

// ── GET /api/user/sessions ────────────────────────────────────────────────────

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

  const db = c.get('db')
  const tenantId = (session as { tid: string }).tid
  const userId = session.sub

  const rows = await listUserSessions(db, userId, tenantId)
  const currentHash = c.get('accessTokenHash')

  const sessions = rows.map((r) => ({
    id: r.id,
    deviceName: r.deviceName,
    ipAddress: r.ipAddress,
    countryCode: r.countryCode,
    createdAt: r.createdAt,
    lastActiveAt: r.lastActiveAt,
    expiresAt: r.expiresAt,
    // Use timingSafeEqual — never === for token hashes.
    isCurrent: currentHash != null && timingSafeEqual(r.tokenHash, currentHash),
  }))

  return c.json({ sessions }, 200)
})

// ── DELETE /api/user/sessions/:id ─────────────────────────────────────────────

const revokeParamSchema = z.object({ id: z.string().uuid() })

sessionsRoute.delete('/:id', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !(session as { tid?: string }).tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const parsed = revokeParamSchema.safeParse({ id: c.req.param('id') })
  if (!parsed.success) return c.json({ error: 'Invalid session id' }, 400)

  const db = c.get('db')
  const tenantId = (session as { tid: string }).tid
  const currentHash = c.get('accessTokenHash')

  const rows = await listUserSessions(db, session.sub, tenantId)
  const target = rows.find((r) => r.id === parsed.data.id)
  if (!target) return c.json({ error: 'Session not found' }, 404)

  if (currentHash && timingSafeEqual(target.tokenHash, currentHash)) {
    return c.json({ error: 'cannot_revoke_current' }, 400)
  }

  const { tokens } = await revokeSession(db, parsed.data.id, session.sub, tenantId, 'user')

  if (tokens.length > 0) {
    await blocklistRevokedTokens(c.env.RATELIMIT_KV, tokens)
  }

  void logAuditEvent({ env: c.env }, {
    tenantId,
    userId: session.sub,
    eventType: 'auth.sessions_revoked',
    metadata: { sessionId: parsed.data.id, count: tokens.length },
  })

  return c.json({ ok: true }, 200)
})

// ── DELETE /api/user/sessions (revoke all other) ──────────────────────────────

sessionsRoute.delete('/', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !(session as { tid?: string }).tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const db = c.get('db')
  const tenantId = (session as { tid: string }).tid
  const currentTokenHash = c.get('accessTokenHash') ?? ''

  const { tokens } = await revokeOtherUserSessions(db, session.sub, tenantId, currentTokenHash, 'user')

  if (tokens.length > 0) {
    await blocklistRevokedTokens(c.env.RATELIMIT_KV, tokens)
  }

  void logAuditEvent({ env: c.env }, {
    tenantId,
    userId: session.sub,
    eventType: 'auth.sessions_revoked',
    metadata: { scope: 'other_sessions', count: tokens.length },
  })

  return c.json({ ok: true, revoked: tokens.length }, 200)
})
