import type { Session, User } from 'lucia'
import { Buffer } from 'node:buffer'
import { AccessToken } from 'livekit-server-sdk'
import { SESSION_EXPIRES_IN } from '../auth/lucia.ts'
import prisma from '../prisma/client.ts'

/**
 * Re-issue the token slightly before it actually expires, so a client that
 * caches the /me response for a while still gets a usable token.
 */
const REFRESH_BEFORE_EXPIRY_SECONDS = 60 * 60

export async function createLivekitToken(user: User, ttlSeconds: number) {
  const apiKey = process.env.LIVEKIT_API_KEY
  const apiSecret = process.env.LIVEKIT_API_SECRET

  if (!apiKey || !apiSecret)
    throw new Error('LIVEKIT_API_KEY and LIVEKIT_API_SECRET must be set')

  const token = new AccessToken(apiKey, apiSecret, {
    identity: user.id,
    name: user.displayName,
    ttl: ttlSeconds,
  })

  token.addGrant({
    room: process.env.LIVEKIT_ROOM || 'chad',
    roomJoin: true,
    canPublish: true,
    canSubscribe: true,
    canPublishData: true,
    canUpdateOwnMetadata: true,
  })

  return token.toJwt()
}

/** Mints the token that gets stored on a session at the moment it is created. */
export function createSessionLivekitToken(user: User) {
  return createLivekitToken(user, SESSION_EXPIRES_IN.seconds())
}

/**
 * Returns the token stored alongside the session, re-issuing and persisting it
 * when it is missing (session predates the column) or about to expire (Lucia
 * extends `expiresAt` on active sessions, outliving the original token).
 */
export async function getSessionLivekitToken(user: User, session: Session) {
  if (session.livekitToken && !isExpiringSoon(session.livekitToken))
    return session.livekitToken

  const ttlSeconds = Math.max(
    Math.ceil((session.expiresAt.getTime() - Date.now()) / 1000),
    REFRESH_BEFORE_EXPIRY_SECONDS,
  )
  const livekitToken = await createLivekitToken(user, ttlSeconds)

  await prisma.session.update({
    where: { id: session.id },
    data: { livekitToken },
  })

  session.livekitToken = livekitToken

  return livekitToken
}

function isExpiringSoon(token: string) {
  try {
    const [, payload] = token.split('.')
    const { exp } = JSON.parse(Buffer.from(payload, 'base64url').toString())

    return typeof exp !== 'number' || exp - REFRESH_BEFORE_EXPIRY_SECONDS <= Date.now() / 1000
  }
  catch {
    return true
  }
}
