import { Provide, Scope, ScopeEnum, Inject, Logger } from '@midwayjs/core';
import { ILogger } from '@midwayjs/logger';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { NetaClawModelChannelService } from '../service/model_channel.js';
import { DictTypeEntity } from '../../dict/entity/type.js';
import { DictInfoEntity } from '../../dict/entity/info.js';
import { LLMMessage } from '../plugins/plugin_entry.js';
import { getProvider, initDefaultProviders } from './model_selection.js';

/** 辅助模型默认配置键名（存储在 dict 表中） */
const DICT_KEY = 'netaclaw';
const DICT_AUX_CHANNEL = 'auxiliary_channel_id';
const DICT_AUX_MODEL = 'auxiliary_model_id';

/** 摘要调用入参 */
export interface SummarizeParams {
  /** Agent 级辅助渠道ID，不传则使用字典表系统默认 */
  agentAuxChannelId?: number;
  /** 已序列化的被压缩历史文本 */
  serialized: string;
  /** 上一次摘要（存在则走迭代更新模板，否则走首次压缩模板） */
  previousSummary?: string;
  /** 本次摘要的 token 预算（作为 maxTokens 传入 LLM） */
  summaryBudget: number;
}

/** 摘要调用结果 */
export interface SummarizeResult {
  /** 生成的摘要文本 */
  summary: string;
  /** 本次调用的 token 使用情况 */
  usage: { inputTokens: number; outputTokens: number };
}

@Provide()
@Scope(ScopeEnum.Singleton)
export class AuxiliaryLLMClient {
  @Logger()
  logger: ILogger;

  @Inject()
  channelService: NetaClawModelChannelService;

  @InjectEntityModel(DictTypeEntity)
  dictTypeRepo: Repository<DictTypeEntity>;

  @InjectEntityModel(DictInfoEntity)
  dictInfoRepo: Repository<DictInfoEntity>;

  /** 供应商 → LLM Provider 映射（与 model_channel.ts 保持一致） */
  private readonly supplierToProvider: Record<string, string> = {
    openai: 'openai',
    anthropic: 'anthropic',
    deepseek: 'deepseek',
    zhipu: 'openai',
    tongyi: 'openai',
    minimax: 'openai',
    volcengine: 'openai',
    ollama: 'openai',
    azure: 'openai',
  };

  /**
   * 从字典表获取系统默认辅助模型配置
   * 查询方式：先查 dict_type 获取 netaclaw 的 id，再用 typeId + name 查 dict_info
   */
  private async getDictValue(name: string): Promise<string | null> {
    const dictType = await this.dictTypeRepo.findOneBy({ key: DICT_KEY });
    if (!dictType) return null;
    const info = await this.dictInfoRepo.findOneBy({ typeId: dictType.id, name });
    return info?.value ?? null;
  }

  /**
   * 解析辅助模型渠道 ID
   * 优先级：Agent 级配置 > 字典表系统默认
   */
  private async resolveChannelId(agentAuxChannelId?: number): Promise<number> {
    if (agentAuxChannelId && agentAuxChannelId > 0) return agentAuxChannelId;
    const chVal = await this.getDictValue(DICT_AUX_CHANNEL);
    if (!chVal) throw new Error('未配置辅助模型渠道，请在 Agent 或字典表中配置');
    return parseInt(chVal, 10);
  }

  /**
   * 调用辅助模型生成摘要（与设计文档契约一致）
   * 返回 { summary, usage }
   */
  async summarize(params: SummarizeParams): Promise<SummarizeResult> {
    const channelId = await this.resolveChannelId(params.agentAuxChannelId);
    const channel = await this.channelService.info(channelId);
    if (!channel) throw new Error(`辅助模型渠道 ${channelId} 不可用`);
    if (channel.status !== 1) throw new Error(`辅助模型渠道 ${channel.name} 已禁用`);

    const models = Array.isArray(channel.models) ? channel.models : [];
    const modelName = typeof models[0] === 'string' ? (models[0] as string) : (models[0] as any)?.name;
    if (!modelName) throw new Error(`辅助模型渠道 ${channelId} 未配置模型`);

    const providerName = this.supplierToProvider[channel.supplier] || 'openai';
    initDefaultProviders();
    const provider = getProvider(providerName);

    const prompt = params.previousSummary
      ? this.buildIterativePrompt(params.previousSummary, params.serialized, params.summaryBudget)
      : this.buildFirstPrompt(params.serialized, params.summaryBudget);

    const response = await provider.chat(
      [{ role: 'user', content: prompt }],
      [],
      {
        provider: providerName,
        model: modelName,
        apiKey: channel.apiKey,
        baseUrl: channel.baseUrl,
        temperature: 0.3,
        maxTokens: params.summaryBudget,
        thinkLevel: 'off',
      } as any,
    );

    const summary = (response.content || '').trim();
    if (summary.length < 50) {
      throw new Error('摘要内容过短，压缩失败');
    }

    return {
      summary,
      usage: {
        inputTokens: response.usage?.inputTokens ?? 0,
        outputTokens: response.usage?.outputTokens ?? 0,
      },
    };
  }

  /** 首次压缩 Prompt 模板（见设计文档第 3 节） */
  private buildFirstPrompt(serialized: string, budget: number): string {
    return `你是一个对话摘要助手。请将下面的多轮对话压缩为一段结构化摘要（不超过 ${budget} tokens），保留：
1. 用户的核心诉求和关键问题
2. 助手已完成的工作、关键结论
3. 调用过的工具及其核心输出
4. 尚未解决的问题

输出格式（Markdown）：

## 对话摘要（由上下文压缩生成）

### 用户目标
...

### 已完成工作
- ...

### 关键发现
- ...

### 待办事项
- ...

---

以下是待压缩的对话：

${serialized}`;
  }
  /** 迭代更新 Prompt 模板（见设计文档第 3 节） */
  private buildIterativePrompt(previousSummary: string, serialized: string, budget: number): string {
    return `你是一个对话摘要助手。下面是同一会话上一次生成的摘要，以及本次新增的对话内容。请在保留上一次摘要关键信息的基础上，融合本次新内容，输出一份更新后的结构化摘要（不超过 ${budget} tokens）。

要求：
1. 保留用户目标不要丢失
2. 合并"已完成工作"，去重但不漏关键里程碑
3. 更新"关键发现"和"待办事项"
4. 如果新增内容让某些旧待办变成已完成，要移动到"已完成工作"

---

【上一次摘要】
${previousSummary}

---

【本次新增的对话】
${serialized}

---

请输出融合后的摘要（Markdown，结构与上次摘要保持一致）：`;
  }
}
