/**
 * test/desktop/port.test.ts — unit tests for pickFreePort (B1).
 *
 * Real loopback binds (not mocks) exercise both branches: the preferred port is
 * free (return it) and the preferred port is occupied (fall back to an OS port).
 */

import { describe, test, expect } from 'vitest'
import net from 'node:net'
import { pickFreePort } from '../../desktop/src/port.js'

const LOOPBACK = '127.0.0.1'
const MIN_PORT = 1
const MAX_PORT = 65535

/** Bind a server on 127.0.0.1:0 and resolve with the live server + its OS-assigned port. */
function occupyEphemeralPort(): Promise<{ server: net.Server; port: number }> {
  return new Promise((resolve, reject) => {
    const server = net.createServer()
    server.on('error', reject)
    server.listen(0, LOOPBACK, () => {
      const address = server.address()
      if (address !== null && typeof address === 'object') resolve({ server, port: address.port })
      else reject(new Error('no AddressInfo'))
    })
  })
}

function closeServer(server: net.Server): Promise<void> {
  return new Promise((resolve) => server.close(() => resolve()))
}

/** Acquire a currently-free port number (bind ephemeral, then release it). */
async function getFreePort(): Promise<number> {
  const { server, port } = await occupyEphemeralPort()
  await closeServer(server)
  return port
}

/** Confirm a port can be bound right now, then release it. */
async function isBindable(port: number): Promise<boolean> {
  const server = net.createServer()
  try {
    await new Promise<void>((resolve, reject) => {
      server.on('error', reject)
      server.listen(port, LOOPBACK, () => resolve())
    })
    await closeServer(server)
    return true
  } catch {
    return false
  }
}

describe('pickFreePort', () => {
  test('returns the preferred port when it is free', async () => {
    // Arrange
    const preferred = await getFreePort()

    // Act
    const result = await pickFreePort(preferred)

    // Assert
    expect(result).toBe(preferred)
  })

  test('returns a different, bindable port when the preferred port is occupied', async () => {
    // Arrange
    const { server, port: occupied } = await occupyEphemeralPort()
    try {
      // Act
      const result = await pickFreePort(occupied)

      // Assert
      expect(result).not.toBe(occupied)
      expect(Number.isInteger(result)).toBe(true)
      expect(result).toBeGreaterThanOrEqual(MIN_PORT)
      expect(result).toBeLessThanOrEqual(MAX_PORT)
      expect(await isBindable(result)).toBe(true)
    } finally {
      await closeServer(server)
    }
  })

  test('returns an integer within the valid TCP port range', async () => {
    // Arrange
    const preferred = await getFreePort()

    // Act
    const result = await pickFreePort(preferred)

    // Assert
    expect(Number.isInteger(result)).toBe(true)
    expect(result).toBeGreaterThanOrEqual(MIN_PORT)
    expect(result).toBeLessThanOrEqual(MAX_PORT)
  })
})
