/**
 * Recompute Persona CLI
 *
 * 批量重算指定 host(默认 demo)所有 patient 的 Persona,产新版本。
 *
 * Usage:
 *   pnpm recompute-persona                   # 默认 host=demo,所有 active patient
 *   pnpm recompute-persona -- --host=friday  # 指定 host
 *   pnpm recompute-persona -- --pid=P001     # 单 patient(host 内 externalId)
 */
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { AppModule } from '../app.module';
import { PersonaService } from '../modules/persona/persona.service';
import { PrismaService } from '../prisma/prisma.service';

interface Args {
  host: string;
  pid?: string;
  pids?: string[]; // 多个 externalId(逗号分隔),定向重算受影响子集用
  force?: boolean; // 跳过水位幂等闸:算法/特征变更后(数据没变)也强制重算
  concurrency: number; // 并发度:每患者 recompute 含 ~15 次 DB 往返,并发可重叠 I/O 等待
  shard?: { i: number; k: number }; // 分片 i/k:多进程并行(吃多核),按患者序号取模
}

function parseArgs(argv: string[]): Args {
  const args: Args = { host: 'demo', concurrency: 1 };
  for (const a of argv) {
    if (a.startsWith('--host=')) args.host = a.slice('--host='.length);
    else if (a.startsWith('--pid=')) args.pid = a.slice('--pid='.length);
    else if (a === '--force') args.force = true;
    else if (a.startsWith('--pids=')) {
      args.pids = a.slice('--pids='.length).split(',').map((s) => s.trim()).filter(Boolean);
    } else if (a.startsWith('--concurrency=')) {
      const n = parseInt(a.slice('--concurrency='.length), 10);
      if (Number.isFinite(n) && n >= 1) args.concurrency = n;
    } else if (a.startsWith('--shard=')) {
      const m = a.slice('--shard='.length).match(/^(\d+)\/(\d+)$/);
      if (m) args.shard = { i: Number(m[1]), k: Number(m[2]) };
    }
  }
  return args;
}

async function bootstrap() {
  const args = parseArgs(process.argv.slice(2));
  const logger = new Logger('recompute-persona');
  const app = await NestFactory.createApplicationContext(AppModule, {
    logger: ['log', 'warn', 'error'],
  });

  try {
    const prisma = app.get(PrismaService);
    const svc = app.get(PersonaService);

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

    let patients = await prisma.patient.findMany({
      where: {
        hostId: host.id,
        active: true,
        ...(args.pids?.length ? { externalId: { in: args.pids } } : args.pid ? { externalId: args.pid } : {}),
      },
      select: { id: true, externalId: true, name: true },
      orderBy: { id: 'asc' }, // 稳定序,供分片按下标取模(各进程不重不漏)
    });
    if (args.shard) {
      const { i, k } = args.shard;
      patients = patients.filter((_, idx) => idx % k === i);
    }

    const total = patients.length;
    logger.log(
      `Recomputing ${total} patients for host=${args.host} ` +
        `(concurrency=${args.concurrency}${args.shard ? `, shard=${args.shard.i}/${args.shard.k}` : ''}) ...`,
    );

    // noop = 水位闸命中(自上次重算无新事件,沿用旧 persona),是正常结果,不并入 failed。
    let ok = 0, partial = 0, noop = 0, failed = 0, done = 0;
    const verbose = total <= 500; // 小批逐条日志;大批只打进度,免刷屏
    const runOne = async (p: { id: string; externalId: string; name: string | null }) => {
      try {
        const r = await svc.recompute({ patientId: p.id, source: `manual:cli`, force: args.force });
        if (verbose) {
          logger.log(
            `  ${p.externalId.padEnd(6)} ${(p.name ?? '').padEnd(12)} v${r.version} features=${r.featureCount} status=${r.status} (${r.durationMs}ms)`,
          );
        }
        if (r.status === 'success') ok++;
        else if (r.status === 'partial') partial++;
        else if (r.status === 'noop') noop++;
        else failed++;
      } catch (err) {
        failed++;
        logger.error(`  ${p.externalId} FAILED: ${err instanceof Error ? err.message : err}`);
      }
      if (!verbose && ++done % 2000 === 0) logger.log(`  …进度 ${done}/${total} (failed=${failed})`);
    };

    // 并发分块:每患者 recompute 含 ~15 次串行 DB 往返(I/O 等待为主),
    // 单进程内并发可重叠这些等待;CPU 占比高的部分再靠多进程 --shard 吃多核。
    for (let i = 0; i < patients.length; i += args.concurrency) {
      await Promise.all(patients.slice(i, i + args.concurrency).map(runOne));
    }

    logger.log('─────────────────────────────────');
    logger.log(`Done: success=${ok} partial=${partial} noop=${noop} failed=${failed}`);
  } catch (err) {
    new Logger('recompute-persona').error(err instanceof Error ? err.stack : String(err));
    process.exitCode = 2;
  } finally {
    await app.close();
  }
}

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