/**
 * Recompute Plans CLI — 批量跑 PlanEngine,产 FollowupPlan + PlanReason
 *
 * Usage:
 *   pnpm recompute-plans                   # 默认 host=demo,全量
 *   pnpm recompute-plans -- --host=friday
 *   pnpm recompute-plans -- --host=jvs-dw --pids=998421,xxx   # 只重算指定 externalId(定向,O(子集))
 *   pnpm recompute-plans -- --host=jvs-dw --clinics=<orgId>,<orgId>  # 按诊所补摄后定向重算
 *
 * ⚠️ **--clinics 只用于「按诊所补摄后立刻补计划」**,不能拿它替代日常全量。
 *    召回是**时间驱动**的:数据一个字没变,沉默时长跨过阈值也该出计划 —— 收窄到几家诊所
 *    就等于其余诊所当天不评估。全量那轮(定时任务)照跑,这个参数只是省掉补摄后的那次全扫。
 */
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { AppModule } from '../app.module';
import { PlanEngineService } from '../modules/plan/engine/plan-engine.service';
import { PrismaService } from '../prisma/prisma.service';

interface Args {
  host: string;
  pids?: string[]; // 指定 externalId(逗号分隔)→ 只重算这些患者(定向,recomputeForPatient)
  clinics?: string[]; // --clinics=X,Y:只重算"在这些诊所有过操作"的患者(批量路径收窄,非逐患者)
}

function parseArgs(argv: string[]): Args {
  const args: Args = { host: 'demo' };
  for (const a of argv) {
    if (a.startsWith('--host=')) args.host = a.slice('--host='.length);
    else if (a.startsWith('--pids=')) {
      args.pids = a.slice('--pids='.length).split(',').map((s) => s.trim()).filter(Boolean);
    } else if (a.startsWith('--clinics=')) {
      args.clinics = a.slice('--clinics='.length).split(',').map((s) => s.trim()).filter(Boolean);
    }
  }
  return args;
}

async function bootstrap() {
  const args = parseArgs(process.argv.slice(2));
  const logger = new Logger('recompute-plans');
  // 连接池对齐本进程的写并发(PlanEngine 的 upsert 段用 PAC_PLAN_BATCH_CONCURRENCY,默认 8)。
  // 必须在 NestFactory 之前设 —— PrismaService 在容器初始化时就定池,晚了不生效。
  // 不设的话池是 Prisma 默认「核数×2+1」(生产 4 核 = 9),把 batch 并发调到 9 以上就会被卡住。
  // 详见 prisma.service.ts 的 withCohortDerivedPool。
  if (!process.env.PAC_DB_CONCURRENCY) {
    const n = Math.max(1, Number(process.env.PAC_PLAN_BATCH_CONCURRENCY) || 8);
    if (n > 1) process.env.PAC_DB_CONCURRENCY = String(n);
  }
  const app = await NestFactory.createApplicationContext(AppModule, {
    logger: ['log', 'warn', 'error'],
  });

  try {
    const prisma = app.get(PrismaService);
    const engine = app.get(PlanEngineService);

    const host = await prisma.host.findUnique({ where: { name: args.host } });
    if (!host) throw new Error(`Host '${args.host}' not found`);

    // ── 定向模式:只重算指定 externalId 的患者(recomputeForPatient,O(子集))──
    if (args.pids?.length) {
      const patients = await prisma.patient.findMany({
        where: { hostId: host.id, externalId: { in: args.pids } },
        select: { id: true, tenantId: true, externalId: true },
      });
      logger.log(`▶ 定向重算 ${patients.length} patients(host=${args.host})...`);
      let created = 0, closed = 0, failed = 0;
      for (const p of patients) {
        try {
          const r = await engine.recomputeForPatient({ hostId: host.id, tenantId: p.tenantId, patientId: p.id });
          created += r.plansCreated;
          closed += r.plansClosed;
        } catch (err) {
          failed++;
          logger.error(`  ${p.externalId} FAILED: ${err instanceof Error ? err.message : err}`);
        }
      }
      logger.log(`Done(定向):patients=${patients.length} plansCreated=${created} plansClosed=${closed} failed=${failed}`);
      return;
    }

    // ── --clinics:把本轮收窄到"在这些诊所有过操作(patient_transactions.clinic_id)"的患者 ──
    //   与 cold-import / recompute-persona 的 --clinics **同一口径**,用于按诊所补摄后只补这批人的
    //   计划,免掉全 host 全扫(生产实测全量单轮 ≈1h50m,其中 ~1h43m 花在 selectHits 全表扫)。
    let clinicPatientIds: string[] | undefined;
    if (args.clinics?.length) {
      const rows = await prisma.patientTransaction.findMany({
        where: { hostId: host.id, clinicId: { in: args.clinics } },
        select: { patientId: true },
        distinct: ['patientId'],
      });
      const ids = rows.map((r) => r.patientId).filter((x): x is string => !!x);
      logger.log(`--clinics=${args.clinics.join(',')} → 命中 ${ids.length} 位患者(按诊所收窄)`);
      if (ids.length === 0) throw new Error('--clinics 未命中任何患者(诊所 id 是否正确 / 是否已摄入?)');
      clinicPatientIds = ids;
    }

    // 取该 host 第一个 tenant(demo 场景固定一个)
    const tenants = await prisma.patient.findMany({
      where: { hostId: host.id },
      select: { tenantId: true },
      distinct: ['tenantId'],
    });
    if (tenants.length === 0) throw new Error('No tenants found for host');

    for (const t of tenants) {
      logger.log(
        `▶ Running engine for host=${args.host} tenant=${t.tenantId}` +
          `${clinicPatientIds ? ` (子集:${clinicPatientIds.length} 位患者)` : ' (全量)'} ...`,
      );
      const r = await engine.runAllForHost({
        hostId: host.id,
        tenantId: t.tenantId,
        ...(clinicPatientIds ? { patientIds: clinicPatientIds } : {}),
      });
      logger.log(`──────────────────────────────────────────────────────`);
      logger.log(`Result(${t.tenantId}):`);
      logger.log(`  scenariosRun:         ${r.scenariosRun}`);
      logger.log(`  patientsHit:          ${r.patientsHit}`);
      logger.log(`  plansCreated:         ${r.plansCreated}`);
      logger.log(`  plansSuperseded:      ${r.plansSuperseded}`);
      logger.log(`  plansUnchanged:       ${r.plansUnchanged}`);
      logger.log(`  plansSkippedAssigned: ${r.plansSkippedAssigned}`);
      logger.log(`  plansSuppressed:      ${r.plansSuppressed}`);
      logger.log(`  plansClosed:          ${r.plansClosed}`);
      logger.log(`  duration:             ${r.durationMs}ms`);
      logger.log(`──────────────────────────────────────────────────────`);
    }
  } catch (err) {
    new Logger('recompute-plans').error(err instanceof Error ? err.stack : String(err));
    process.exitCode = 2;
  } finally {
    await app.close();
  }
}

bootstrap().catch((err) => {
  console.error(err);
  process.exit(3);
});
