/**
 * Stateful portal session lifecycle — tenant-portals (wave 9b, Task 2).
 *
 * Every authenticated portal request verifies the JWT AND an active portal_sessions row.
 */
import { and, eq, gt, isNull, ne } from 'drizzle-orm'
import type { Db } from '@zync/db/queries'

type DbTx = Parameters<Parameters<Db['transaction']>[0]>[0]
import { portalSessions } from '@zync/db/schema'
import { timingSafeEqual } from '../crypto'
import { hashToken } from '../tokens'

type DbLike = Db | DbTx

export async function createPortalSession(
  db: DbLike,
  args: {
    tenantId: string
    customerId: string
    userId: string
    jwt: string
    expiresAt: Date
  },
): Promise<{ sessionId: string }> {
  const tokenHash = await hashToken(args.jwt)
  const rows = await db
    .insert(portalSessions)
    .values({
      tenantId: args.tenantId,
      customerId: args.customerId,
      userId: args.userId,
      tokenHash,
      expiresAt: args.expiresAt,
    })
    .returning({ id: portalSessions.id })

  return { sessionId: rows[0]!.id }
}

export async function findActivePortalSession(
  db: DbLike,
  tokenHash: string,
): Promise<{ id: string; tenantId: string; customerId: string; userId: string } | null> {
  const now = new Date()
  const rows = await db
    .select({
      id: portalSessions.id,
      tenantId: portalSessions.tenantId,
      customerId: portalSessions.customerId,
      userId: portalSessions.userId,
      tokenHash: portalSessions.tokenHash,
    })
    .from(portalSessions)
    .where(
      and(
        eq(portalSessions.tokenHash, tokenHash),
        isNull(portalSessions.revokedAt),
        gt(portalSessions.expiresAt, now),
      ),
    )
    .limit(1)

  const row = rows[0]
  if (!row) return null
  if (!timingSafeEqual(tokenHash, row.tokenHash)) return null

  return {
    id: row.id,
    tenantId: row.tenantId,
    customerId: row.customerId,
    userId: row.userId,
  }
}

export async function revokePortalSession(db: DbLike, sessionId: string): Promise<void> {
  await db
    .update(portalSessions)
    .set({ revokedAt: new Date() })
    .where(and(eq(portalSessions.id, sessionId), isNull(portalSessions.revokedAt)))
}

export async function revokeAllPortalSessions(
  db: DbLike,
  tenantId: string,
  customerId: string,
): Promise<void> {
  await db
    .update(portalSessions)
    .set({ revokedAt: new Date() })
    .where(
      and(
        eq(portalSessions.tenantId, tenantId),
        eq(portalSessions.customerId, customerId),
        isNull(portalSessions.revokedAt),
      ),
    )
}

/** Revoke every active portal session for the customer except `keepSessionId`. */
export async function revokeOtherPortalSessions(
  db: DbLike,
  tenantId: string,
  customerId: string,
  keepSessionId: string,
): Promise<void> {
  await db
    .update(portalSessions)
    .set({ revokedAt: new Date() })
    .where(
      and(
        eq(portalSessions.tenantId, tenantId),
        eq(portalSessions.customerId, customerId),
        isNull(portalSessions.revokedAt),
        ne(portalSessions.id, keepSessionId),
      ),
    )
}

export async function rotatePortalSession(
  db: Db,
  args: {
    oldSessionId: string
    tenantId: string
    customerId: string
    userId: string
    jwt: string
    expiresAt: Date
  },
): Promise<{ sessionId: string }> {
  return db.transaction(async (tx) => {
    await revokePortalSession(tx, args.oldSessionId)
    return createPortalSession(tx, {
      tenantId: args.tenantId,
      customerId: args.customerId,
      userId: args.userId,
      jwt: args.jwt,
      expiresAt: args.expiresAt,
    })
  })
}
