import { Injectable } from '@nestjs/common';
import { PersonaFeatureKey, FactType } from '@pac/types';
import type {
  ActiveFact,
  FeatureExtractor,
  FeatureExtractorContext,
  PersonaFeatureDraft,
} from './feature.interface';
import { visitFactsOf, visitSpanOf } from './visit-facts';
import { earliestFuture, nextDayBoundary } from './time-boundary';

/**
 * 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);

    // 就诊:去重天数 + 首/末(口径见 visit-facts.ts,与 rfm / urgency_level 同源)
    const visitFacts = visitFactsOf(ctx);
    const span = visitSpanOf(visitFacts);
    const first = span.first?.getTime() ?? null;
    const last = span.last?.getTime() ?? null;
    const totalVisits = span.dayCount;
    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),
          ]),
        ],
      },
    };
  }

  /**
   * 分期的天数档界:末诊 180 / 540 / 730(待激活 → 沉睡 → 流失),首诊 180(新客窗口到期)。
   *
   * 未覆盖:近 1 年消费 vs 历史年均(成长客 ↔ 成熟客)是连续量,没有确切跨越时刻 ——
   * 那条边界只能等下一笔消费事实(fact 水位会管)。这里只报能算准的。
   */
  nextBoundaryAt(ctx: FeatureExtractorContext): Date | null {
    const { first, last } = visitSpanOf(visitFactsOf(ctx));
    return earliestFuture(
      ctx.now,
      nextDayBoundary(last, ctx.now, [180, 540, 730]),
      nextDayBoundary(first, ctx.now, [180]),
    );
  }
}
