import {
  blocklistRevokedTokens,
  hashToken,
  isTokenBlocklisted,
  verifySession,
} from '@zync/auth'
import {
  createDb,
  revokeSession,
  touchSession,
} from '@zync/db/queries'
import type { Env } from '@zync/types'
import { evaluateUserSessionRow } from '../middleware/session-guard'
import { getUserVersion } from '../middleware/user-version'

type VerifiedSession = Awaited<ReturnType<typeof verifySession>>

export type SessionExecutionContext = {
  waitUntil(promise: Promise<unknown>): void
}

export type SessionAuthenticationResult =
  | {
      ok: true
      session: VerifiedSession
      accessTokenHash: string
      sessionId?: string
    }
  | {
      ok: false
      status: 401 | 503
      error: string
      code?: 'idle_timeout'
    }

/** Complete the CPU-heavy authentication/session-row gate in one runtime. */
export async function runSessionAuthentication(
  env: Env,
  token: string,
  executionCtx?: SessionExecutionContext,
): Promise<SessionAuthenticationResult> {
  const accessTokenHash = await hashToken(token)
  const [legacyBlocked, canonicalBlocked] = await Promise.all([
    env.KV.get(`blocklist:${accessTokenHash}`),
    isTokenBlocklisted(env.RATELIMIT_KV, accessTokenHash),
  ])
  if (legacyBlocked || canonicalBlocked) {
    return { ok: false, status: 401, error: 'Unauthorized' }
  }

  let session: VerifiedSession
  try {
    session = await verifySession(token, env.JWT_SECRET)
  } catch {
    return { ok: false, status: 401, error: 'Unauthorized' }
  }

  const currentVersion = await getUserVersion(env, session.sub)
  if (session.v < currentVersion) {
    return { ok: false, status: 401, error: 'Unauthorized' }
  }

  if (session.type !== 'user' || !session.tid) {
    return { ok: true, session, accessTokenHash }
  }

  const db = createDb(env)
  const evaluation = await evaluateUserSessionRow(db, session.tid, accessTokenHash)
  if (!evaluation.ok) {
    if (evaluation.reason === 'missing') {
      return { ok: false, status: 401, error: 'Unauthorized' }
    }
    if (evaluation.reason === 'revoked') {
      return { ok: false, status: 401, error: 'Session revoked' }
    }
    const sessionRow = evaluation.sessionRow!
    const cleanup = revokeSession(
      db,
      sessionRow.id,
      sessionRow.userId,
      session.tid,
      'idle_timeout',
    ).then((revoked) => {
      if (revoked.tokens.length > 0) return blocklistRevokedTokens(env.RATELIMIT_KV, revoked.tokens)
    }).catch(() => undefined)
    executionCtx?.waitUntil(cleanup)
    return {
      ok: false,
      status: 401,
      error: 'Session expired due to inactivity',
      code: 'idle_timeout',
    }
  }

  const touch = touchSession(db, evaluation.sessionRow.tenantId, evaluation.sessionRow.id).catch(() => undefined)
  executionCtx?.waitUntil(touch)
  return {
    ok: true,
    session,
    accessTokenHash,
    sessionId: evaluation.sessionRow.id,
  }
}
