/**
 * Sync Incremental CLI(W4 末)
 *
 * 从上次 sync_logs.cursor_after 增量拉 DW 数据,跑完触发 persona + plan recompute。
 *
 * 链路:
 *   1. ColdImportService.importDirectory({ incremental: true })
 *      - 读 sync_logs[host=jvs-dw, resource=incremental_bundle].cursor_after 拿上次 cursor
 *      - SQL 注入 WHERE updated_date > '${cursor}'(per-query)
 *      - 跑完写新 sync_log + cursor_after JSON
 *   2. 收集本批 affected patient_ids
 *   3. 对每个 affected patient 跑 PersonaService.recompute(等价 BullMQ 触发,本地同步跑)
 *   4. 跑 PlanEngineService.runHost(SQL 召回 + 6 因子打分,生成/更新 followup_plans)
 *
 * 用法:
 *   pnpm sync-incremental -- --dir=./data/jvs-dw                 # 增量 + 联动 persona/plan
 *   pnpm sync-incremental -- --dir=./data/jvs-dw --no-recompute  # 只拉数据不联动重算(debug)
 *   pnpm sync-incremental -- --dir=./data/jvs-dw --dry-run       # 不写库(测试 cursor 注入是否正确)
 *
 * 部署:BullMQ cron 每天 02:00 触发(DW 那边每日刷新后)
 */

import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { AppModule } from '../app.module';
import { ColdImportService } from '../modules/sync/cold-import/cold-import.service';
import { PersonaService } from '../modules/persona/persona.service';
import { PlanEngineService } from '../modules/plan/engine/plan-engine.service';
import { PrismaService } from '../prisma/prisma.service';

interface CliArgs {
  dir?: string;
  dryRun: boolean;
  noRecompute: boolean;
  help: boolean;
}

function parseArgs(argv: string[]): CliArgs {
  const out: CliArgs = { dryRun: false, noRecompute: false, help: false };
  for (const a of argv) {
    if (a === '--help' || a === '-h') out.help = true;
    else if (a === '--dry-run') out.dryRun = true;
    else if (a === '--no-recompute') out.noRecompute = true;
    else if (a.startsWith('--dir=')) out.dir = a.slice('--dir='.length);
  }
  return out;
}

function printHelp() {
  console.log(`
Sync Incremental CLI

DW 直连增量摄入 + 自动 persona/plan 联动重算

Usage:
  pnpm sync-incremental -- --dir=<manifest_dir> [--dry-run] [--no-recompute]

Options:
  --dir=<path>     必填,manifest.yaml 所在目录(增量 cursor 配置也在 manifest.sql_source.incremental)
  --dry-run        只读 cursor 注入预览,不写库
  --no-recompute   只拉数据,不触发 persona/plan 重算
  --help, -h       显示本帮助

首跑(无 cursor):等价全量(去 dev cohort LIMIT,按 yaml 完整拉)
后续:WHERE updated_date > '\${cursor}',只拉新/改的行
`);
}

async function bootstrap() {
  const args = parseArgs(process.argv.slice(2));
  if (args.help || !args.dir) {
    printHelp();
    process.exit(args.help ? 0 : 1);
  }

  const logger = new Logger('sync-incremental:cli');
  logger.log(`Starting incremental sync(dir=${args.dir}, dryRun=${args.dryRun}, noRecompute=${args.noRecompute})`);

  const app = await NestFactory.createApplicationContext(AppModule, {
    logger: ['log', 'warn', 'error'],
  });

  try {
    const importSvc = app.get(ColdImportService);
    const result = await importSvc.importDirectory(args.dir!, {
      dryRun: args.dryRun,
      incremental: true,
    });

    logger.log('─────────────────────────────────────────');
    logger.log(`Result:`);
    logger.log(`  runId:                 ${result.runId}`);
    logger.log(`  host:                  ${result.hostName} (${result.hostId})`);
    logger.log(`  tenants:               [${result.tenantIds.join(',')}]`);
    logger.log(`  status:                ${result.status}`);
    logger.log(`  patients upserted:     ${result.totals.patientsUpserted}`);
    logger.log(`  transactions written:  ${result.totals.transactionsWritten}`);
    logger.log(`  duplicates (idem):     ${result.totals.duplicates}`);
    logger.log(`  failed:                ${result.totals.failed}`);
    logger.log(
      `  facts:                 created=${result.totals.factsCreated} superseded=${result.totals.factsSuperseded} unchanged=${result.totals.factsUnchanged} failed=${result.totals.factsFailed}`,
    );

    if (args.dryRun || args.noRecompute) {
      logger.log('Skipping persona/plan recompute (dry-run 或 --no-recompute)');
      await app.close();
      process.exit(0);
    }

    // ─────────────────────────────────────────
    // 联动 persona 重算 — 仅受影响的 patient
    // ─────────────────────────────────────────
    const prisma = app.get(PrismaService);
    const personaSvc = app.get(PersonaService);
    const planEngine = app.get(PlanEngineService);

    // 找本次 syncLog 之后 written 的 transactions 涉及的 patient
    if (!result.syncLogId) {
      logger.warn('No syncLog id — 跳过 persona/plan(可能 dry-run)');
      await app.close();
      process.exit(0);
    }
    const syncLog = await prisma.syncLog.findUnique({ where: { id: result.syncLogId } });
    if (!syncLog) {
      logger.warn('syncLog 找不到 — 跳过');
      await app.close();
      process.exit(0);
    }
    const affectedPatients = await prisma.patientTransaction.findMany({
      where: {
        hostId: result.hostId,
        createdAt: { gte: syncLog.startedAt },
      },
      select: { patientId: true },
      distinct: ['patientId'],
    });
    const patientIds = affectedPatients.map((r) => r.patientId).filter((id): id is string => !!id);
    logger.log('─────────────────────────────────────────');
    logger.log(`Persona recompute:${patientIds.length} patients affected`);
    let pSuccess = 0;
    let pNoop = 0;
    let pFailed = 0;
    for (const patientId of patientIds) {
      try {
        const r = await personaSvc.recompute({
          patientId,
          source: `incremental-sync:${result.runId}`,
        });
        if (r.status === 'success' || r.status === 'partial') pSuccess++;
        else if (r.status === 'noop') pNoop++;
        else pFailed++;
      } catch (e) {
        pFailed++;
        logger.warn(`persona patient=${patientId} failed: ${(e as Error).message}`);
      }
    }
    logger.log(`  success=${pSuccess} noop=${pNoop} failed=${pFailed}`);

    // ─────────────────────────────────────────
    // 联动 plan 重算 — 整 host(scenario SQL 跑一次刷新所有 patient 池)
    // ─────────────────────────────────────────
    logger.log('─────────────────────────────────────────');
    logger.log(`Plan recompute:host=${result.hostName}`);
    const tenants = await prisma.patient.findMany({
      where: { hostId: result.hostId },
      distinct: ['tenantId'],
      select: { tenantId: true },
    });
    let totalPlansCreated = 0;
    let totalHits = 0;
    for (const t of tenants) {
      const r = await planEngine.runAllForHost({
        hostId: result.hostId,
        tenantId: t.tenantId,
        now: new Date(),
      });
      totalPlansCreated += r.plansCreated;
      totalHits += r.patientsHit;
      logger.log(`  tenant=${t.tenantId}: patientsHit=${r.patientsHit} plansCreated=${r.plansCreated} skipped=${r.plansSkippedAssigned}`);
    }
    logger.log(`  Total: patientsHit=${totalHits} plansCreated=${totalPlansCreated}`);
    logger.log('─────────────────────────────────────────');
  } catch (e) {
    logger.error(`Incremental sync failed: ${(e as Error).message}`);
    if ((e as Error).stack) logger.error((e as Error).stack);
    await app.close();
    process.exit(1);
  }

  await app.close();
  process.exit(0);
}

bootstrap();
