import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import * as path from 'path';
import { PrismaService } from '../prisma/prisma.service';
import {
  ColdImportService,
  SyncAlreadyRunningError,
} from '../modules/sync/cold-import/cold-import.service';
import { PersonaService } from '../modules/persona/persona.service';
import { PlanEngineService } from '../modules/plan/engine/plan-engine.service';

/**
 * SyncIncrementalSchedulerService — W4 末:DW 直连增量自动跑(每天 02:30)
 *
 * 上游:DW 团队每日凌晨 ~02:00 完成全量刷新
 * 我们 02:30 触发:
 *   1. ColdImportService.importDirectory({ incremental: true })
 *      - 读 sync_logs 上次 cursor_after → SQL 注入 WHERE updated_date > '...'
 *      - 反向拉主档(C 方案)+ stub auto-create(A 方案)→ 无数据丢失
 *      - 写新 cursor 到 sync_logs
 *   2. Persona 重算(affected patient — 本次 sync 写入的 distinct patient_id)
 *   3. Plan 重算(per tenant,scenario SQL + 6 因子打分)
 *
 * 多 host:默认自动发现 data/<host>/manifest.yaml 中 `auto_sync: true` 的 host;
 *   PAC_INCREMENTAL_HOSTS env(逗号分隔)若设则作显式 override(escape hatch)。
 * 每个 host 期望 data/<host>/manifest.yaml 存在
 *
 * 跑失败:
 *   - cursor 不前进(ColdImportService 内 transaction)→ 下次自动 catchup
 *   - log ERROR + 不抛(让 cron 下次继续)
 */
@Injectable()
export class SyncIncrementalSchedulerService {
  private readonly logger = new Logger(SyncIncrementalSchedulerService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly coldImport: ColdImportService,
    private readonly persona: PersonaService,
    private readonly planEngine: PlanEngineService,
  ) {}

  /// DW 增量同步 cron — env 驱动
  /// PAC_INCREMENTAL_CRON:
  ///   不设       → 不跑(local 默认,避免开发期自动消耗 DW 流量)
  ///   '30 2 * * *' → 生产推荐(DW 02:00 全量刷新后)
  ///   '30 3 * * *' → staging 错峰
  /// 详见 .env.example
  @Cron(process.env.PAC_INCREMENTAL_CRON || '0 0 31 12 *' /* never — env not set */, {
    name: 'sync-incremental-daily',
    // cron 表达式按北京时间解释(容器为 UTC;不加则实际 +8h —— 03:30 会变 11:30)
    timeZone: 'Asia/Shanghai',
  })
  async runDaily(): Promise<void> {
    const dataDir = process.env.PAC_INCREMENTAL_DATA_DIR ?? path.resolve(__dirname, '../../data');
    // host 来源:env 显式 override(逗号分隔)优先;未设 → 自动发现 manifest.auto_sync=true。
    // 自动发现取代旧 `PAC_INCREMENTAL_HOSTS=jvs-dw` 默认值,接入新 host 只改其 manifest,不碰 env(防 drift)。
    const envHosts = (process.env.PAC_INCREMENTAL_HOSTS ?? '')
      .split(',')
      .map((h) => h.trim())
      .filter(Boolean);
    const hosts = envHosts.length > 0 ? envHosts : this.coldImport.discoverAutoSyncHostDirs(dataDir);
    const source = envHosts.length > 0 ? 'env-override' : 'manifest auto_sync';
    if (hosts.length === 0) {
      this.logger.warn(
        `sync-incremental: 无 host 可同步(env PAC_INCREMENTAL_HOSTS 未设,且 ${dataDir} 下无 manifest.auto_sync=true);skip`,
      );
      return;
    }
    this.logger.log(
      `sync-incremental: START hosts=[${hosts.join(',')}] (source=${source}) dataDir=${dataDir}`,
    );
    for (const host of hosts) {
      try {
        await this.runOne(path.join(dataDir, host));
      } catch (err) {
        if (err instanceof SyncAlreadyRunningError) {
          // 并发拦截 — 已有手动跑或上次 cron 还没完。下次 cron 自然 retry。
          this.logger.warn(
            `sync-incremental: host=${host} skip(并发锁拦截):${err.message}`,
          );
        } else {
          this.logger.error(
            `sync-incremental: host=${host} failed: ${(err as Error).message}`,
          );
        }
        // 不抛 — 下个 host 继续;cursor 没前进自然下次 catchup
      }
    }
    this.logger.log('sync-incremental: ALL DONE');
  }

  /// 跑单个 host 的完整链路(可手动调,debug 用)
  async runOne(dir: string): Promise<void> {
    const started = Date.now();
    const r = await this.coldImport.importDirectory(dir, { incremental: true });
    this.logger.log(
      `sync-incremental: cold-import done host=${r.hostName} ` +
        `patients=${r.totals.patientsUpserted} txns=${r.totals.transactionsWritten} ` +
        `facts(created=${r.totals.factsCreated} superseded=${r.totals.factsSuperseded})`,
    );

    // ─── Persona recompute(affected patient only)──────
    if (r.syncLogId) {
      const syncLog = await this.prisma.syncLog.findUnique({ where: { id: r.syncLogId } });
      if (syncLog) {
        const affected = await this.prisma.patientTransaction.findMany({
          where: { hostId: r.hostId, createdAt: { gte: syncLog.startedAt } },
          select: { patientId: true },
          distinct: ['patientId'],
        });
        const pids = affected.map((p) => p.patientId).filter((p): p is string => !!p);
        let ok = 0;
        let failed = 0;
        // 串行:persona 重算是 **Node-CPU-bound**(16 特征 JS 计算占主),实测 conc=8 不提速、
        //   还因竞争略慢(本地 300 患者 30s→32s)。异步并发只能重叠 I/O,重叠不了单线程 CPU。
        //   真要提速需 profile 特征计算或多进程/worker,不在此处做。
        for (const pid of pids) {
          try {
            await this.persona.recompute({
              patientId: pid,
              source: `incremental-sync:${r.runId}`,
            });
            ok++;
          } catch (err) {
            failed++;
            this.logger.warn(`persona patient=${pid}: ${(err as Error).message}`);
          }
        }
        this.logger.log(`persona recompute: success=${ok} failed=${failed} (${pids.length} affected)`);
      }
    }

    // ─── Plan recompute(per tenant)──────
    const tenants = await this.prisma.patient.findMany({
      where: { hostId: r.hostId },
      distinct: ['tenantId'],
      select: { tenantId: true },
    });
    let plansCreated = 0;
    for (const t of tenants) {
      const r2 = await this.planEngine.runAllForHost({
        hostId: r.hostId,
        tenantId: t.tenantId,
        now: new Date(),
      });
      plansCreated += r2.plansCreated;
    }
    this.logger.log(
      `plan recompute: ${tenants.length} tenants, plansCreated=${plansCreated} ` +
        `(total elapsed=${Date.now() - started}ms)`,
    );
  }
}
