/**
 * Seed Assignment CLI —— 造一批「已经跑完的」分配批次。
 *
 * ⚠️⚠️⚠️ **用途只有一个:验工程。** ⚠️⚠️⚠️
 *
 * 可以拿它验:
 *   · 跟踪报表的 SQL 会不会超时、查询计划走没走索引
 *   · 分子分母口径对不对(agentStats 之和 = planned、退回率两个数)
 *   · `n < 50` 时"样本不足"判定真的生效(不出百分比、不画图)
 *   · 退回原因聚合有没有漏掉 `event='release'` 过滤
 *     (不过滤会把 auto_release 的 timeout/assignment_expired 和 feedback 的 up/down 混进来)
 *   · 边界:全退回 / 全到期 / 零转化 / 单人独吞
 *
 * ⛔⛔ **绝对不许拿它的数去调任何默认值**(容量 50、时限 3 天、专属优先策略、批次规模 100)。
 *   T20 的自优化要的是"真实客服在真实压力下怎么反应",而这里的每一个数都是我按
 *   `--release-rate` 之类的参数**编出来的** —— 拿它反推 = 自己教自己。
 *   而且比没有数据更危险:假信号会驱动你真的去改策略,然后错的策略顶着"数据支持"的名义跑起来。
 *   真规律只能让真人跑真批次,没有替代品。
 *
 * Usage:
 *   pnpm seed-assignment -- --host=jvs-dw --n=200
 *   pnpm seed-assignment -- --host=jvs-dw --n=60 --release-rate=0.3 --expire-rate=0.2
 *   pnpm seed-assignment -- --host=jvs-dw --clean            # 只清理,不造
 */
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import {
  AssignStrategy,
  PlanEventReason,
  PlanEventType,
  ReleaseReason,
  releaseReasonsForForm,
} from '@pac/types';
import { AppModule } from '../app.module';
import { PrismaService } from '../prisma/prisma.service';

interface Args {
  host: string;
  n: number;
  /** 分下去之后被客服带原因退回的比例 */
  releaseRate: number;
  /** 到期被系统收走的比例 */
  expireRate: number;
  /** 客服打开过详情页(view 事件)的比例 —— 「动过没」的唯一可用判据 */
  viewRate: number;
  clean: boolean;
}

function parseArgs(argv: string[]): Args {
  const a: Args = { host: 'jvs-dw', n: 200, releaseRate: 0.15, expireRate: 0.1, viewRate: 0.6, clean: false };
  for (const s of argv) {
    if (s.startsWith('--host=')) a.host = s.slice(7);
    else if (s.startsWith('--n=')) a.n = Number(s.slice(4));
    else if (s.startsWith('--release-rate=')) a.releaseRate = Number(s.slice(15));
    else if (s.startsWith('--expire-rate=')) a.expireRate = Number(s.slice(14));
    else if (s.startsWith('--view-rate=')) a.viewRate = Number(s.slice(12));
    else if (s === '--clean') a.clean = true;
  }
  return a;
}

/**
 * 确定性伪随机 —— ⛔ 不用 Math.random():
 * 造完一批发现报表不对,得能**原样重造一遍**再看,否则每次数据都不一样,根本没法排查。
 */
function mulberry32(seed: number): () => number {
  let t = seed;
  return () => {
    t = (t + 0x6d2b79f5) | 0;
    let r = Math.imul(t ^ (t >>> 15), 1 | t);
    r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
    return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
  };
}

async function main(): Promise<void> {
  const logger = new Logger('SeedAssignment');
  const args = parseArgs(process.argv.slice(2));
  const app = await NestFactory.createApplicationContext(AppModule, { logger: ['warn', 'error'] });
  const prisma = app.get(PrismaService);

  const host = await prisma.host.findFirst({ where: { name: args.host }, select: { id: true } });
  if (!host) throw new Error(`Host not found by name=${args.host}`);

  // ── 清理:seed 造的批次一律带 SEED_ 前缀的 requestId,只清自己造的 ──
  const seeded = await prisma.planAssignment.findMany({
    where: { hostId: host.id, requestId: { startsWith: 'SEED_' } },
    select: { id: true },
  });
  if (seeded.length > 0) {
    const ids = seeded.map((s) => s.id);
    const plans = await prisma.followupPlan.findMany({
      where: { assignmentId: { in: ids } },
      select: { id: true },
    });
    await prisma.planEventLog.deleteMany({ where: { planId: { in: plans.map((p) => p.id) } } });
    await prisma.followupPlan.updateMany({
      where: { assignmentId: { in: ids } },
      data: {
        assignmentId: null, assignedBy: null, assignStrategy: null, assignmentExpiresAt: null,
        dedicatedCsAtAssign: null, dedicatedCsLastVisitAt: null, priorityScoreAtAssign: null,
        sourceConfidenceAtAssign: null, selectionMode: null,
        releaseReason: null, releaseNote: null,
        status: 'active', assigneeUserId: null, assignedAt: null, recycleAt: null,
      },
    });
    await prisma.planAssignment.deleteMany({ where: { id: { in: ids } } });
    logger.log(`已清理 ${seeded.length} 个 seed 批次`);
  }
  if (args.clean) {
    await app.close();
    return;
  }

  // ── 取候选:未分配的活跃 plan ────────────────────────────────
  const pool = await prisma.followupPlan.findMany({
    where: { hostId: host.id, status: 'active', assigneeUserId: null, supersededAt: null },
    orderBy: { priorityScore: 'desc' },
    take: args.n,
    select: { id: true, tenantId: true, patientId: true, targetClinicId: true, priorityScore: true },
  });
  if (pool.length === 0) throw new Error('召回池里没有可用候选 —— 先跑 recompute-plans');
  const clinicId = pool[0]!.targetClinicId;
  const tenantId = pool[0]!.tenantId;
  const rows = pool.filter((p) => p.targetClinicId === clinicId);

  // 名册里挑几个真客服(用真 id,报表里的姓名解析才走得通)
  const roster = await prisma.patientReturnVisit.groupBy({
    by: ['taskDirectorId'],
    where: { hostId: host.id, tenantId, clinicId, taskDirectorId: { not: null } },
    _count: { _all: true },
    orderBy: { _count: { taskDirectorId: 'desc' } },
    take: 6,
  });
  const agents = roster.map((r) => r.taskDirectorId!).filter(Boolean);
  if (agents.length === 0) throw new Error('该诊所没有回访记录,取不到客服 id');

  const rand = mulberry32(20260802);
  const now = new Date();
  const assignedAt = new Date(now.getTime() - 5 * 86400_000); // 5 天前分的
  const expiresAt = new Date(now.getTime() - 2 * 86400_000); // 2 天前就到期了

  const batch = await prisma.planAssignment.create({
    data: {
      hostId: host.id,
      tenantId,
      clinicId,
      createdBy: agents[0]!,
      requestId: `SEED_${randomUUID()}`,
      criteria: { seeded: true, note: '⚠️ 造的数据,只验工程,不得用于反推规律' },
      attributes: { benefit: { text: '8 月种植体检免费(seed)' } },
      expiresAt,
      status: 'confirmed',
    },
    select: { id: true },
  });

  // ⚠️ 2026-08-05 起 releaseReasonsForForm() 本身已不含 'other'(它退成历史值),
  //   所以这里不用再过滤 —— 留着 filter 反而会让人以为 other 还在候选里。
  const releaseOptions = releaseReasonsForForm();
  let released = 0;
  let expired = 0;
  let viewed = 0;

  for (let i = 0; i < rows.length; i++) {
    const p = rows[i]!;
    const agent = agents[i % agents.length]!;
    const strategy =
      i % 10 < 7
        ? AssignStrategy.DEDICATED
        : i % 10 < 9
          ? AssignStrategy.SPREAD_OVERFLOW
          : AssignStrategy.SPREAD_NO_DEDICATED;

    const roll = rand();
    const isReleased = roll < args.releaseRate;
    const isExpired = !isReleased && roll < args.releaseRate + args.expireRate;
    const isViewed = rand() < args.viewRate;

    await prisma.$transaction(async (tx) => {
      await tx.followupPlan.update({
        where: { id: p.id },
        data: {
          assignmentId: batch.id,
          assignedBy: agents[0]!,
          assignStrategy: strategy,
          priorityScoreAtAssign: p.priorityScore,
          sourceConfidenceAtAssign: 1,
          selectionMode: rand() < 0.1 ? 'explore' : 'rank',
          dedicatedCsAtAssign: strategy === AssignStrategy.DEDICATED ? agent : null,
          // ⚠️ 三种终态互斥,列的组合必须自洽,否则验出来的报表口径是假的:
          //   退回 → active + 有 releaseReason + 无期限
          //   到期 → active + 无 releaseReason + 无期限(系统收的,不是客服退的)
          //   在手 → assigned + 有期限
          ...(isReleased
            ? {
                status: 'active', assigneeUserId: null, assignedAt: null, assignmentExpiresAt: null,
                releaseReason: releaseOptions[i % releaseOptions.length]!,
              }
            : isExpired
              ? { status: 'active', assigneeUserId: null, assignedAt: null, assignmentExpiresAt: null }
              : { status: 'assigned', assigneeUserId: agent, assignedAt, assignmentExpiresAt: expiresAt }),
        },
      });

      // 分配事件(每条都有)
      await tx.planEventLog.create({
        data: {
          hostId: host.id, tenantId, planId: p.id, patientId: p.patientId,
          event: PlanEventType.ASSIGN, assigneeUserId: agent, actorUserId: agents[0]!,
          createdAt: assignedAt,
        },
      });
      // 「动过没」的唯一可用判据 —— plan_executions 回写率仅 11%,拿它判会把 89% 打过电话的算成没动
      if (isViewed) {
        await tx.planEventLog.create({
          data: {
            hostId: host.id, tenantId, planId: p.id, patientId: p.patientId,
            event: PlanEventType.VIEW, actorUserId: agent,
            createdAt: new Date(assignedAt.getTime() + 3600_000),
          },
        });
        viewed++;
      }
      if (isReleased) {
        await tx.planEventLog.create({
          data: {
            hostId: host.id, tenantId, planId: p.id, patientId: p.patientId,
            event: PlanEventType.RELEASE, assigneeUserId: null, actorUserId: agent,
            reason: releaseOptions[i % releaseOptions.length]!,
            heldSeconds: 86400, createdAt: new Date(assignedAt.getTime() + 86400_000),
          },
        });
        released++;
      }
      if (isExpired) {
        // ⚠️ 到期是**系统**收的:event=auto_release、actor 为空、⛔ 不写 release_reason。
        //   混进 release_reason 会让退回率的分子分母一起虚高 —— 这正是要验的口径之一。
        await tx.planEventLog.create({
          data: {
            hostId: host.id, tenantId, planId: p.id, patientId: p.patientId,
            event: PlanEventType.AUTO_RELEASE, assigneeUserId: null, actorUserId: null,
            reason: PlanEventReason.ASSIGNMENT_EXPIRED,
            heldSeconds: 3 * 86400, createdAt: expiresAt,
          },
        });
        expired++;
      }
    });
  }

  logger.warn(
    `[SEED · 造的数据] 批次 ${batch.id.slice(0, 8)}:${rows.length} 条 / ${agents.length} 位客服 —— ` +
      `退回 ${released} · 到期 ${expired} · 打开过 ${viewed}。` +
      `⛔ 只用于验报表口径与性能,**不得据此调整任何默认值**。`,
  );
  await app.close();
}

void main().catch((e) => {
  console.error(e);
  process.exit(1);
});
