import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '../prisma/prisma.service';
import { AlertService } from '../common/alerting/alert.service';
import { ColdImportService } from '../modules/sync/cold-import/cold-import.service';

/**
 * DwLagMonitorService — 每小时检查 DW 增量数据滞后(W4 末)
 *
 * 输入:sync_logs 中最近一条成功的 incremental_bundle 的 cursor_after JSON
 *       例:{"fact_emr_treatment_out":"2026-05-26 15:30:00",...}
 *
 * 计算:max(cursor_after.*) vs now() → diff hours
 *   - diff <= 24h:🟢 健康
 *   - 24h < diff <= 48h:🟡 注意(DW 可能没刷新,提醒值班)
 *   - diff > 48h:🔴 异常(数据严重滞后,事件可能漏召)
 *
 * 当前实现:log + AlertService.send(webhook/钉钉)。
 *
 * 调整阈值:PAC_LAG_WARN_HOURS=24 / PAC_LAG_ERROR_HOURS=48 env
 */
@Injectable()
export class DwLagMonitorService {
  private readonly logger = new Logger(DwLagMonitorService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly alerter: AlertService,
    private readonly coldImport: ColdImportService,
  ) {}

  /// DW 数据滞后告警 cron — env 驱动
  /// PAC_LAG_MONITOR_CRON:
  ///   不设         → 不跑(local 默认)
  ///   '0 * * * *'  → 生产 / staging 推荐(每小时检查)
  @Cron(process.env.PAC_LAG_MONITOR_CRON || '0 0 31 12 *' /* never — env not set */, {
    name: 'dw-lag-monitor',
  })
  async checkLag(): Promise<void> {
    // 全局默认(env);每宿主可在 manifest.monitoring 覆盖(上游节奏不同,一刀切会误报/漏报)
    const gWarnH = Number(process.env.PAC_LAG_WARN_HOURS ?? '24');
    const gErrorH = Number(process.env.PAC_LAG_ERROR_HOURS ?? '48');
    const now = Date.now();

    const hosts = await this.prisma.host.findMany({ select: { id: true, name: true } });
    for (const host of hosts) {
      const ops = this.coldImport.getHostOpsConfig(host.name);
      // ⚠️ 只对 **pull/DW 宿主**(manifest 声明 sql_source)算游标滞后。
      //   push / 文件宿主的数据由宿主主动推,sync_logs 的 cursor_before/after 按设计恒为 null,
      //   拿"游标多久没推进"衡量它们是**指标本身用错了**:数据流正常也照报,且**永不自愈**
      //   —— 每小时响一次,直到把真告警淹掉。
      //   2026-07-31 实测:friday(push 宿主)因一条 fetched=0 的误跑增量留下游标,
      //   连报 37 小时"DW 数据滞后";而同期它 push 了 614 批 10.79 万行,数据好得很。
      //   push 宿主的健康度应看"距上次成功 push 的时长",不在本监控职责内。
      if (!ops.hasSqlSource) {
        this.logger.debug(`dw-lag: host=${host.name} 非 pull/DW 宿主(无 sql_source)— 跳过游标滞后检查`);
        continue;
      }
      const warnH = ops.dwLagWarnHours ?? gWarnH;
      const errorH = ops.dwLagErrorHours ?? gErrorH;
      const last = await this.prisma.syncLog.findFirst({
        where: {
          hostId: host.id,
          resource: 'incremental_bundle',
          status: 'success',
          cursorAfter: { not: null },
        },
        orderBy: { startedAt: 'desc' },
      });
      if (!last?.cursorAfter) {
        this.logger.warn(`dw-lag: host=${host.name} 还没跑过增量(无 cursor)— 检查 sync-incremental cron`);
        continue;
      }
      let cursors: Record<string, string>;
      try {
        cursors = JSON.parse(last.cursorAfter) as Record<string, string>;
      } catch {
        this.logger.warn(`dw-lag: host=${host.name} cursor JSON 解析失败 — ${last.cursorAfter}`);
        continue;
      }
      const maxCursorTs = Math.max(
        ...Object.values(cursors)
          .map((v) => new Date(v).getTime())
          .filter((t) => !isNaN(t)),
      );
      const diffHours = (now - maxCursorTs) / 3_600_000;
      const tag =
        diffHours > errorH ? '🔴' :
        diffHours > warnH ? '🟡' :
        '🟢';
      const msg = `dw-lag: ${tag} host=${host.name} max_cursor=${new Date(maxCursorTs).toISOString()} lag=${diffHours.toFixed(1)}h (warn=${warnH} error=${errorH})`;
      if (diffHours > errorH) {
        this.logger.error(msg);
        await this.alerter.send({
          level: 'critical',
          title: 'DW 数据严重滞后',
          body: `增量游标已 ${diffHours.toFixed(1)} 小时未推进(阈值 ${errorH}h)— 事件可能漏召。`,
          context: { host: host.name, maxCursor: new Date(maxCursorTs).toISOString() },
        });
      } else if (diffHours > warnH) {
        this.logger.warn(msg);
        await this.alerter.send({
          level: 'warning',
          title: 'DW 数据滞后(注意)',
          body: `增量游标已 ${diffHours.toFixed(1)} 小时未推进(阈值 ${warnH}h)— DW 可能没刷新。`,
          context: { host: host.name, maxCursor: new Date(maxCursorTs).toISOString() },
        });
      } else {
        this.logger.log(msg);
      }
    }

    await this.checkPushLag(hosts, now);
  }

  /**
   * push 断流检测(纯 push 宿主的唯一"数据还活着"探针)。
   * 只对**曾经推过**的 host 生效(有 push SyncLog)—— 还没上线 push 的 host 不误报。
   * 阈值 PAC_PUSH_LAG_ERROR_HOURS(默认 26h:容忍"每日一推/每晚补推"节奏 + 2h 余量)。
   */
  private async checkPushLag(
    hosts: Array<{ id: string; name: string }>,
    now: number,
  ): Promise<void> {
    const gErrorH = Number(process.env.PAC_PUSH_LAG_ERROR_HOURS ?? '26');
    for (const host of hosts) {
      // 每宿主阈值:实时推的 host 设小(断几小时就该报),批量推的设大;缺省回退全局
      const ops = this.coldImport.getHostOpsConfig(host.name);
      // 联调期宿主 manifest 置 push_lag_alert: false —— "推过几次就停"是正常节奏,
      // 不是链路故障;每小时报一次 critical 会把真告警淹掉。
      if (!ops.pushLagAlert) {
        this.logger.log(`push-lag: ⏸ host=${host.name} 告警已按 manifest 关闭(联调期)`);
        continue;
      }
      const errorH = ops.pushLagErrorHours ?? gErrorH;
      const lastPush = await this.prisma.syncLog.findFirst({
        where: { hostId: host.id, direction: 'push' },
        orderBy: { startedAt: 'desc' },
        select: { startedAt: true },
      });
      if (!lastPush) continue; // 从未推过 = 未上线 push,不监控
      const diffHours = (now - lastPush.startedAt.getTime()) / 3_600_000;
      if (diffHours > errorH) {
        this.logger.error(
          `push-lag: 🔴 host=${host.name} 已 ${diffHours.toFixed(1)}h 无 push(阈值 ${errorH}h)`,
        );
        await this.alerter.send({
          level: 'critical',
          title: 'push 断流:宿主推送中断',
          body:
            `host=${host.name} 最近一次 push 在 ${lastPush.startedAt.toISOString()},` +
            `已 ${diffHours.toFixed(1)} 小时无推送(阈值 ${errorH}h)— 宿主侧推送链路可能挂了,数据在漏。`,
          context: { host: host.name, lastPushAt: lastPush.startedAt.toISOString() },
        });
      } else {
        this.logger.log(`push-lag: 🟢 host=${host.name} last_push=${diffHours.toFixed(1)}h ago`);
      }
    }
  }
}
