import { describe, it, expect, beforeEach } from 'vitest';
import dgram from 'node:dgram';

import {
  allocatePortPair,
  releasePortPair,
  getPortPoolStats,
  _clearPortPool,
} from '../../src/utils/port-pool.js';

describe('utils/port-pool', () => {
  beforeEach(() => {
    _clearPortPool();
  });

  it('allocates a pair where rtcp = rtp + 1 and rtp is even', async () => {
    const { rtp, rtcp } = await allocatePortPair();
    expect(rtcp).toBe(rtp + 1);
    expect(rtp % 2).toBe(0);
    expect(rtp).toBeGreaterThanOrEqual(50000);
    expect(rtcp).toBeLessThanOrEqual(59999);
  });

  it('does not return the same pair twice while still reserved', async () => {
    const seen = new Set<number>();
    const pairs = await Promise.all(Array.from({ length: 8 }, () => allocatePortPair()));
    for (const p of pairs) {
      expect(seen.has(p.rtp)).toBe(false);
      expect(seen.has(p.rtcp)).toBe(false);
      seen.add(p.rtp);
      seen.add(p.rtcp);
    }
    expect(getPortPoolStats().reserved).toBe(16);
  });

  it('release allows the pair to be re-issued', async () => {
    const a = await allocatePortPair();
    releasePortPair(a.rtp, a.rtcp);
    expect(getPortPoolStats().reserved).toBe(0);

    // 不一定立刻拿到完全相同的端口（随机分配），但能继续分配且不再含已释放的端口
    const b = await allocatePortPair();
    expect(b.rtp).not.toBe(undefined);
  });

  it('release is idempotent', () => {
    releasePortPair(50000, 50001); // 未被分配过，应静默
    expect(getPortPoolStats().reserved).toBe(0);
  });

  it('skips ports already bound by another socket', async () => {
    // 强制占用一个偶数端口，确认 probe 会跳过它并最终返回别的端口
    const occupiedRtp = 50100;
    const blocker = dgram.createSocket('udp4');
    await new Promise<void>((resolve, reject) => {
      blocker.once('error', reject);
      blocker.bind(occupiedRtp, '0.0.0.0', () => resolve());
    });
    try {
      // 多次分配，确保命中冲突路径
      const results = await Promise.all(Array.from({ length: 6 }, () => allocatePortPair()));
      for (const r of results) {
        expect(r.rtp).not.toBe(occupiedRtp);
      }
    } finally {
      blocker.close();
    }
  });
});
