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

/**
 * time_preference 时间偏好(D.1.2)— 规则层,snapshot · 多标签(允许并列)
 *
 * 标签:工作日偏好 / 周末偏好 / 上午偏好 / 下午偏好 / 晚间偏好。习惯就诊时间,优化推送与排班。
 *
 * 数据源:appointment_record.planned_for(= 预约槽时刻;北京 TZ 下是干净营业钟形 8-18)。
 *   ⚠️ occurred_at 小时不可靠(时区/解析乱),用 planned_for;近2年(730d)。
 *   ⚠️ TZ 硬编北京(+8)— 试点单宿主;多宿主应读 host 时区(follow-up)。
 *
 * 算法(图):近2年记录提取星期+钟点(记录数≥2):
 *   工作日占比≥60%→工作日偏好;周末≥50%→周末偏好;
 *   上午(8-12)≥50%→上午;下午(12-18)≥50%→下午;晚间(18-21)≥50%→晚间。
 *   无命中或记录<2→不打标签。
 */
@Injectable()
export class TimePreferenceFeatureExtractor implements FeatureExtractor {
  readonly key = PersonaFeatureKey.TIME_PREFERENCE;
  private static readonly TZ_OFFSET_MS = 8 * 3600_000; // 北京 UTC+8
  private static readonly WINDOW_MS = 730 * 86400_000;

  extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null {
    const appts = (ctx.factsByType.get(FactType.APPOINTMENT_RECORD) ?? []) as ActiveFact[];
    const since = ctx.now.getTime() - TimePreferenceFeatureExtractor.WINDOW_MS;

    let total = 0;
    let weekday = 0;
    let weekend = 0;
    let morning = 0;
    let afternoon = 0;
    let evening = 0;
    let periodTotal = 0; // 落在 8-21 的记录(算时段占比的分母)
    const factIds: string[] = [];
    for (const f of appts) {
      const at = f.plannedFor ?? f.occurredAt;
      if (!at || at.getTime() < since) continue;
      total++;
      factIds.push(f.id);
      const bj = new Date(at.getTime() + TimePreferenceFeatureExtractor.TZ_OFFSET_MS);
      const dow = bj.getUTCDay(); // 0=周日..6=周六
      if (dow === 0 || dow === 6) weekend++;
      else weekday++;
      const h = bj.getUTCHours();
      if (h >= 8 && h < 12) {
        morning++;
        periodTotal++;
      } else if (h >= 12 && h < 18) {
        afternoon++;
        periodTotal++;
      } else if (h >= 18 && h < 21) {
        evening++;
        periodTotal++;
      }
    }
    if (total < 2) return null;

    const codes: string[] = [];
    const labels: string[] = [];
    const add = (c: string, z: string) => {
      codes.push(c);
      labels.push(z);
    };
    if (weekday / total >= 0.6) add('weekday', '工作日偏好');
    if (weekend / total >= 0.5) add('weekend', '周末偏好');
    if (periodTotal >= 2) {
      if (morning / periodTotal >= 0.5) add('morning', '上午偏好');
      if (afternoon / periodTotal >= 0.5) add('afternoon', '下午偏好');
      if (evening / periodTotal >= 0.5) add('evening', '晚间偏好');
    }
    if (codes.length === 0) return null;

    const pct = (n: number, d: number) => (d > 0 ? Math.round((100 * n) / d) : 0);
    return {
      key: this.key,
      description: labels.join(' / '),
      score: null,
      data: {
        types: codes,
        labels,
        recordCount: total,
        weekdayPct: pct(weekday, total),
        weekendPct: pct(weekend, total),
        morningPct: pct(morning, periodTotal),
        afternoonPct: pct(afternoon, periodTotal),
        eveningPct: pct(evening, periodTotal),
      },
      evidence: { factIds },
    };
  }

  /**
   * 近 2 年滑动窗口:窗内最早那条预约滑出去时,占比(甚至"记录数 ≥2"这个门槛)就会变。
   */
  nextBoundaryAt(ctx: FeatureExtractorContext): Date | null {
    const appts = (ctx.factsByType.get(FactType.APPOINTMENT_RECORD) ?? []) as ActiveFact[];
    return nextWindowExpiry(
      appts.map((f) => f.plannedFor ?? f.occurredAt),
      730,
      ctx.now,
    );
  }

}
