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

export interface RecallBriefResult {
  /** 一句话召回简报;status='empty' 时为 null */
  summary: string | null;
  status: 'ready' | 'empty';
  source?: 'agent' | 'template_fallback';
}

/** plan_reasons.signals 形态(跟前端 ReasonLine 契约一致)。 */
interface ReasonSignals {
  subKey?: string | null;
  triggers?: Array<{ type?: string; code?: string | null }>;
  toothPosition?: string | null;
  daysSince?: number;
  expectedCategories?: string[];
}

/** 把天数转人读文本(server 端轻量版,无需精确到日)。 */
function daysText(d: number): string {
  if (!Number.isFinite(d) || d <= 0) return '近期';
  if (d < 60) return `${d} 天`;
  const months = Math.round(d / 30);
  if (months < 12) return `${d} 天(约 ${months} 个月)`;
  const years = (d / 365).toFixed(1).replace(/\.0$/, '');
  return `${d} 天(约 ${years} 年)`;
}

/**
 * RecallBriefOrchestrator — 本次召回「一句话简报」get-or-generate。
 *
 *   有 ready 的 plan_summaries[recall_brief] → 直接返回(秒回,不调 LLM)。
 *   没有 → 取 召回原因(plan_reasons) + 历史治疗(treatment_record actual 计数) + 画像(persona_features)
 *          → 跑 AiCall(回答 谁/解决什么/到诊做什么 三问)→ upsert 存库 → 返回。
 *   无召回原因 → status='empty'。
 *
 * 与 Recall/PersonaSummaryOrchestrator 平行,只是输入更全(三类信号合一)。
 */
@Injectable()
export class RecallBriefOrchestrator {
  private readonly logger = new Logger(RecallBriefOrchestrator.name);
  private static readonly TYPE = 'recall_brief' as const;

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

  async getOrGenerate(scope: TenantScopeContext, planId: string): Promise<RecallBriefResult> {
    const plan = await this.prisma.followupPlan.findUnique({
      where: { id: planId },
      select: {
        id: true,
        hostId: true,
        tenantId: true,
        patientId: true,
        reasons: {
          orderBy: { priorityScore: 'desc' },
          select: { scenario: true, subKey: true, reason: true, signals: true },
        },
      },
    });
    if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) {
      throw new NotFoundException(`Plan ${planId} not found`);
    }
    if (plan.reasons.length === 0) return { summary: null, status: 'empty' };

    // 1) 已生成 → 直接返回
    const existing = await this.prisma.planSummary.findUnique({
      where: { planId_type: { planId: plan.id, type: RecallBriefOrchestrator.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) 召回原因(翻译成中文)
    const reasons = plan.reasons.map((r) => {
      const s = (r.signals ?? {}) as ReasonSignals;
      const code = (s.triggers ?? []).find((t) => /^K\d/i.test(t.code ?? ''))?.code ?? null;
      // 触发感知(同 ReasonLine/话术):lead=最高优先来源(诊断>建议>影像)。
      //   lead=建议(无诊断)→ subLabel 用建议名("建议种植"),不套子场景诊断词"缺失牙未启动修复"。
      const lead = (s.triggers ?? [])[0];
      const isRecLead = lead?.type === 'recommendation' && !!lead.code;
      const source: 'diagnosis' | 'recommendation' | 'image_finding' =
        lead?.type === 'recommendation'
          ? 'recommendation'
          : lead?.type === 'image_finding'
            ? 'image_finding'
            : 'diagnosis';
      return {
        subLabel: isRecLead
          ? diagnosisCodeNameZh(lead.code as string)
          : subLabelZh(r.scenario, s.subKey ?? '') || r.scenario,
        source,
        diagnosis: code ? diagnosisCodeNameZh(code) : null,
        tooth: (s.toothPosition ?? '').trim() || null,
        daysSinceText: daysText(s.daysSince ?? 0),
        expectedCategories: (s.expectedCategories ?? []).map((c) => treatmentCategoryNameZh(c)),
      };
    });

    // 3) 历史治疗:treatment_record actual 按 category 计数
    const txFacts = await this.prisma.patientFact.findMany({
      where: {
        hostId: scope.hostId,
        tenantId: scope.tenantId,
        patientId: plan.patientId,
        type: 'treatment_record',
        kind: 'actual',
      },
      select: { content: true },
    });
    const catCount = new Map<string, number>();
    for (const f of txFacts) {
      const cat = String((f.content as Record<string, unknown> | null)?.category ?? '').trim();
      if (!cat) continue;
      catCount.set(cat, (catCount.get(cat) ?? 0) + 1);
    }
    const treatmentHistory = [...catCount.entries()]
      .sort((a, b) => b[1] - a[1])
      .map(([cat, count]) => ({ category: treatmentCategoryNameZh(cat), count }));

    // 4) 画像标签
    const persona = await this.prisma.persona.findFirst({
      where: { patientId: plan.patientId, supersededAt: null },
      orderBy: { version: 'desc' },
      select: { features: { orderBy: { createdAt: 'asc' }, select: { key: true, description: true } } },
    });
    const personaItems = (persona?.features ?? []).map((f) => ({
      label: PERSONA_FEATURE_SPECS[f.key]?.nameZh ?? f.key,
      value: f.description,
    }));

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

    const input: DraftRecallBriefInput = {
      patientNameMasked: maskName(patient?.name ?? null) ?? '该患者',
      persona: personaItems,
      treatmentHistory,
      reasons,
    };

    // 5) 跑 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: RecallBriefOrchestrator.TYPE } },
      create: {
        hostId: plan.hostId,
        tenantId: plan.tenantId,
        planId: plan.id,
        type: RecallBriefOrchestrator.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 };
  }
}
