/**
 * push 通道 lookup 回落(from 表缺席 → 从 PAC patients 重建)
 *
 * 契约背景:channel-push.mdx §7.3「推送顺序无要求」。push 是单表请求,lookup 的 from 表
 * 不在场,不回落就等于偷偷要求宿主先推 from 表。2026-07-28 FRIDAY 实测踩到:
 * customer_referee_circle 拿不到对方性别 → 关系码 3 无法拆 father/mother → 全掉 other。
 *
 * 这里连着 runLookup 一起测,断言的是**最终喂给 assembler 的那个值**,
 * 而不是中间产物 —— 中间对了下游错了没有意义。
 */
import {
  buildPushLookupFallbackRows,
  type PatientFallbackRow,
} from '../src/modules/sync/cold-import/push-lookup-fallback';
import { runLookup } from '../src/modules/sync/transforms/operators/lookup.op';
import type { LookupOp } from '../src/modules/sync/transforms/transforms.schema';

/** friday manifest 里那条真实配置 */
const OP: LookupOp = {
  kind: 'lookup',
  input: 'customer_referee_circle',
  output: '_referee_with_sex',
  from: 'customer_basic_info',
  left_key: 'referee_patient_id',
  right_key: 'id',
  select: { referee_sex: 'sex' },
  push_fallback: {
    entity: 'patients',
    select: { sex: { column: 'gender', map: { male: '1', female: '2' } } },
  },
};

const patient = (o: Partial<PatientFallbackRow> & { externalId: string }): PatientFallbackRow => ({
  sourceUnit: 'brandA',
  gender: null,
  birthDate: null,
  name: null,
  medicalRecordNumber: null,
  ...o,
});

/** 源品牌列 = friday 的 identity_namespace_field: tenant_id */
const resolveUnit = (row: Record<string, unknown>) => String(row.tenant_id ?? '');

const fetchOf = (rows: PatientFallbackRow[]) => async () => rows;

describe('push lookup 回落 | 重建 from 表', () => {
  test('对方已入库 → 性别按宿主原编码回填,码 3 能拆父/母', async () => {
    const input = [
      { customer_id: 'A', referee_patient_id: 'B', referee_relationship: '3', tenant_id: 'brandA' },
      { customer_id: 'C', referee_patient_id: 'D', referee_relationship: '3', tenant_id: 'brandA' },
    ];
    const { rows, resolved, absent, ambiguous } = await buildPushLookupFallbackRows(
      OP,
      input,
      resolveUnit,
      fetchOf([
        patient({ externalId: 'B', gender: 'male' }),
        patient({ externalId: 'D', gender: 'female' }),
      ]),
    );
    expect({ resolved, absent, ambiguous }).toEqual({ resolved: 2, absent: 0, ambiguous: 0 });

    const joined = runLookup(OP, input, rows);
    expect(joined.rows.map((r) => `${String(r.referee_relationship)}|${String(r.referee_sex)}`))
      .toEqual(['3|1', '3|2']); // → patient_relation.yaml 里映射成 father / mother
  });

  test('对方主档还没推达 → 不硬猜,落 null(下游走 _default: other)', async () => {
    const input = [
      { customer_id: 'A', referee_patient_id: 'B', referee_relationship: '3', tenant_id: 'brandA' },
    ];
    const { rows, resolved, absent } = await buildPushLookupFallbackRows(
      OP,
      input,
      resolveUnit,
      fetchOf([]),
    );
    expect({ resolved, absent }).toEqual({ resolved: 0, absent: 1 });
    expect(runLookup(OP, input, rows).rows[0]!.referee_sex).toBeNull();
  });

  test('external_id 跨 source_unit 撞号 → 整条跳过,不猜哪个品牌', async () => {
    const input = [
      { customer_id: 'A', referee_patient_id: 'B', referee_relationship: '3', tenant_id: 'brandA' },
    ];
    const { rows, ambiguous, resolved } = await buildPushLookupFallbackRows(
      OP,
      input,
      resolveUnit,
      fetchOf([
        patient({ externalId: 'B', sourceUnit: 'brandA', gender: 'male' }),
        patient({ externalId: 'B', sourceUnit: 'brandB', gender: 'female' }),
      ]),
    );
    expect({ ambiguous, resolved }).toEqual({ ambiguous: 1, resolved: 0 });
    expect(rows).toHaveLength(0);
  });

  test('宿主未知性别落库是空串 → 等同取不到', async () => {
    const input = [
      { customer_id: 'A', referee_patient_id: 'B', referee_relationship: '3', tenant_id: 'brandA' },
    ];
    const { rows } = await buildPushLookupFallbackRows(
      OP,
      input,
      resolveUnit,
      fetchOf([patient({ externalId: 'B', gender: '' })]),
    );
    expect(rows[0]!.sex).toBeNull();
  });

  test('map 未命中 → null,不把 PAC 归一值漏给只认宿主编码的下游', async () => {
    const input = [
      { customer_id: 'A', referee_patient_id: 'B', referee_relationship: '3', tenant_id: 'brandA' },
    ];
    const { rows } = await buildPushLookupFallbackRows(
      OP,
      input,
      resolveUnit,
      fetchOf([patient({ externalId: 'B', gender: 'unknown-新值' })]),
    );
    expect(rows[0]!.sex).toBeNull();
  });

  test('没配 push_fallback 的 lookup 保持旧行为(本改动对存量 host 零影响)', async () => {
    const bare: LookupOp = { ...OP, push_fallback: undefined };
    const r = await buildPushLookupFallbackRows(
      bare,
      [{ referee_patient_id: 'B', tenant_id: 'brandA' }],
      resolveUnit,
      fetchOf([patient({ externalId: 'B', gender: 'male' })]),
    );
    expect(r).toEqual({ rows: [], resolved: 0, ambiguous: 0, absent: 0 });
  });

  test('left_key 空/缺失的行不产生查询需求', async () => {
    const fetch = jest.fn(async () => []);
    const { resolved, absent } = await buildPushLookupFallbackRows(
      OP,
      [
        { referee_patient_id: null, tenant_id: 'brandA' },
        { referee_patient_id: '  ', tenant_id: 'brandA' },
        { tenant_id: 'brandA' },
      ],
      resolveUnit,
      fetch,
    );
    expect(fetch).not.toHaveBeenCalled();
    expect({ resolved, absent }).toEqual({ resolved: 0, absent: 0 });
  });
});
