import type { NodeCapacity, NodeSnapshot } from './types';

export const HEARTBEAT_STALE_MS = 90_000;

/** Nodes that accept new server allocations (provisioning). */
export const ALLOCATION_ELIGIBLE_STATUSES = ['ONLINE'] as const;

/** Nodes that can start already-provisioned servers. */
export const START_ELIGIBLE_STATUSES = ['ONLINE', 'DRAINING'] as const;

export function isHeartbeatFresh(
  lastHeartbeatAt: Date | null,
  now = Date.now(),
): boolean {
  if (!lastHeartbeatAt) {
    return false;
  }

  return now - lastHeartbeatAt.getTime() <= HEARTBEAT_STALE_MS;
}

export function effectiveMaxRamMb(node: NodeSnapshot): number {
  return Math.max(node.maxRamMb - node.platformRamMb, 0);
}

export function computeAvailableRamMb(node: NodeSnapshot): number {
  const budget = effectiveMaxRamMb(node);

  if (
    node.memoryTotalBytes != null &&
    node.memoryUsedBytes != null &&
    node.memoryTotalBytes > 0
  ) {
    const hostFreeMb = Math.floor(
      (node.memoryTotalBytes - node.memoryUsedBytes) / (1024 * 1024),
    );
    return Math.max(0, Math.min(budget - node.reservedRamMb, hostFreeMb));
  }

  return Math.max(0, budget - node.reservedRamMb);
}

export function computeAvailableSlots(node: NodeSnapshot): number {
  const byMax = Math.max(0, node.maxServers - node.activeAllocationCount);
  const byActive = Math.max(0, node.maxServers - node.activeServers);
  return Math.min(byMax, byActive);
}

export function canAllocateOnNode(node: NodeSnapshot, requiredRamMb: number): boolean {
  if (!ALLOCATION_ELIGIBLE_STATUSES.includes(node.status as (typeof ALLOCATION_ELIGIBLE_STATUSES)[number])) {
    return false;
  }

  if (!isHeartbeatFresh(node.lastHeartbeatAt)) {
    return false;
  }

  const capacity = evaluateNodeCapacity(node, requiredRamMb);
  return capacity.schedulable;
}

export function canStartOnNode(node: NodeSnapshot, requiredRamMb: number): boolean {
  if (!START_ELIGIBLE_STATUSES.includes(node.status as (typeof START_ELIGIBLE_STATUSES)[number])) {
    return false;
  }

  if (!isHeartbeatFresh(node.lastHeartbeatAt)) {
    return false;
  }

  const capacity = evaluateNodeCapacity(node, requiredRamMb);
  return capacity.schedulable;
}

export function evaluateNodeCapacity(
  node: NodeSnapshot,
  requiredRamMb: number,
): NodeCapacity {
  const availableRamMb = computeAvailableRamMb(node);
  const availableSlots = computeAvailableSlots(node);
  const schedulable =
    requiredRamMb > 0 &&
    availableRamMb >= requiredRamMb &&
    availableSlots > 0;

  const ramHeadroom = availableRamMb - requiredRamMb;
  const slotHeadroom = availableSlots - 1;
  const score = schedulable
    ? ramHeadroom * 10 + slotHeadroom * 100 - node.reservedRamMb
    : -1;

  return {
    schedulable,
    availableRamMb,
    availableSlots,
    score,
  };
}
