export const AUTH_ACTIVITY_PAGE_SIZE = 20;

type ActivityCursor = {
  id: number;
  time: string;
  userId: number;
};

export function encodeAuthActivityCursor(cursor: ActivityCursor) {
  return Buffer.from(JSON.stringify(cursor)).toString("base64url");
}

export function decodeAuthActivityCursor(value: string | null, userId: number) {
  if (value === null) {
    return null;
  }

  try {
    if (value.length > 256 || !/^[A-Za-z0-9_-]+$/.test(value)) {
      throw new Error();
    }

    const cursor = JSON.parse(
      Buffer.from(value, "base64url").toString("utf8"),
    ) as ActivityCursor;
    if (
      !cursor ||
      cursor.userId !== userId ||
      !Number.isSafeInteger(cursor.id) ||
      cursor.id <= 0 ||
      typeof cursor.time !== "string" ||
      !/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}$/.test(cursor.time)
    ) {
      throw new Error();
    }

    const isoTime = `${cursor.time.slice(0, 23).replace(" ", "T")}Z`;
    if (new Date(isoTime).toISOString() !== isoTime) {
      throw new Error();
    }

    return cursor;
  } catch {
    throw new Error("invalid-auth-activity-cursor");
  }
}

export function authActivityPageWindow(
  userId: number,
  cursor: ActivityCursor | null,
) {
  // The composite order also disambiguates events with the same millisecond.
  return {
    where: cursor
      ? "user_id = ? AND (occurred_at < ? OR (occurred_at = ? AND id < ?))"
      : "user_id = ?",
    params: cursor
      ? [
          userId,
          cursor.time,
          cursor.time,
          cursor.id,
          AUTH_ACTIVITY_PAGE_SIZE + 1,
        ]
      : [userId, AUTH_ACTIVITY_PAGE_SIZE + 1],
  };
}

export function authActivityPage<T extends { id: number; cursor_time: string }>(
  rows: T[],
  userId: number,
) {
  const items = rows.slice(0, AUTH_ACTIVITY_PAGE_SIZE);
  const last = items.at(-1);
  return {
    items,
    nextCursor:
      rows.length > AUTH_ACTIVITY_PAGE_SIZE && last
        ? encodeAuthActivityCursor({
            id: Number(last.id),
            time: last.cursor_time,
            userId,
          })
        : null,
  };
}
