import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import {
  buildProfileUpsertData,
} from '../src/modules/sync/cold-import/patient-upsert.util';
import { normalizeCanonical } from '../src/modules/sync/assembler/field-mapper';
import { PatientCanonicalSchema } from '@pac/types';

/**
 * 宿主跟进闸(`patient_profiles.host_follow_up_active`)—— 宿主自己正在跟进的患者不进召回池。
 *
 * 【本测最要紧的一条:三态,不是两态】
 *   null  = 宿主**未提供**该信号(如 jvs-dw 未接入)→ 不启用该闸,患者照常可召回
 *   false = 宿主明确说"没在跟"                    → 可召回
 *   true  = 宿主正在跟                            → 排除
 *
 *   由此查询必须写 `IS NOT TRUE` 而**不是** `= false`:后者遇 NULL 恒为 UNKNOWN,
 *   会把所有未接入该信号的宿主(现在就是 jvs-dw 全部 38 万患者)静默挡在召回池外 ——
 *   而且不报错、不留痕,只表现为"池子空了",极难排查。同理 upsert 侧不能 `?? false`,
 *   那等于替宿主断言"确认没在跟"。
 *
 * 与 do_not_contact / deceased 并列在②合规硬过滤,但性质不同:
 *   合规闸 = 法务/风险,永久,需人工解除;跟进闸 = 协作分工,临时,宿主停手即回池。
 */
describe('宿主跟进闸 host_follow_up_active', () => {
  describe('① canonical 归一:0/1 与 true/false 都吃', () => {
    const ctx = { amountUnit: 'yuan' as const, timezone: 'Asia/Shanghai' };
    const norm = (v: unknown) =>
      normalizeCanonical({ externalId: 'P1', hostFollowUpActive: v }, 'patient', ctx)
        .hostFollowUpActive;

    test.each([
      ['1', true],
      [1, true],
      ['true', true],
      [true, true],
      ['0', false],
      [0, false],
      ['false', false],
      [false, false],
    ])('%s → %s', (input, expected) => {
      expect(norm(input)).toBe(expected);
    });

    test('⭐ 未提供 → 保持 undefined,不被补成 false', () => {
      const out = normalizeCanonical({ externalId: 'P1' }, 'patient', ctx);
      expect(out.hostFollowUpActive).toBeUndefined();
    });
  });

  describe('② 副表 upsert:full(cold-import 全量覆盖语义)', () => {
    test('⭐ 宿主未提供 → 落 null,**不能**落 false', () => {
      const d = buildProfileUpsertData({}, { partial: false });
      expect(d.hostFollowUpActive).toBeNull();
      // 对照:doNotContact / deceased 是有默认值的合规位,未提供落 false —— 语义不同,别照抄
      expect(d.doNotContact).toBe(false);
      expect(d.deceased).toBe(false);
    });

    test('宿主给 true / false → 如实落库', () => {
      expect(buildProfileUpsertData({ hostFollowUpActive: true }, { partial: false })
        .hostFollowUpActive).toBe(true);
      expect(buildProfileUpsertData({ hostFollowUpActive: false }, { partial: false })
        .hostFollowUpActive).toBe(false);
    });
  });

  describe('③ 副表 upsert:partial(push 单表部分更新语义)', () => {
    test('⭐ 未提供 → 该键不进 update 集合(不覆盖存量)', () => {
      const d = buildProfileUpsertData({ name: 'X' }, { partial: true });
      expect('hostFollowUpActive' in d).toBe(false);
    });

    test('提供 false → 进 update(宿主明确说没在跟,要能把 true 改回来)', () => {
      const d = buildProfileUpsertData({ hostFollowUpActive: false }, { partial: true });
      expect(d.hostFollowUpActive).toBe(false);
    });

    test('提供 true → 进 update', () => {
      const d = buildProfileUpsertData({ hostFollowUpActive: true }, { partial: true });
      expect(d.hostFollowUpActive).toBe(true);
    });
  });

  describe('④ 召回 SQL 闸门(源码闸)', () => {
    const sql = readFileSync(
      join(__dirname, '../src/modules/plan/engine/scenarios/treatment-initiation-recall.scenario.ts'),
      'utf8',
    );
    const code = sql
      .split('\n')
      .filter((l) => !/^\s*(\/\/|\/\*|\*)/.test(l))
      .join('\n');

    test('⭐ 入池 SQL 带宿主跟进闸', () => {
      expect(code).toContain('pp.host_follow_up_active IS NOT TRUE');
    });

    test('⭐ 绝不能写成 `= false` —— 会因 NULL 把未接入宿主的患者全挡在池外', () => {
      expect(code).not.toMatch(/host_follow_up_active\s*=\s*false/);
    });
  });

  describe('⑤ FRIDAY 映射 + 脏值防护', () => {
    const yamlPath = join(__dirname, '../data/friday/assemblers/patient.yaml');
    const yamlText = readFileSync(yamlPath, 'utf8');

    test('⭐ FRIDAY patient.yaml 映射到契约约定的宿主列名', () => {
      expect(yamlText).toMatch(/hostFollowUpActive:\s*has_active_complex_case/);
    });

    test('⭐ 脏值降级成 null,**不能**拖垮整条患者主档', () => {
      // 该字段是可选的召回闸信号,而 patient 是主数据。宿主推来无法识别的值时,
      // 若让 zod 拒收整行,姓名/电话/生日全丢 —— 为一个 nice-to-have 信号赔上主数据。
      for (const dirty of ['Y2', '待定', 'maybe', 42]) {
        const out = PatientCanonicalSchema.parse({ externalId: 'P1', name: '张三', hostFollowUpActive: dirty });
        expect(`${String(dirty)} → ${JSON.stringify(out.hostFollowUpActive)}`).toBe(`${String(dirty)} → null`);
        expect(out.name).toBe('张三'); // 主数据完好
      }
    });

    test('正常值不受 catch 影响', () => {
      expect(PatientCanonicalSchema.parse({ externalId: 'P1', hostFollowUpActive: true }).hostFollowUpActive).toBe(true);
      expect(PatientCanonicalSchema.parse({ externalId: 'P1', hostFollowUpActive: false }).hostFollowUpActive).toBe(false);
    });
  });

  describe('⑥ prisma schema:必须可空且无默认值', () => {
    const schema = readFileSync(join(__dirname, '../prisma/schema.prisma'), 'utf8');
    const line = schema
      .split('\n')
      .find((l) => l.includes('hostFollowUpActive'))!;

    test('列声明为可空 Boolean?', () => {
      expect(line).toMatch(/hostFollowUpActive\s+Boolean\?/);
    });

    test('⭐ 不得有 @default —— 给了默认值就把存量患者一次性断言成某种状态', () => {
      expect(line).not.toContain('@default');
    });
  });
});
