import type { ClinicalSignal } from '@pac/types';
import { ContraindicationFeatureExtractor } from '../src/modules/persona/features/contraindication.feature';
import type { FeatureExtractorContext } from '../src/modules/persona/features/feature.interface';
import { loadSignalDict } from '../src/modules/clinical-signals/signal-dict';
import { extractSignalsFromContent } from '../src/modules/clinical-signals/signal-extractor';

/**
 * 禁忌裁决层回归(persona contraindication 特征)。
 *
 * 抽取(文本→信号)由 clinical-signal-extractor.spec.ts 守;本文件守**裁决**:
 * 同一个 code 在多年病历里反复出现怎么办、到期怎么消、超复评期怎么降级、
 * 核查项有没有被当成禁忌。这一层错了不会报错,只会安静地给客服一个错标签。
 */

const NOW = new Date('2026-07-27T00:00:00Z');
const dict = loadSignalDict();
const extractor = new ContraindicationFeatureExtractor();

/** 拿真字典把一段真实病历文本抽成 signal —— 不手搓 signal 对象,免得测的是我编的形状 */
function signalsOf(text: string, occurredAt: string, field = 'past_history'): ClinicalSignal[] {
  return extractSignalsFromContent({ [field]: text }, [field], new Date(`${occurredAt}T00:00:00Z`), dict);
}

let factSeq = 0;
function emr(text: string, occurredAt: string, field = 'past_history') {
  return {
    id: `f${++factSeq}`,
    subjectId: `s${factSeq}`,
    type: 'emr_record',
    kind: 'actual',
    status: 'active',
    occurredAt: new Date(`${occurredAt}T00:00:00Z`),
    plannedFor: null,
    clinicId: null,
    content: { [field]: text, clinical_signals: signalsOf(text, occurredAt, field) },
  };
}

function diagnosis(nameZh: string, occurredAt: string) {
  return {
    ...emr(nameZh, occurredAt, 'name_zh'),
    type: 'diagnosis_record',
  };
}

function ctx(over: Partial<FeatureExtractorContext> = {}): FeatureExtractorContext {
  return {
    patient: { id: 'p1', externalId: 'e1', name: null, phone: null, gender: null, birthDate: null, active: true },
    profile: null,
    factsByType: new Map(),
    relations: [],
    appointmentsAll: [],
    potentialGaps: [],
    now: NOW,
    populationStats: null,
    ...over,
  } as FeatureExtractorContext;
}

function withFacts(facts: ReturnType<typeof emr>[], over: Partial<FeatureExtractorContext> = {}) {
  const byType = new Map<string, unknown[]>();
  for (const f of facts) {
    const arr = byType.get(f.type) ?? [];
    arr.push(f);
    byType.set(f.type, arr);
  }
  return ctx({ factsByType: byType as never, ...over });
}

/** 便捷取值 */
const data = (c: FeatureExtractorContext) =>
  extractor.extract(c)?.data as
    | { types: string[]; labels: string[]; domains: string[]; detail: Array<Record<string, unknown>>; checks: Array<Record<string, unknown>> }
    | undefined;

describe('无信号 → 不产标签', () => {
  it('既往史「无特殊陈述」的成年人没有任何禁忌', () => {
    expect(extractor.extract(withFacts([emr('无特殊陈述。否认药物过敏史', '2026-05-01')]))).toBeNull();
  });
});

describe('① 年龄禁忌(不依赖 fact)', () => {
  it('未满 19 → 种植禁忌,到期日 = 19 岁生日', () => {
    const d = data(ctx({ patient: { ...ctx().patient, birthDate: new Date('2010-03-05') } }));
    expect(d?.types).toContain('implant_age');
    expect(d?.labels).toEqual(['种植禁忌']);
    expect(d?.detail[0]!.validUntil).toBe('2029-03-05');
  });

  it('满 19 → 不再打标', () => {
    expect(extractor.extract(ctx({ patient: { ...ctx().patient, birthDate: new Date('2000-01-01') } }))).toBeNull();
  });
});

describe('② 病历信号 → 禁忌标签', () => {
  it('抗凝药 → 手术禁忌 + 种植禁忌(一个病因映射多个治疗域)', () => {
    const d = data(withFacts([emr('3年前行心脏支架治疗,现口服抗凝血药,降脂药治疗', '2025-06-01')]));
    expect(d?.types).toEqual(expect.arrayContaining(['anticoagulant', 'cardiac_implant']));
    expect(d?.labels).toEqual(['手术禁忌', '种植禁忌']); // 按风险固定序,不是命中序
  });

  it('重度牙周炎(来自诊断原文)→ 正畸禁忌', () => {
    const d = data(withFacts([diagnosis('重度牙周炎', '2026-06-01')]));
    expect(d?.types).toEqual(['severe_periodontitis']);
    expect(d?.labels).toEqual(['正畸禁忌']);
  });

  it('description 自包含 —— 客服不点开也知道禁什么、凭什么', () => {
    const f = extractor.extract(withFacts([emr('2+年前颌面部放疗史', '2025-01-01')]));
    expect(f?.description).toBe('手术禁忌 / 种植禁忌(放疗史)');
  });

  it('evidence.factIds 指向真实病历 —— 旧版是空数组,客服看到标签不知凭什么', () => {
    const f = extractor.extract(withFacts([emr('长期口服泰嘉(抗凝药物)', '2026-01-01')]));
    expect(f?.evidence.factIds.length).toBe(1);
    expect((f?.data as { detail: Array<{ phrase: string }> }).detail[0]!.phrase).toContain('泰嘉');
  });
});

describe('③ 时效 —— 禁忌≠终身禁忌', () => {
  it('2016 年那条「哺乳期」今天已失效,不再打标', () => {
    expect(extractor.extract(withFacts([emr('哺乳期', '2016-03-01')]))).toBeNull();
  });

  it('三个月前的「哺乳期」仍在有效期内', () => {
    const d = data(withFacts([emr('哺乳期', '2026-05-01')]));
    expect(d?.types).toContain('lactation');
  });

  it('放疗终身有效 —— 十年前的记录今天照样禁', () => {
    const d = data(withFacts([emr('乳腺癌放疗史', '2016-03-01')]));
    expect(d?.types).toContain('radiotherapy');
  });
});

describe('④ 超复评期仍算禁忌,只在依据里注明记录较早', () => {
  // 抗凝药可能已停,但**漏挡比多挡危险得多** —— 不降级、不消失,把"要不要确认"交给客服看日期判断
  it('三年前的抗凝药记录(复评期 730 天)仍是手术/种植禁忌', () => {
    const d = data(withFacts([emr('长期口服泰嘉(抗凝药物)', '2023-01-01')]));
    expect(d?.types).toContain('anticoagulant');
    expect(d?.labels).toEqual(['手术禁忌', '种植禁忌']);
    expect(String(d?.detail[0]!.reason)).toContain('记录较早');
  });

  it('复评期内的不加提示', () => {
    const d = data(withFacts([emr('长期口服泰嘉(抗凝药物)', '2026-06-01')]));
    expect(String(d?.detail[0]!.reason)).not.toContain('记录较早');
  });
});

describe('⑤ 多年病历冲突裁决', () => {
  it('终身型:阳性一旦出现,不被后续否定推翻(骨坏死风险不可逆)', () => {
    const d = data(
      withFacts([
        emr('2+年前颌面部放疗史', '2020-01-01'),
        emr('否认放疗史、否认系统性疾病史。', '2026-06-01'), // 后来的模板没提 → 不能抹掉
      ]),
    );
    expect(d?.types).toContain('radiotherapy');
  });

  it('非终身型:以最近一条病历为准(妊娠会结束)', () => {
    const d = data(
      withFacts([
        emr('现怀孕7月余', '2026-06-01'),
        emr('否认妊娠', '2026-07-01'),
      ]),
    );
    expect(d?.types ?? []).not.toContain('pregnancy');
  });
});

describe('⑥ check 档不进画像 —— 业务口径:对客服不构成可执行信息', () => {
  // 「有糖尿病史但控制状态未知」摆给客服看,只会让人把「问一句」当成「不能治」。
  // 信号本身照常抽取并存在 fact 的 clinical_signals 里,事实一条不丢,只是画像层不说。
  it('只有糖尿病史 → 完全不产禁忌特征', () => {
    expect(extractor.extract(withFacts([emr('糖尿病,长期服药史、其余体健', '2026-06-01')]))).toBeNull();
  });

  it.each(['高血压病史现服药控制', '骨质疏松症', '患者自述有吸烟史'])('%s 也不产', (t) => {
    expect(extractor.extract(withFacts([emr(t, '2026-06-01')]))).toBeNull();
  });

  it('真禁忌与 check 同句时,只讲禁忌那部分', () => {
    const f = extractor.extract(
      withFacts([emr('高血压病史现服药控制;曾在外院做个心脏支架手术', '2026-06-01')]),
    );
    expect(f?.description).toBe('手术禁忌 / 种植禁忌(心脏植入物/心血管手术史)');
    expect(f?.description).not.toContain('高血压');
  });

  it('抽取层照常抽到 check 信号 —— 画像不说 ≠ 事实没存', () => {
    const sigs = signalsOf('糖尿病,长期服药史、其余体健', '2026-06-01');
    expect(sigs.find((s) => s.code === 'diabetes')?.negated).toBe(false);
  });
});

describe('⑦ nextBoundaryAt —— 到期当晚自动重算,标签自己消失', () => {
  it('报出哺乳期的失效日', () => {
    // 缺了它,这个标签会一直挂到她下次恰好有新病历为止(水位闸只在事实变了时放行)
    const at = extractor.nextBoundaryAt(withFacts([emr('哺乳期', '2026-05-01')]));
    expect(at?.toISOString().slice(0, 10)).toBe('2026-10-28'); // 2026-05-01 + 180d
  });

  it('多个到期日取最早的那个', () => {
    const at = extractor.nextBoundaryAt(
      withFacts([emr('哺乳期', '2026-05-01'), emr('现怀孕7月余', '2026-06-01')]),
    );
    expect(at?.toISOString().slice(0, 10)).toBe('2026-10-28'); // 哺乳 10-28 早于妊娠 2027-03-08
  });

  it('全终身型 + 成年 → 无边界(再等下去也不会变)', () => {
    expect(extractor.nextBoundaryAt(withFacts([emr('乳腺癌放疗史', '2020-01-01')]))).toBeNull();
  });
});
