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

/**
 * lifecycle_stage 生命周期(B.1.4)— 规则层,snapshot
 *
 * 标签:潜客/新客/成长客/成熟客/待激活/沉睡客/流失客。决定运营策略;成长vs成熟用消费斜率区分。
 *
 * 数据字段(spec 用 fact_client_out 的 first/last_visit_time/total_visit_times/net_receipts_this/_total),
 *   但源表无 net_receipts_total/total_visit_times → PAC 自算(同 RFM 口径):
 *     总消费 = Σpayment+recharge−refund(lifetime);近1年消费 = 近365天同口径;
 *     就诊次数 = 去重就诊天数(encounter/actual treatment/挂号);首/末诊 = 就诊事件 min/max。
 *
 * 判定(⚠️ 修正 spec 顺序 bug:原"沉睡>540"在"流失>730"前 → 流失永不触发;此处流失提前):
 *   total=0 且有预约 → 潜客
 *   首诊≤180天 且 就诊≤3 → 新客
 *   末诊≤540 且 年均就诊≥0.5 且 近1年消费 > 历史年均×1.5 → 成长客
 *   末诊≤540 且 年均就诊≥0.5 且 近1年消费 ≤ 历史年均×1.5 → 成熟客
 *   末诊 180–540 且 就诊≥2 → 待激活
 *   末诊>730 → 流失客           (提到沉睡前)
 *   末诊>540 且 就诊≥2 → 沉睡客  (= 540–730)
 *   否则 → 新客(兜底)
 */
@Injectable()
export class LifecycleStageFeatureExtractor implements FeatureExtractor {
  readonly key = PersonaFeatureKey.LIFECYCLE_STAGE;
  private static readonly DAY = 86400_000;

  private sumCents(facts: ActiveFact[], sign: number, since: number | null): number {
    let s = 0;
    for (const f of facts) {
      if (since !== null && (!f.occurredAt || f.occurredAt.getTime() < since)) continue;
      s += sign * Number((f.content as Record<string, unknown>).amount_cents ?? 0);
    }
    return s;
  }

  extract(ctx: FeatureExtractorContext): PersonaFeatureDraft {
    const get = (t: string) => ctx.factsByType.get(t) ?? [];
    const now = ctx.now.getTime();
    const DAY = LifecycleStageFeatureExtractor.DAY;

    // 消费:lifetime / 近1年
    const pays = get('payment_record');
    const recharges = get('recharge_record');
    const refunds = get('refund_record');
    const netTotal =
      this.sumCents(pays, 1, null) + this.sumCents(recharges, 1, null) - this.sumCents(refunds, 1, null);
    const yearAgo = now - 365 * DAY;
    const netThis =
      this.sumCents(pays, 1, yearAgo) + this.sumCents(recharges, 1, yearAgo) - this.sumCents(refunds, 1, yearAgo);

    // 就诊:去重天数 + 首/末
    const visitFacts: ActiveFact[] = [
      ...get(FactType.ENCOUNTER_RECORD),
      ...get(FactType.TREATMENT_RECORD).filter((f) => f.kind === FactKind.ACTUAL),
      ...get(FactType.VISIT_REGISTRATION_RECORD),
    ];
    const days = new Set<string>();
    let first: number | null = null;
    let last: number | null = null;
    for (const f of visitFacts) {
      if (!f.occurredAt) continue;
      const t = f.occurredAt.getTime();
      days.add(f.occurredAt.toISOString().slice(0, 10));
      if (first === null || t < first) first = t;
      if (last === null || t > last) last = t;
    }
    const totalVisits = days.size;
    const hasAppt = get(FactType.APPOINTMENT_RECORD).length > 0;
    // 咨询记录(consultation_record,W7末摄入)— 潜客判定的另一触点(spec:零就诊 + 有预约/咨询)
    const hasConsult = get(FactType.CONSULTATION_RECORD).length > 0;

    const firstDays = first !== null ? Math.floor((now - first) / DAY) : Infinity;
    const lastDays = last !== null ? Math.floor((now - last) / DAY) : Infinity;
    const firstYears = Math.max((first !== null ? now - first : DAY) / (365.25 * DAY), 1 / 365.25);
    const visitsPerYear = totalVisits / firstYears;
    const histYearlySpend = netTotal / firstYears;

    let code: string;
    let zh: string;
    if (totalVisits === 0) {
      // 从未到诊(spec B.1.4):有预约 OR 有咨询 → 潜客(咨询/约过但没来);
      //   都没有(有档无诊无触点)→ 兜底新客(不落 lastDays 分支,避免 Infinity 误判流失)。
      if (hasAppt || hasConsult) {
        code = 'prospect';
        zh = '潜客';
      } else {
        code = 'new';
        zh = '新客';
      }
    } else if (firstDays <= 180 && totalVisits <= 3) {
      code = 'new';
      zh = '新客';
    } else if (lastDays <= 540 && visitsPerYear >= 0.5 && netThis > histYearlySpend * 1.5) {
      code = 'growth';
      zh = '成长客';
    } else if (lastDays <= 540 && visitsPerYear >= 0.5) {
      code = 'mature';
      zh = '成熟客';
    } else if (lastDays > 180 && lastDays <= 540 && totalVisits >= 2) {
      code = 'reactivate';
      zh = '待激活';
    } else if (lastDays > 730) {
      code = 'churned';
      zh = '流失客';
    } else if (lastDays > 540 && totalVisits >= 2) {
      code = 'dormant';
      zh = '沉睡客';
    } else {
      code = 'new';
      zh = '新客'; // 兜底
    }

    const lastStr = last !== null ? `末诊${lastDays}天` : (hasAppt ? '仅预约未诊' : '无就诊');
    return {
      key: this.key,
      description: `${zh} · ${lastStr} · 就诊${totalVisits}次`,
      score: null,
      data: {
        stage: code,
        label: zh,
        firstSeenDays: first !== null ? firstDays : null,
        recencyDays: last !== null ? lastDays : null,
        totalVisits,
        visitsPerYear: Math.round(visitsPerYear * 10) / 10,
        netTotalCents: netTotal,
        netThisCents: netThis,
        hasAppt,
      },
      // 证据 = 驱动分期的就诊 + 消费事实(去重)
      evidence: {
        factIds: [
          ...new Set([
            ...visitFacts.map((f) => f.id),
            ...pays.map((f) => f.id),
            ...recharges.map((f) => f.id),
            ...refunds.map((f) => f.id),
          ]),
        ],
      },
    };
  }
}
