import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { maskName } from '@pac/utils';
import { PrismaService } from '../../../prisma/prisma.service';
import { AiCallRunnerService } from '../ai-call-runner.service';
import { DraftRecallSummaryCall } from '../calls/draft-recall-summary/call';
import type { DraftRecallSummaryInput } from '../calls/draft-recall-summary/input.types';
import type { TenantScopeContext } from '../../../common/decorators/tenant-scope.decorator';

export interface RecallSummaryResult {
  /** 一句话摘要;status='empty' 时为 null */
  summary: string | null;
  status: 'ready' | 'empty';
  source?: 'agent' | 'template_fallback';
}

/**
 * RecallSummaryOrchestrator — 回访历史「一句话摘要」get-or-generate。
 *
 *   有 ready 的 plan_summaries[recall_history] → 直接返回(秒回,不调 LLM)。
 *   没有 → 取该 patient 的回访记录(plan_executions)→ 跑 AiCall → upsert 存库 → 返回。
 *   无回访记录 → status='empty'(前端继续展示结构数据,不显示摘要)。
 */
@Injectable()
export class RecallSummaryOrchestrator {
  private readonly logger = new Logger(RecallSummaryOrchestrator.name);
  private static readonly TYPE = 'recall_history' as const;

  constructor(
    private readonly prisma: PrismaService,
    private readonly runner: AiCallRunnerService,
    private readonly call: DraftRecallSummaryCall,
  ) {}

  async getOrGenerate(scope: TenantScopeContext, planId: string): Promise<RecallSummaryResult> {
    const plan = await this.prisma.followupPlan.findUnique({
      where: { id: planId },
      select: { id: true, hostId: true, tenantId: true, patientId: true },
    });
    if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) {
      throw new NotFoundException(`Plan ${planId} not found`);
    }

    // 1) 已生成 → 直接返回
    const existing = await this.prisma.planSummary.findUnique({
      where: { planId_type: { planId: plan.id, type: RecallSummaryOrchestrator.TYPE } },
      select: { content: true, status: true, source: true },
    });
    if (existing?.status === 'ready' && existing.content) {
      return {
        summary: existing.content,
        status: 'ready',
        source: (existing.source as 'agent' | 'template_fallback' | null) ?? undefined,
      };
    }

    // 2) 取历史联系(诊所回访 patient_return_visits,patient 级,最近 12 条)
    const visits = await this.prisma.patientReturnVisit.findMany({
      where: { hostId: scope.hostId, tenantId: scope.tenantId, patientId: plan.patientId },
      // nulls:'last' 是刻意的 —— Postgres 的 DESC 默认 NULLS FIRST,无日期的记录会占住
      // 列表头部(既冒充"最近一次",又吃掉 take:12 的名额)。无日期的当最老处理。
      orderBy: { taskDate: { sort: 'desc', nulls: 'last' } },
      take: 12,
      select: {
        taskDate: true, type: true, status: true, taskStatus: true,
        treatmentItems: true, followContent: true, result: true,
      },
    });
    if (visits.length === 0) return { summary: null, status: 'empty' };

    const patient = await this.prisma.patient.findUnique({
      where: { id: plan.patientId },
      select: { name: true },
    });

    // ⭐ 未来排程判定放这里(程序算),不交给 LLM —— 回访表含未来排程,
    //   模型没有"今天"就分不清"还没到期"和"漏做了"(2026-07 生产事故)。
    //   口径:按**日期**比(taskDate 是 @db.Date,无时分秒),taskDate > today 即未到期。
    const today = new Date().toISOString().slice(0, 10);

    const dated = visits.map((v) => ({
      ...v,
      taskDate: v.taskDate ? v.taskDate.toISOString().slice(0, 10) : null,
    }));
    // ⭐「最近一次真正发生过的联系」同样程序判定:列表已按日期倒序,
    //   第一条**有日期且已到期**的就是它(至多一条;全是未来排程/全无日期 → 一条都不标)。
    //   业务第一诉求是"上次联系聊到哪儿了",摘要必须先答这条 —— 见 input.types.isLatestPast。
    const latestPastIndex = dated.findIndex((v) => v.taskDate != null && v.taskDate <= today);
    // ⭐「距今多少天」也程序算 —— 模型自己减日期会算错(2026-07-27:把 7.5 个月前的回访
    //   说成"近3个月")。口径同 isFuture:按日期(UTC 零点)差,未来排程为负。
    const todayMs = Date.parse(`${today}T00:00:00Z`);
    const daysAgoOf = (d: string | null): number | null =>
      d == null ? null : Math.round((todayMs - Date.parse(`${d}T00:00:00Z`)) / 86400_000);

    const input: DraftRecallSummaryInput = {
      patientNameMasked: maskName(patient?.name ?? null) ?? '该患者',
      today,
      items: dated.map((v, i) => ({
        taskDate: v.taskDate,
        type: v.type,
        status: v.status,
        taskStatus: v.taskStatus,
        // 无日期 → 不算未来(宁可当普通历史,也不要给出"已排期"的错误暗示)
        isFuture: v.taskDate != null && v.taskDate > today,
        isLatestPast: i === latestPastIndex,
        daysAgo: daysAgoOf(v.taskDate),
        treatmentItems: v.treatmentItems,
        followContent: v.followContent,
        result: v.result,
      })),
    };

    // 3) 跑 AiCall → upsert
    const result = await this.runner.run(this.call, input, {
      hostId: plan.hostId,
      tenantId: plan.tenantId,
      workflowRunId: randomUUID(),
      linkedPatientId: plan.patientId,
      linkedPlanId: plan.id,
      bustCache: false,
      evalMode: 'production',
    });

    await this.prisma.planSummary.upsert({
      where: { planId_type: { planId: plan.id, type: RecallSummaryOrchestrator.TYPE } },
      create: {
        hostId: plan.hostId,
        tenantId: plan.tenantId,
        planId: plan.id,
        type: RecallSummaryOrchestrator.TYPE,
        content: result.output.summary,
        status: 'ready',
        source: result.source,
        agentInvocationId: result.invocationId,
      },
      update: {
        content: result.output.summary,
        status: 'ready',
        source: result.source,
        agentInvocationId: result.invocationId,
      },
    });

    return { summary: result.output.summary, status: 'ready', source: result.source };
  }
}
