import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { ASSIGNMENT_ITEMS_HARD_LIMIT } from '@pac/types';

/**
 * 批量落库的**写法**回归 —— 这一类失败全是"到了某个条数才炸",本地小数据永远测不出来。
 *
 * ═══ 事故(2026-08-20 生产实遇)═══════════════════════════════════════
 * 杭州大厦 23 位客服,按默认值 `23 × 15 通 × 3 天 = 1035`,提案出了 537 条的确认单,
 * 主管点确认收到一句「请求字段校验失败」,在卡片上改什么都没用。
 * 根因不是业务,是**写法**:落库那条 UPDATE 用 `VALUES (…),(…),…`,每行 9 个 bind,
 * 而 PG 单条语句上限 32,767 → `32767/9 ≈ 3,640 行`是一堵硬墙。
 * 测试机实测 N=4000 直接报
 *   `too many bind variables in prepared statement, expected maximum of 32767, received 36000`
 *
 * ⇒ 改成 `unnest(数组)`:每列一个数组,**不管多少行永远 9 个 bind**,墙消失,而且更快
 *   (N=500 444ms → 74ms;N=2000 458ms → 220ms)。
 *
 * 这几条断言守的就是"别改回去"。
 */
const BIND_MAX = 32_767;
/** 旧写法每行 9 个 bind,能撑到的最大行数 —— 新上限必须**明显超过**它,否则等于没改 */
const OLD_WALL = Math.floor(BIND_MAX / 9);

const read = (f: string) => readFileSync(join(__dirname, `../src/modules/plan/${f}`), 'utf8');
const ASSIGN = read('plan-assignment.service.ts');
const REARRANGE = read('plan-rearrange.service.ts');

/** 去注释 —— 上面那几段红字里当然会出现 `VALUES`,不剥掉断言会被自己的说明打败 */
const strip = (x: string) =>
  x.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/[^\n]*/g, '$1');

describe('批量落库必须用 unnest(数组),⛔ 不能回到逐行 VALUES', () => {
  test('🔴 分配落库用 unnest,且⛔ 没有逐行 VALUES', () => {
    const code = strip(ASSIGN);
    expect(code).toMatch(/FROM \(\s*SELECT \* FROM unnest\(/);
    // 逐行 VALUES 的特征:`VALUES ${Prisma.join(...)}`
    expect(code).not.toMatch(/VALUES \$\{Prisma\.join/);
  });

  test('🔴 调整在手的三条写路径也都用 unnest', () => {
    const code = strip(REARRANGE);
    // 改派 / 改时限 / 移出
    expect((code.match(/FROM unnest\(/g) ?? []).length).toBeGreaterThanOrEqual(3);
    expect(code).not.toMatch(/VALUES \$\{Prisma\.join/);
  });

  test('⛔ 大 id 列表不许用 `IN (Prisma.join(...))` —— 每个 id 占一个 bind', () => {
    // `= ANY($1::uuid[])` 永远 1 个 bind,而且更快(3 万个 id:IN 521ms → ANY 219ms)
    expect(strip(REARRANGE)).not.toMatch(/IN \(\$\{Prisma\.join/);
  });

  test('⭐ 上限必须**越过**旧写法那堵墙,否则等于没解决', () => {
    expect(OLD_WALL).toBe(3640); // 32767 / 9,钉住这个推导本身
    expect(ASSIGNMENT_ITEMS_HARD_LIMIT).toBeGreaterThan(OLD_WALL);
  });

  test('⭐ 事务超时随条数放宽 —— ⛔ 不是一刀切', () => {
    // 一刀切的话:小批次白留 100 倍余量,大批次又不够
    expect(strip(ASSIGN)).toMatch(/timeout: Math\.min\(180_000, 30_000 \+ applicable\.length \* 3\)/);
  });

  test('⭐ 报错文案要说清他能动哪个旋钮(他手上没有"本批人数"这个输入框)', () => {
    expect(ASSIGN).toMatch(/把时限改短、或者把每天几通调小/);
  });
});
