import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createServer } from 'node:net';

// Mock the docker-client module to avoid actual Docker calls
vi.mock('./docker-client.js', () => ({
  getPreviewPorts: vi.fn(),
}));

import { allocatePort } from './port-allocator.js';
import { getPreviewPorts } from './docker-client.js';

const mockedGetPreviewPorts = vi.mocked(getPreviewPorts);

describe('allocatePort', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('returns BASE_PORT (9100) when no ports are in use', async () => {
    mockedGetPreviewPorts.mockResolvedValue([]);
    const port = await allocatePort();
    expect(port).toBe(9100);
  });

  it('skips ports already used by previews', async () => {
    mockedGetPreviewPorts.mockResolvedValue([9100, 9101]);
    const port = await allocatePort();
    expect(port).toBe(9102);
  });

  it('skips non-contiguous used ports', async () => {
    mockedGetPreviewPorts.mockResolvedValue([9100, 9103]);
    const port = await allocatePort();
    expect(port).toBe(9101);
  });

  it('skips a port that is bound by another process', async () => {
    mockedGetPreviewPorts.mockResolvedValue([]);

    // Bind port 9100 to simulate external use
    const server = createServer();
    await new Promise<void>((resolve) => {
      server.listen(9100, '127.0.0.1', () => resolve());
    });

    try {
      const port = await allocatePort();
      expect(port).toBe(9101);
    } finally {
      await new Promise<void>((resolve) => {
        server.close(() => resolve());
      });
    }
  });
});
