import { Injectable } from '@nestjs/common';
import { PersonaFeatureKey } from '@pac/types';
import type {
  ActiveFact,
  FeatureExtractor,
  FeatureExtractorContext,
  PersonaFeatureDraft,
} from './feature.interface';

/**
 * entitlement_status 权益身份(商保 / 医保 …)
 *
 * ⭐ 本特征是「事实投影型」,跟 value / recall_risk(计算分档型)性质不同:
 *   - 不引入业务阈值判断,只把"用过哪类保险结算"的事实 rollup 出来
 *   - 商业保险**强时效**(雇主团险换工作即失效;实测 61% 患者最近一次商保 >2 年前)
 *     且 DW 无保单有效期字段 → **不断言"当前在保"**,只产「史 + 最近日期」,
 *     时效判断留给读取方(UI 按 lastInsuranceAt 变措辞,scorer 按 monthsSinceLast 套窗口)
 *
 * 数据来源(payment_record fact,单一收口 fact.content):
 *   - 商保:channel='insurance' 或 content.insurance_name 非空(阶段2 re-ingest 后才有保司名)
 *   - 医保:channel='medical_insurance'(社保普惠,**不当 VIP**,仅记录)
 *
 * 落库(零迁移):description 人读串 / score=monthsSinceLast 给排序 /
 *   evidence.data 放结构化明细(insurers / lastInsuranceAt …)给 scorer 与 UI 结构化消费。
 *
 * 未命中(既无商保也无医保结算)→ 返回 null,不打标签。
 */
@Injectable()
export class EntitlementStatusFeatureExtractor implements FeatureExtractor {
  readonly key = PersonaFeatureKey.ENTITLEMENT_STATUS;

  /** 保司名归一(57 个脏名 → canonical;别名折叠 + 排除测试数据)。
   *  阶段1(未 re-ingest)content.insurance_name 为空 → 列表自然为空,不影响"是否商保"判定。 */
  private static canonicalInsurer(raw: unknown): string | null {
    const s = String(raw ?? '').trim();
    if (!s) return null;
    if (/测试|test|xxx|宣南书馆|乐雅健康科技|乐牙/i.test(s)) return null; // 测试/非保险脏数据
    const ALIAS: [RegExp, string][] = [
      [/万欣和|MSH/i, '万欣和'],
      [/招商信诺|信诺|CIGNA/i, '招商信诺'],
      [/平安/i, '平安健康险'],
      [/中意/i, '中意人寿'],
      [/太保安联|太平洋|CPIC/i, '太保安联'],
      [/安态|AETNA/i, '安态'],
      [/柏盛/i, '柏盛健康'],
      [/保柏|BUPA/i, '保柏'],
      [/友邦|AIA/i, '友邦'],
      [/中间带|Medilink/i, '中间带'],
      [/安联|ALLIANZ/i, '安联'],
      [/吉倍吉|GBG/i, '吉倍吉'],
      [/工银安盛/i, '工银安盛'],
      [/复星|FOSUN/i, '复星联合'],
      [/方胜|FESCO/i, '方胜'],
      [/江泰/i, '江泰救援'],
      [/休荪|HSC/i, '休荪'],
      [/安顾|ERV/i, '安顾援助'],
      [/泰康/i, '泰康养老'],
    ];
    for (const [re, name] of ALIAS) if (re.test(s)) return name;
    const zh = s.split('/')[0]!.trim();
    return zh || null;
  }

  private monthsSince(from: Date, now: Date): number {
    return Math.max(0, Math.floor((now.getTime() - from.getTime()) / (1000 * 60 * 60 * 24 * 30.4375)));
  }

  private isCommercial(c: Record<string, unknown>): boolean {
    return c.channel === 'insurance' || !!String(c.insurance_name ?? '').trim();
  }

  extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null {
    const payments = ctx.factsByType.get('payment_record') ?? [];

    const commercialFactIds: string[] = [];
    const insurerSet = new Set<string>();
    let lastCommercialAt: Date | null = null;
    let lastMedicalAt: Date | null = null;
    const medicalFactIds: string[] = [];

    for (const f of payments as ActiveFact[]) {
      const c = (f.content ?? {}) as Record<string, unknown>;
      const at = f.occurredAt ?? null;
      if (this.isCommercial(c)) {
        commercialFactIds.push(f.id);
        const ins = EntitlementStatusFeatureExtractor.canonicalInsurer(c.insurance_name);
        if (ins) insurerSet.add(ins);
        if (at && (!lastCommercialAt || at > lastCommercialAt)) lastCommercialAt = at;
      } else if (c.channel === 'medical_insurance') {
        medicalFactIds.push(f.id);
        if (at && (!lastMedicalAt || at > lastMedicalAt)) lastMedicalAt = at;
      }
    }

    const hasCommercial = commercialFactIds.length > 0;
    const hasMedical = medicalFactIds.length > 0;
    if (!hasCommercial && !hasMedical) return null; // 未命中,不打标签

    const insurers = [...insurerSet].sort();
    const fmtYm = (d: Date | null) => (d ? `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}` : null);
    const monthsCommercial = lastCommercialAt ? this.monthsSince(lastCommercialAt, ctx.now) : null;
    const monthsMedical = lastMedicalAt ? this.monthsSince(lastMedicalAt, ctx.now) : null;

    // description:人读自包含;商保优先,把"最近日期"显式写出来(时效判断留给人/scorer)
    const parts: string[] = [];
    if (hasCommercial) {
      const who = insurers.length ? ` · ${insurers.join('、')}` : '';
      const when = fmtYm(lastCommercialAt);
      parts.push(`商保客户${who}${when ? ` · 最近 ${when}（${monthsCommercial}个月前）` : ''}`);
    }
    if (hasMedical) {
      const when = fmtYm(lastMedicalAt);
      parts.push(`医保结算${when ? ` · 最近 ${when}` : ''}`);
    }

    return {
      key: this.key,
      description: parts.join(';'),
      // score = 商保最近月数(越小越新);仅医保时用医保月数兜底。旗标型,非梯度,scorer 读 data 更精确。
      score: monthsCommercial ?? monthsMedical ?? null,
      // 结构化特征值 → persona_features.data 列(给 scorer/UI 结构化消费)
      data: {
        commercialInsured: hasCommercial,
        commercialInsurers: insurers, // 阶段2 re-ingest 后才有保司名
        lastCommercialInsuranceAt: lastCommercialAt ? lastCommercialAt.toISOString() : null,
        monthsSinceLastCommercial: monthsCommercial,
        medicalInsured: hasMedical,
        lastMedicalInsuranceAt: lastMedicalAt ? lastMedicalAt.toISOString() : null,
      },
      evidence: {
        factIds: [...commercialFactIds, ...medicalFactIds],
      },
    };
  }
}
