import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { refreshLabelsSql, countMissingLabelsSql, LABEL_REFRESH_BATCH } from './plan-label.sql';

interface BatchResult {
  seen: number;
  written: number;
  maxId: string | null;
}

/**
 * `plan_reasons.potential_labels` 的刷新器 —— 初选矩阵的提速全靠这一列。
 *
 * 两种跑法：
 *   · `backfillMissing()` —— 只补没算过的（NULL）。上线回填、以及新写入行的兜底。
 *   · `refreshAll()`      —— 连算过的一起重算。⚠️ 标签含年龄规则，**不重算就会逐日漂**。
 *
 * ⛔ 两者都**不在请求路径上**：它们扫全表，得由 CLI / 定时任务跑。
 */
@Injectable()
export class PlanLabelService {
  private readonly logger = new Logger(PlanLabelService.name);

  constructor(private readonly prisma: PrismaService) {}

  /** 还有多少条没算过 —— 回填收尾核对、以及"刷新任务是不是在漏人"的体检项。 */
  async countMissing(): Promise<number> {
    const [row] = await this.prisma.$queryRaw<Array<{ n: number }>>(countMissingLabelsSql);
    return row?.n ?? 0;
  }

  /** 只补没算过的。返回写了多少行。 */
  async backfillMissing(batch = LABEL_REFRESH_BATCH): Promise<number> {
    return this.run({ onlyMissing: true, batch, label: '回填' });
  }

  /** 全量重算（年龄会变）。返回写了多少行。 */
  async refreshAll(batch = LABEL_REFRESH_BATCH): Promise<number> {
    return this.run({ onlyMissing: false, batch, label: '重算' });
  }

  private async run(o: { onlyMissing: boolean; batch: number; label: string }): Promise<number> {
    let afterId: string | null = null;
    let written = 0;
    let seenTotal = 0;
    for (;;) {
      const rows: BatchResult[] = await this.prisma.$queryRaw<BatchResult[]>(
        refreshLabelsSql({ afterId, onlyMissing: o.onlyMissing, batch: o.batch }),
      );
      const r = rows[0];
      const seen = r?.seen ?? 0;
      if (seen === 0) break;
      written += r?.written ?? 0;
      seenTotal += seen;
      /**
       * 🔴 游标按 `maxId` 推进，⛔ 不按"写了几行" ——
       *   全量重算时绝大多数行算出来跟原值一样、不会被写，此时 written=0
       *   但**并不是做完了**。拿 written 当停止条件 = 第一批之后就静默罢工。
       */
      afterId = r?.maxId ?? null;
      if (!afterId) break;
      if (seen < o.batch) break; // 最后一批
    }
    this.logger.log(`${o.label}完成：看过 ${seenTotal} 条，写入 ${written} 条`);
    return written;
  }
}
