import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '../prisma/prisma.service';
import { AlertService, type AlertLevel } from '../common/alerting/alert.service';

/**
 * DailyHealthReportService — 每日 09:00(沪)给企微推一份**简洁的线上健康日报**。
 *
 * 目的:把分散的监控(DW 延迟 / 画像覆盖 / 摄入 / 重算 / 召回 / AI 用量)收口成**一条**
 *      每日可对比的指标快照。每条都带"近 24h 增量(diff)",一眼看出昨天系统动了多少、有没有异常。
 *
 * 通道:复用 AlertService → 企微群机器人 webhook(ALERT_WEBHOOK_URL)。
 *      报告整体级别(info/warning/critical)取最差项,企微图标自动 🟢/🟡/🔴。
 *
 * cron:PAC_HEALTH_REPORT_CRON(默认 '0 9 * * *',Asia/Shanghai);DW 08:00 落库后跑。
 */
@Injectable()
export class DailyHealthReportService {
  private readonly logger = new Logger(DailyHealthReportService.name);

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

  @Cron(process.env.PAC_HEALTH_REPORT_CRON || '0 9 * * *', {
    name: 'daily-health-report',
    timeZone: 'Asia/Shanghai',
  })
  async run(): Promise<void> {
    try {
      const { level, title, body } = await this.build();
      await this.alerter.send({ level, title, body });
      this.logger.log(`每日健康报告已推送(${level})`);
    } catch (err) {
      this.logger.error(`每日健康报告生成/推送失败: ${err instanceof Error ? err.message : String(err)}`);
    }
  }

  /** 生成报告(也供手动触发端点预览)。 */
  async build(): Promise<{ level: AlertLevel; title: string; body: string }> {
    const now = Date.now();
    const since24h = new Date(now - 24 * 3_600_000);
    const since7d = new Date(now - 7 * 24 * 3_600_000);
    const warnH = Number(process.env.PAC_LAG_WARN_HOURS ?? '24');
    const errorH = Number(process.env.PAC_LAG_ERROR_HOURS ?? '48');

    // ── 数据量 + 近 24h 增量 ──
    const [patientsTotal, patients24h, factsTotal, facts24h, plansActive, plansAssigned, plans24h] =
      await Promise.all([
        this.prisma.patient.count(),
        this.prisma.patient.count({ where: { createdAt: { gte: since24h } } }),
        this.prisma.patientFact.count(),
        this.prisma.patientFact.count({ where: { createdAt: { gte: since24h } } }),
        this.prisma.followupPlan.count({ where: { status: 'active', supersededAt: null } }),
        this.prisma.followupPlan.count({ where: { status: 'assigned', supersededAt: null } }),
        this.prisma.followupPlan.count({ where: { createdAt: { gte: since24h } } }),
      ]);

    // ── 摄入:最近一次成功增量 + DW 延迟 + 近 7 日失败 ──
    const lastSync = await this.prisma.syncLog.findFirst({
      where: { resource: 'incremental_bundle', status: 'success', cursorAfter: { not: null } },
      orderBy: { startedAt: 'desc' },
      select: { startedAt: true, transactionsWritten: true, factsEmitted: true, cursorAfter: true },
    });
    const fail7d = await this.prisma.syncLog.count({
      where: { resource: 'incremental_bundle', status: { not: 'success' }, startedAt: { gte: since7d } },
    });
    const lagHours = lastSync?.cursorAfter ? cursorLagHours(lastSync.cursorAfter, now) : null;
    const lagTag = lagHours == null ? '⚪' : lagHours > errorH ? '🔴' : lagHours > warnH ? '🟡' : '🟢';

    // ── 重算:画像 / 召回 最近一次 ──
    const [lastPersona, lastPlanGen] = await Promise.all([
      this.prisma.personaRecomputeLog.findFirst({ orderBy: { startedAt: 'desc' }, select: { startedAt: true, status: true } }),
      this.prisma.planGenerationLog.findFirst({ orderBy: { startedAt: 'desc' }, select: { startedAt: true, plansCreated: true } }),
    ]);

    // ── 客服执行(近 24h)按 outcome ──
    const execGroups = await this.prisma.planExecution.groupBy({
      by: ['outcome'],
      where: { createdAt: { gte: since24h } },
      _count: { _all: true },
    });
    const execTotal = execGroups.reduce((s, g) => s + g._count._all, 0);
    const execBreakdown = execGroups
      .sort((a, b) => b._count._all - a._count._all)
      .map((g) => `${OUTCOME_ZH[g.outcome] ?? g.outcome} ${g._count._all}`)
      .join(' / ');

    // ── 画像覆盖 ──
    const personaPatients = await this.prisma.persona.count({ where: { supersededAt: null } });
    const coveragePct = patientsTotal > 0 ? Math.round((personaPatients / patientsTotal) * 100) : 0;

    // ── AI 用量(近 24h)──
    const [inv24h, costAgg] = await Promise.all([
      this.prisma.agentInvocation.count({ where: { startedAt: { gte: since24h } } }),
      this.prisma.agentInvocation.aggregate({ _sum: { costYuan: true }, where: { startedAt: { gte: since24h } } }),
    ]);
    const cost24h = Number(costAgg._sum.costYuan ?? 0);

    // ── 整体级别:取最差(摄入失败/DW 严重滞后 → critical;滞后/重算异常 → warning)──
    let level: AlertLevel = 'info';
    if (fail7d > 0 && lastSync == null) level = 'critical';
    if (lagHours != null && lagHours > warnH) level = 'warning';
    if (lastPersona?.status === 'failed' || (lastPlanGen == null)) level = 'warning';
    if (lagHours != null && lagHours > errorH) level = 'critical';

    const dateStr = new Date(now).toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai' }).replace(/\//g, '-');
    const title = `每日健康报告 · ${dateStr}`;

    const body = [
      `📥 摄入  ${lagTag} 上次 ${fmtTime(lastSync?.startedAt)} · DW延迟 ${lagHours == null ? '—' : lagHours.toFixed(1) + 'h'} · 近7日失败 ${fail7d}`,
      `🔄 重算  画像 ${fmtTime(lastPersona?.startedAt)}${lastPersona?.status && lastPersona.status !== 'success' ? `(${lastPersona.status})` : ''} · 召回 ${fmtTime(lastPlanGen?.startedAt)}(+${lastPlanGen?.plansCreated ?? 0}计划)`,
      `📈 数据(近24h↑)  患者 ${fmtNum(patientsTotal)}(+${patients24h}) · 事实 ${fmtWan(factsTotal)}(+${fmtNum(facts24h)}) · 新计划 +${plans24h}`,
      `📋 召回池  待办 ${fmtNum(plansActive)} · 进行中 ${fmtNum(plansAssigned)}`,
      `📞 客服执行(24h)  ${execTotal} 次${execBreakdown ? ` · ${execBreakdown}` : ''}`,
      `🖼 画像覆盖  ${fmtNum(personaPatients)} / ${fmtNum(patientsTotal)}(${coveragePct}%)`,
      `🤖 AI(24h)  ${inv24h} 次 · ¥${cost24h.toFixed(2)}`,
    ].join('\n');

    return { level, title, body };
  }
}

/** cursor_after JSON({resource: "2026-06-21 19:30:00", ...})→ 距今小时数(取最大游标)。 */
function cursorLagHours(cursorAfter: string, now: number): number | null {
  try {
    const cursors = JSON.parse(cursorAfter) as Record<string, string>;
    const maxTs = Math.max(...Object.values(cursors).map((v) => new Date(v).getTime()).filter((t) => !isNaN(t)));
    if (!isFinite(maxTs)) return null;
    return (now - maxTs) / 3_600_000;
  } catch {
    return null;
  }
}

const OUTCOME_ZH: Record<string, string> = {
  success_appointed: '约到诊',
  scheduled_next: '约下次',
  no_answer: '未接通',
  abandoned: '放弃',
  refused: '拒绝',
  rescheduled: '改约',
  considering: '考虑中',
  declined_recent: '近期不考虑',
  needs_doctor: '需医生',
  external_treatment: '外院已治',
  pending_info: '待确认',
  sms_sent: '已发短信',
  proactive_sms: '主动短信',
  marked_invalid: '标记无效',
};

const fmtNum = (n: number): string => n.toLocaleString('en-US');
const fmtWan = (n: number): string => (n >= 10000 ? `${(n / 10000).toFixed(1)}万` : `${n}`);
function fmtTime(d: Date | null | undefined): string {
  if (!d) return '—';
  return new Date(d).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false });
}
