import { describe, expect, it } from 'vitest';

import {
  canAllocateOnNode,
  canStartOnNode,
  selectNodeForAllocation,
} from './index';
import type { NodeSnapshot } from './types';

function node(partial: Partial<NodeSnapshot> & Pick<NodeSnapshot, 'id'>): NodeSnapshot {
  return {
    status: 'ONLINE',
    maxServers: 10,
    maxRamMb: 16384,
    platformRamMb: 2048,
    activeServers: 0,
    lastHeartbeatAt: new Date(),
    reservedRamMb: 0,
    activeAllocationCount: 0,
    ...partial,
  };
}

describe('scheduler capacity', () => {
  it('rejects maintenance and draining nodes for new allocations', () => {
    const maintenance = node({ id: 'a', status: 'MAINTENANCE' });
    const draining = node({ id: 'b', status: 'DRAINING' });

    expect(canAllocateOnNode(maintenance, 1024)).toBe(false);
    expect(canAllocateOnNode(draining, 1024)).toBe(false);
    expect(canStartOnNode(draining, 1024)).toBe(true);
  });

  it('picks the least loaded node for placement', () => {
    const lightlyLoaded = node({ id: 'light', reservedRamMb: 1024 });
    const heavilyLoaded = node({ id: 'heavy', reservedRamMb: 8192 });

    const selected = selectNodeForAllocation(
      [heavilyLoaded, lightlyLoaded],
      2048,
    );

    expect(selected?.id).toBe('light');
  });

  it('returns null when no node has enough RAM', () => {
    const saturated = node({
      id: 'full',
      reservedRamMb: 14000,
      maxRamMb: 16384,
      platformRamMb: 2048,
    });

    expect(selectNodeForAllocation([saturated], 4096)).toBeNull();
  });
});
