import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import * as yaml from 'js-yaml';

/**
 * FRIDAY 患者关系边的**方向**必须与 PAC 契约同向:码 = 「对方(referee)是本人(customer)的 X」。
 *
 * 踩过的坑(2026-07-20 引入 → 07-30 发现):
 *   assembler 注释曾断言「码 = 本人是对方的 X」(依据 RecommendRelationshipEnum.reverseValue()
 *   源码 + 早期年龄差推断),于是 enum_mapping 整体取逆:码2 爸爸→child、码3 子女→father/mother、
 *   码5 爷爷→grandchild…… 结果 FRIDAY 的父母/子女/祖孙关系方向**系统性反转**:
 *     PAC 侧方向矛盾率 friday 94.6%(1187/1255) vs jvs-dw 4.0%(432/10842,人工零星录错)
 *     互反对:mother↔child 326 对里只有 8 对方向正确(2.5%)
 *     实样:秦佳(47) --mother--> 韩秦瑜(21) —— 47 岁的人有个 21 岁的「妈妈」
 *
 * 本测用**源库实测的年龄方向**作为判据来锁语义,而不是比对字符串字面量 ——
 * 这样将来谁再把映射取逆,都会在这里被抓住,而不是等有人在工作台上看见一个 21 岁的妈妈。
 *
 * 源库全量实测(2026-07-30,join customer_basic_info 两侧 birthday):
 *   码10 妈妈 关系人年长 419/430(97%)  |  码3 子女 本人年长 643/675(95%)
 *   码 2 爸爸 关系人年长 252/277(91%)  |  码15 外公 关系人年长 20/22(91%)
 *   码11 奶奶 关系人年长  17/19(89%)  |  码16 外婆 关系人年长 15/17(88%)
 *   码17 外孙 本人年长    31/36(86%)  |  码 6 孙辈 本人年长   32/46(70%)
 * 对照(方法学自检):对称码 1配偶 348/354、4兄弟 56/55、7朋友 265/264 全是 50/50。
 */
describe('FRIDAY 关系边方向(与 PAC 契约同向)', () => {
  const yamlPath = join(
    __dirname,
    '../data/friday/assemblers/patient_relation.yaml',
  );
  const cfg = yaml.load(readFileSync(yamlPath, 'utf8')) as {
    enum_mapping: { relationship: Record<string, string> };
  };
  const map = cfg.enum_mapping.relationship;

  /** 取某个源码在 enum_mapping 里的归一结果(性别变体应一致) */
  const resolve = (code: number, sex: '1' | '2' | '' = ''): string =>
    map[`${code}|${sex}`] ?? map['_default']!;

  // 源枚举:1配偶 2爸爸 3子女 4兄弟 5爷爷 6孙子女 7朋友 8其他亲属 9姐妹 10妈妈
  //         11奶奶 12姐弟 13兄妹 14其他 15外公 16外婆 17外孙子女
  /** 该码描述的「对方」相对本人的辈分:older = 长辈,younger = 晚辈,peer = 同辈/无方向 */
  const EXPECT: Array<[number, string, string, 'older' | 'younger' | 'peer']> = [
    [1, '配偶', 'spouse', 'peer'],
    [2, '爸爸', 'father', 'older'],
    [3, '子女', 'child', 'younger'],
    [4, '兄弟', 'sibling', 'peer'],
    [5, '爷爷', 'grandparent', 'older'],
    [6, '孙子女', 'grandchild', 'younger'],
    [7, '朋友', 'friend', 'peer'],
    [9, '姐妹', 'sibling', 'peer'],
    [10, '妈妈', 'mother', 'older'],
    [11, '奶奶', 'grandparent', 'older'],
    [12, '姐弟', 'sibling', 'peer'],
    [13, '兄妹', 'sibling', 'peer'],
    [15, '外公', 'grandparent', 'older'],
    [16, '外婆', 'grandparent', 'older'],
    [17, '外孙子女', 'grandchild', 'younger'],
  ];

  /** PAC 关系词各自蕴含的辈分方向 */
  const DIRECTION: Record<string, 'older' | 'younger' | 'peer'> = {
    father: 'older',
    mother: 'older',
    grandparent: 'older',
    child: 'younger',
    grandchild: 'younger',
    spouse: 'peer',
    sibling: 'peer',
    friend: 'peer',
    other: 'peer',
  };

  test.each(EXPECT)('码%i(%s)→ %s', (code, _cn, expected) => {
    expect(resolve(code)).toBe(expected);
  });

  test.each(EXPECT)(
    '⭐ 码%i(%s)的归一结果辈分方向必须是 %s ——(源库年龄实测)',
    (code, _cn, _expected, dir) => {
      expect(DIRECTION[resolve(code)]).toBe(dir);
    },
  );

  test('⭐ 长辈码不得映射成晚辈词(逆映射回归闸)', () => {
    // 这四类是当年被取逆的重灾区:爸爸/妈妈/祖辈曾被映射成 child/grandchild
    for (const code of [2, 10, 5, 11, 15, 16]) {
      expect(['child', 'grandchild']).not.toContain(resolve(code));
    }
    // 反向:晚辈码不得映射成长辈词
    for (const code of [3, 6, 17]) {
      expect(['father', 'mother', 'grandparent']).not.toContain(resolve(code));
    }
  });

  test('每个码的性别变体(|1 / |2 / 缺失)结果一致 —— 同向后性别不参与判定', () => {
    for (const [code] of EXPECT) {
      const [a, b, c] = [resolve(code, '1'), resolve(code, '2'), resolve(code, '')];
      expect(`码${code}: ${a}/${b}/${c}`).toBe(`码${code}: ${a}/${a}/${a}`);
    }
  });

  test('性别缺失变体必须显式配置,不能掉进 _default —— 旧版码3 少 "3|" 就静默成了 other', () => {
    const missing = EXPECT.map(([c]) => c).filter((c) => map[`${c}|`] === undefined);
    expect(missing).toEqual([]);
  });

  test('码8 其他亲属 / 码14 其他 / 未知码 → other', () => {
    expect(resolve(8)).toBe('other');
    expect(resolve(14)).toBe('other');
    expect(resolve(99)).toBe('other');
  });
});
