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

/**
 * special_attention 特别关注(D.2.3)— 规则层,snapshot · 多标签
 *
 * 标签:屡次爽约 / 经常迟到 / 免打扰 / 不可等候。一线服务需特别注意,影响排班与触达。
 *
 * 算法(近1年):
 *   屡次爽约:履约率 = 到诊/(到诊+爽约) < 50% 且 决定数(到诊+爽约)≥3。
 *     到诊 = completed/arrived/in_treatment;爽约 = no_show;cancelled/rescheduled/scheduled 不计分母。
 *   经常迟到:有到店时间的预约中,迟到(arrived_at > planned_for+15min)占比≥50% 且 ≥3 次。
 *   免打扰:profile.doNotContact=true(客户明确标注/客服标记;复用现有合规字段)。
 *   不可等候:notes/tags/病历(emr)含 不可等候/时间敏感/赶时间/不能等。
 *   全不命中 → 不打标签。
 */
@Injectable()
export class SpecialAttentionFeatureExtractor implements FeatureExtractor {
  readonly key = PersonaFeatureKey.SPECIAL_ATTENTION;
  private static readonly DAY = 86400_000;
  private static readonly LATE_MS = 15 * 60_000;
  // ⚠️ arrived_at(in_time)摄入时区 bug:比 planned_for 一致早 8h(实测 -480min±实际早晚)。
  //   +8h 补偿对齐 planned_for(UTC)。根治应修摄入 in_time 时区(follow-up,同 occurred_at bug)。
  private static readonly ARRIVED_TZ_FIX_MS = 8 * 3600_000;
  private static readonly ATTENDED = new Set(['completed', 'arrived', 'in_treatment']);
  private static readonly WAIT_KW = ['不可等候', '时间敏感', '赶时间', '不能等'];

  extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null {
    const now = ctx.now.getTime();
    const yearAgo = now - 365 * SpecialAttentionFeatureExtractor.DAY;
    // 用 appointmentsAll(各状态;factsByType 只 active/fulfilled,no_show 预约不在其中)
    const appts = (ctx.appointmentsAll ?? []) as ActiveFact[];

    // 近1年预约:履约 / 爽约 / 迟到
    let attended = 0;
    let noShow = 0;
    let arrivalRecs = 0;
    let lateRecs = 0;
    for (const f of appts) {
      const c = (f.content ?? {}) as Record<string, unknown>;
      const planned = f.plannedFor ?? null;
      if (!planned || planned.getTime() < yearAgo) continue;
      const status = String(c.status ?? '');
      if (SpecialAttentionFeatureExtractor.ATTENDED.has(status)) attended++;
      else if (status === 'no_show') noShow++;
      // 迟到:到店时间(+8h 修正摄入 TZ bug)vs 预约时间
      const arrRaw = c.arrived_at;
      if (arrRaw) {
        const arr = new Date(String(arrRaw)).getTime() + SpecialAttentionFeatureExtractor.ARRIVED_TZ_FIX_MS;
        if (Number.isFinite(arr)) {
          arrivalRecs++;
          if (arr - planned.getTime() > SpecialAttentionFeatureExtractor.LATE_MS) lateRecs++;
        }
      }
    }
    const decided = attended + noShow;

    const codes: string[] = [];
    const labels: string[] = [];
    const add = (c: string, z: string) => {
      codes.push(c);
      labels.push(z);
    };
    if (decided >= 3 && attended / decided < 0.5) add('frequent_no_show', '屡次爽约');
    if (arrivalRecs >= 3 && lateRecs / arrivalRecs >= 0.5) add('often_late', '经常迟到');
    if (ctx.profile?.doNotContact) add('do_not_disturb', '免打扰');

    // 不可等候:notes / tags / 病历自由文本关键词
    const emrText = (ctx.factsByType.get(FactType.EMR_RECORD) ?? [])
      .map((f) => {
        const c = (f.content ?? {}) as Record<string, unknown>;
        return [c.illness_desc, c.diagnosis_text, c.treatment_plan, c.doctor_advice, c.notes].join(' ');
      })
      .join(' ');
    const hay = `${ctx.profile?.notes ?? ''} ${(ctx.profile?.tags ?? []).join(' ')} ${emrText}`;
    if (SpecialAttentionFeatureExtractor.WAIT_KW.some((k) => hay.includes(k))) add('cannot_wait', '不可等候');

    if (codes.length === 0) return null;
    return {
      key: this.key,
      description: labels.join(' / '),
      score: null,
      data: {
        types: codes,
        labels,
        fulfillRate: decided > 0 ? Math.round((100 * attended) / decided) : null,
        apptDecided: decided,
        lateRate: arrivalRecs > 0 ? Math.round((100 * lateRecs) / arrivalRecs) : null,
        arrivalRecs,
      },
      evidence: { factIds: [] },
    };
  }
}
