import { DataSource } from 'typeorm';
import { ServerMembershipEntity } from '../../../entities';

export interface RankableServer {
  id: string;
  lastSeen: number;
}

const DEFAULT_LIMIT = 12;

function clampLimit(limit: number): number {
  if (!Number.isFinite(limit) || limit <= 0) {
    return 0;
  }

  return Math.floor(limit);
}

function membershipCount(counts: Map<string, number>, id: string): number {
  return counts.get(id) ?? 0;
}

/**
 * Featured = most-populated public servers. Ranks by membership count descending,
 * breaking ties by most recent activity (`lastSeen`).
 */
export function rankFeaturedServers<T extends RankableServer>(
  rows: readonly T[],
  counts: Map<string, number>,
  limit: number = DEFAULT_LIMIT
): T[] {
  return [...rows]
    .sort((first, second) => {
      const countDelta = membershipCount(counts, second.id) - membershipCount(counts, first.id);

      if (countDelta !== 0) {
        return countDelta;
      }

      return (second.lastSeen ?? 0) - (first.lastSeen ?? 0);
    })
    .slice(0, clampLimit(limit));
}

/**
 * Trending = recently-active public servers. Ranks by most recent activity
 * (`lastSeen`) descending, breaking ties by membership count.
 */
export function rankTrendingServers<T extends RankableServer>(
  rows: readonly T[],
  counts: Map<string, number>,
  limit: number = DEFAULT_LIMIT
): T[] {
  return [...rows]
    .sort((first, second) => {
      const activityDelta = (second.lastSeen ?? 0) - (first.lastSeen ?? 0);

      if (activityDelta !== 0) {
        return activityDelta;
      }

      return membershipCount(counts, second.id) - membershipCount(counts, first.id);
    })
    .slice(0, clampLimit(limit));
}

/**
 * Loads membership counts for the supplied server ids in a single grouped query.
 */
export async function loadMembershipCounts(
  dataSource: DataSource,
  serverIds: readonly string[]
): Promise<Map<string, number>> {
  const counts = new Map<string, number>();

  if (serverIds.length === 0) {
    return counts;
  }

  const rows = await dataSource
    .getRepository(ServerMembershipEntity)
    .createQueryBuilder('membership')
    .select('membership.serverId', 'serverId')
    .addSelect('COUNT(membership.id)', 'count')
    .where('membership.serverId IN (:...serverIds)', { serverIds: [...serverIds] })
    .groupBy('membership.serverId')
    .getRawMany<{ serverId: string; count: string | number }>();

  for (const row of rows) {
    counts.set(row.serverId, Number(row.count) || 0);
  }

  return counts;
}
