import { Injectable, Logger } from '@nestjs/common';
import { streamText, tool, jsonSchema, stepCountIs, type ModelMessage, type ToolSet } from 'ai';
import { AiProviderService } from '../ai/core/ai-provider.service';
import { McpClientService } from './mcp-client.service';

const SYSTEM_PROMPT = `你是 PAC(疗效保障 / 患者分析中心)的患者召回助手,正在模拟"外部 agent"通过工具访问 PAC。

你有一组工具可以查询患者数据。使用建议:
- 用户给患者姓名/手机/患者号时,先用 find_patient 拿到 patientId,再继续。
- 要患者全景就用 get_patient_overview(一次给画像+事实+召回计划);要细节再用 get_persona / get_facts / get_recall_plan。
- 问"现在该联系谁 / 召回池"用 list_recall_queue。

铁律:只依据工具返回的真实数据回答,绝不编造;工具没返回的就说"没有该信息"。手机号只显示掩码。
用中文,简洁专业。回答时可简述你查了哪些数据。`;

export interface AssistantChatInput {
  userToken: string;
  modelId?: string;
  messages: ModelMessage[];
  abortSignal?: AbortSignal;
}

/**
 * AssistantService — 独立的"外部 agent"模拟器(不复用 AiCall 单发框架)。
 *
 * 模型自主决定调哪些工具(model-driven tool-calling,非强制工作流):
 *   动态从 MCP 拉工具 → 包成 AI SDK tool(execute = 回调 MCP)→ streamText 多步循环
 *   (模型出 tool-call → 执行 → 喂回 → 直到产出最终文本)→ 流式返回。
 * provider 可切换(deepseek / gemini / qwen),复用 AiProviderService 配置。
 */
@Injectable()
export class AssistantService {
  private readonly logger = new Logger(AssistantService.name);

  constructor(
    private readonly provider: AiProviderService,
    private readonly mcp: McpClientService,
  ) {}

  // 显式收窄返回类型为控制器实际消费的最小形状,避开 streamText 返回类型引用 ai 内部
  // 未导出的 Output 类型导致的 .d.ts 命名失败(TS4053)。
  async chat(input: AssistantChatInput): Promise<{ fullStream: AsyncIterable<unknown> }> {
    // 1. 动态拉 MCP 工具 → 转成 AI SDK tools(execute 回调真 MCP 调用,带用户 token)
    const mcpTools = await this.mcp.listTools(input.userToken);
    const tools: ToolSet = {};
    for (const t of mcpTools) {
      tools[t.name] = tool({
        description: t.description ?? t.name,
        inputSchema: jsonSchema(t.inputSchema),
        execute: async (args: unknown) => this.mcp.callTool(input.userToken, t.name, args),
      });
    }
    this.logger.log(
      `assistant chat: model=${input.modelId ?? 'deepseek'} tools=${Object.keys(tools).join(',')}`,
    );

    // 2. resolve provider(可切换)+ streamText 跑 model-driven tool-calling 循环
    const { model } = this.provider.resolve(input.modelId ?? 'deepseek');
    return streamText({
      model,
      system: SYSTEM_PROMPT,
      messages: input.messages,
      tools,
      stopWhen: stepCountIs(8), // 防失控:最多 8 步工具循环
      abortSignal: input.abortSignal,
    });
  }
}
