import { refreshLabelsSql, countMissingLabelsSql, LABEL_REFRESH_BATCH } from '../src/modules/plan/plan-label.sql';
import { planLabelAnchorsSql } from '../src/modules/plan/reason-temperature.sql';
import { Prisma } from '@prisma/client';

/**
 * `plan_reasons.potential_labels` —— 初选矩阵提速的那一列。
 *
 * 本地实测（15,884 条 plan 的诊所）：958 ms → 110 ms，磁盘读 119,861 → 13,500，
 * 而 24 格的数**逐字未变**。这组测试守的是让它继续成立的几条结构性前提。
 */

const text = (q: Prisma.Sql) => q.sql;

describe('矩阵查询不再碰 patient_facts', () => {
  const sql = text(planLabelAnchorsSql(Prisma.sql`fp.status = 'active'`));

  it('🔴 ⛔ 不再 JOIN patient_facts —— 那是三万多次随机查、372 MB I/O 的来源', () => {
    expect(sql).not.toMatch(/patient_facts/);
  });

  it('🔴 ⛔ 不再展开 evidence.factIds', () => {
    expect(sql).not.toMatch(/jsonb_array_elements_text/);
  });

  it('⛔ 不再 JOIN patients —— 年龄已烘进标签，那张表原本要被全表扫', () => {
    expect(sql).not.toMatch(/JOIN patients/);
  });

  it('改用预存的 potential_labels', () => {
    expect(sql).toMatch(/unnest\(pr\.potential_labels\)/);
  });

  it('末诊锚点仍来自 patient_profiles —— 温度那根轴没动', () => {
    expect(sql).toMatch(/patient_profiles/);
    expect(sql).toMatch(/last_visit_at/);
  });
});

describe('刷新 SQL 的几条结构前提', () => {
  it('🔴 带 id 游标 —— 否则全量重算每次取同一批，原地打转且不报错', () => {
    const first = text(refreshLabelsSql({ afterId: null, onlyMissing: false }));
    const next = text(refreshLabelsSql({ afterId: 'x', onlyMissing: false }));
    expect(first).not.toMatch(/pr\.id >/);
    expect(next).toMatch(/pr\.id >/);
  });

  it('🔴 seen / written / maxId 分开报 —— written=0 ⛔ 不等于做完了', () => {
    const s = text(refreshLabelsSql({ afterId: null, onlyMissing: false }));
    expect(s).toMatch(/AS seen/);
    expect(s).toMatch(/AS written/);
    expect(s).toMatch(/"maxId"/);
  });

  it('onlyMissing 才加 IS NULL 闸；全量重算⛔ 不加', () => {
    expect(text(refreshLabelsSql({ afterId: null, onlyMissing: true }))).toMatch(
      /potential_labels IS NULL/,
    );
    expect(text(refreshLabelsSql({ afterId: null, onlyMissing: false }))).not.toMatch(
      /potential_labels IS NULL/,
    );
  });

  it('🔴 用 LEFT JOIN 取 fact —— 推不出标签的也要写 {}，否则永远停在 NULL 被反复捞', () => {
    const s = text(refreshLabelsSql({ afterId: null, onlyMissing: true }));
    expect(s).toMatch(/LEFT JOIN patient_facts/);
    expect(s).toMatch(/'\{\}'::text\[\]/);
  });

  it('⛔ 不在这里另写一份标签 CASE —— 复用矩阵那份（规则表是单一真源）', () => {
    const q = refreshLabelsSql({ afterId: null, onlyMissing: true });
    // ⚠️ 标签值走参数化占位，在 `values` 里而不在 `sql` 里 —— 断言写在 sql 上会假绿。
    const vals = q.values.map(String);
    for (const lbl of ['implant', 'early_ortho', 'restoration']) {
      expect(vals).toContain(lbl);
    }
    // 且确实是一段 CASE（来自 labelCaseSql），⛔ 不是这里手写的映射
    expect(q.sql).toMatch(/CASE\s+WHEN/);
  });

  it('只处理未作废的 plan', () => {
    expect(text(refreshLabelsSql({ afterId: null, onlyMissing: true }))).toMatch(
      /fp\.superseded_at IS NULL/,
    );
    expect(text(countMissingLabelsSql)).toMatch(/fp\.superseded_at IS NULL/);
  });

  it('批量有上限 —— ⛔ 别一条 UPDATE 扫全表', () => {
    expect(LABEL_REFRESH_BATCH).toBeGreaterThan(0);
    expect(LABEL_REFRESH_BATCH).toBeLessThanOrEqual(20_000);
  });
});
