import { runPool } from '../src/common/run-pool';
import { withCohortDerivedPool } from '../src/prisma/prisma.service';

/**
 * 画像重算的两个吞吐修复(2026-07-26,依据测试服 12 万患者实测):
 *
 *   基线  p50 139ms / p90 230ms / p99 386ms / max 75.7s,均值 165ms
 *         concurrency=8 理论上限 8÷0.165 = 2,909 人/分,实际 ~1,600 → **效率 55%**
 *
 *   ① 批次栅栏 → worker pool:缺口正好是 E[8 次抽样最大值](≈p90)与均值之比;
 *      那个 75.7 秒的患者会把同批 7 个 worker 一起冻住。
 *   ② --concurrency 没放大连接池 —— **潜在**缺陷,咬不咬人看该机 .env:
 *        测试服 URL 显式 `connection_limit=30` → 不受影响
 *        生产   URL 没写 → Prisma 默认 核数×2+1,4 核机 = 9 → --concurrency>8 会被卡住
 *      所以按 --concurrency=8 跑生产时本修复无收益,价值在于解锁更高并发。
 */

describe('runPool —— 连续调度,不做批次栅栏', () => {
  test('全部跑完,且顺序不丢项', async () => {
    const items = Array.from({ length: 50 }, (_, i) => i);
    const seen: number[] = [];
    await runPool(items, 7, async (x) => { seen.push(x); });
    expect(seen.sort((a, b) => a - b)).toEqual(items);
  });

  test('并发上限严格 ≤ concurrency', async () => {
    let inFlight = 0;
    let peak = 0;
    await runPool(Array.from({ length: 40 }, (_, i) => i), 5, async () => {
      peak = Math.max(peak, ++inFlight);
      await new Promise((r) => setTimeout(r, 5));
      inFlight--;
    });
    expect(peak).toBeLessThanOrEqual(5);
    expect(peak).toBe(5); // 也要真的用满,不是退化成串行
  });

  test('⭐ 一个慢任务只拖住自己那条线,不冻住同批其他 worker', async () => {
    // 4 个任务:第 0 个慢 200ms,其余 3 个各 10ms;并发 2。
    //   栅栏写法:[0,1] 一批 → 等 200ms;[2,3] 一批 → 10ms;总 ≈210ms
    //   worker pool:worker A 卡在 0 上,worker B 依次做完 1、2、3;总 ≈200ms 且 1/2/3 早早完成
    const done: Array<{ id: number; at: number }> = [];
    const t0 = Date.now();
    await runPool([0, 1, 2, 3], 2, async (x) => {
      await new Promise((r) => setTimeout(r, x === 0 ? 200 : 10));
      done.push({ id: x, at: Date.now() - t0 });
    });
    const slow = done.find((d) => d.id === 0)!;
    const fastest3 = done.filter((d) => d.id !== 0);
    expect(fastest3).toHaveLength(3);
    // 三个快任务必须在慢任务之前就都做完了 —— 栅栏写法下 2、3 只能等慢任务结束才开始
    for (const f of fastest3) expect(f.at).toBeLessThan(slow.at);
  });

  test('concurrency 大于任务数 / 空列表 都不炸', async () => {
    const seen: number[] = [];
    await runPool([1, 2], 99, async (x) => { seen.push(x); });
    expect(seen.sort()).toEqual([1, 2]);
    await expect(runPool([], 8, async () => { throw new Error('不该被调用'); })).resolves.toBeUndefined();
  });

  test('fn 抛错会向上冒(调用方各自 try/catch,跟原 .map(runOne) 用法一致)', async () => {
    await expect(runPool([1], 2, async () => { throw new Error('boom'); })).rejects.toThrow('boom');
  });
});

describe('连接池随并发放大 —— PAC_DB_CONCURRENCY', () => {
  const URL_ = 'postgresql://u:p@db:5432/pac?schema=public';
  const saved = { db: process.env.PAC_DB_CONCURRENCY, cohort: process.env.PAC_COHORT_CONCURRENCY };
  const set = (db?: string, cohort?: string) => {
    if (db === undefined) delete process.env.PAC_DB_CONCURRENCY; else process.env.PAC_DB_CONCURRENCY = db;
    if (cohort === undefined) delete process.env.PAC_COHORT_CONCURRENCY; else process.env.PAC_COHORT_CONCURRENCY = cohort;
  };
  afterAll(() => set(saved.db, saved.cohort));

  const limitOf = (u: string | undefined) =>
    u ? new URL(u).searchParams.get('connection_limit') : null;
  /// 口径与实现同源:n×6+5,封顶 40
  const expected = (n: number) => String(Math.min(n * 6 + 5, 40));

  test('⭐ 修的正是这条:PAC_DB_CONCURRENCY 要能放大池(原来只认 COHORT,CLI 旋钮静默失效)', () => {
    set('4', undefined);
    expect(limitOf(withCohortDerivedPool(URL_))).toBe(expected(4)); // 29
    set('8', undefined);
    expect(limitOf(withCohortDerivedPool(URL_))).toBe(expected(8)); // 53 → 封顶 40
  });

  test('向后兼容:PAC_COHORT_CONCURRENCY 仍然有效(摄入路径不能被改坏)', () => {
    set(undefined, '4');
    expect(limitOf(withCohortDerivedPool(URL_))).toBe(expected(4));
  });

  test('两者都设 → 取大', () => {
    set('2', '5');
    expect(limitOf(withCohortDerivedPool(URL_))).toBe(expected(5)); // 35,不是 17
  });

  test('封顶 40(守住 Postgres max_connections=100)', () => {
    set('64', undefined);
    expect(limitOf(withCohortDerivedPool(URL_))).toBe('40');
  });

  test('都没设 / =1(常驻 API 进程)→ 不动 URL,走 Prisma 默认池', () => {
    set(undefined, undefined);
    expect(withCohortDerivedPool(URL_)).toBe(URL_);
    set('1', '1');
    expect(withCohortDerivedPool(URL_)).toBe(URL_);
  });

  test('URL 里已显式写了 connection_limit → 尊重,不覆盖(逃生口)', () => {
    set('16', undefined);
    const explicit = 'postgresql://u:p@db:5432/pac?connection_limit=7';
    expect(withCohortDerivedPool(explicit)).toBe(explicit);
  });

  test('非标准 URL(unix socket 等)原样返回,不抛', () => {
    set('8', undefined);
    expect(withCohortDerivedPool('not-a-url')).toBe('not-a-url');
    expect(withCohortDerivedPool(undefined)).toBeUndefined();
  });
});
