/**
 * 统一的 LLM 服务 - Provider Registry 模式
 *
 * 架构改进:
 * 1. 供应商级别的 Provider 抽象（ILlmProvider）
 * 2. 注册表管理（ProviderRegistry），统一调度和熔断
 * 3. 公共 API 保持向后兼容（callLLM, callLLMWithMessages, callLLMWithTools, callLLMStream）
 *
 * 供应商优先级由 models.json 中的 priority 字段决定。
 * 调度策略: 同供应商 Key 轮转 → 跨供应商降级（优先同名/别名模型）
 */

import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, SystemMessage, AIMessage, BaseMessage, ToolMessage } from '@langchain/core/messages';
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { withAiLog } from '../ai-call-logger';
import { config } from '../../config';
import {
  getLlmRegistry,
  findProviderNodeForModel,
  getToolCapableModels,
  getNextToolCapableModel as getNextToolCapable,
  getDefaultModelId,
  initLlmRegistry,
  startHealthCheck,
} from './provider.registry';
import { ILlmProvider } from './provider.interface';
import { CircuitBreakerOpenError } from '../../common/circuit-breaker';
import { cleanLlmResponse } from './response-cleaner';

/** 检测额度耗尽错误的错误消息模式 */
const EXHAUSTED_PATTERNS = [
  'quota', 'balance', 'insufficient', '额度', '余额', '用量',
  'rate limit', 'too many requests',
];

export type ChatMessage = {
  role: 'system' | 'user' | 'assistant';
  content: string;
};

export type ToolDefinition = {
  name: string;
  description: string;
  inputSchema: z.ZodObject<any>;
  execute: (input: any) => Promise<any>;
};

export type ToolCallResult = {
  text: string;
  toolCalls: Array<{
    name: string;
    input: any;
    result: any;
  }>;
};

// 模型缓存（按 modelId 缓存 ChatOpenAI 实例）
const modelCache: Map<string, ChatOpenAI> = new Map();

// ============ 供应商/模型选择调度 ============

/**
 * 文本模型选择策略
 */
type SelectionStrategy = 'round-robin' | 'random' | 'weighted';

/**
 * 选择文本模型
 * @param preferredModel 用户偏好的模型名（可选）
 * @param strategy 选择策略，默认 round-robin
 * @returns 模型ID 或 null
 */
export function selectTextModel(
  preferredModel?: string,
  strategy: SelectionStrategy = 'round-robin'
): string | null {
  const registry = getLlmRegistry();
  const available = registry.listAvailable();

  if (available.length === 0) {
    console.warn('[LLM] 没有可用的文本模型供应商');
    return null;
  }

  // 按策略选择供应商
  let vendorNode = available[0];
  if (strategy === 'random') {
    vendorNode = available[Math.floor(Math.random() * available.length)];
  } else if (strategy === 'round-robin') {
    // 轮转：基于时间戳简单轮转
    const idx = Math.floor(Date.now() / 1000) % available.length;
    vendorNode = available[idx];
  }
  // weighted 暂未实现

  // 优先用用户指定的模型
  if (preferredModel && vendorNode.provider.hasModel(preferredModel)) {
    return preferredModel;
  }

  // 检查该供应商是否支持工具调用
  const toolModels = vendorNode.provider.textModels.filter(id => {
    const cfg = vendorNode.provider.getModelConfig(id);
    return cfg?.supportsToolCall;
  });

  // 优先选支持工具调用的模型
  if (toolModels.length > 0) {
    return toolModels[0];
  }

  // 否则选第一个文本模型
  const firstModel = vendorNode.provider.textModels[0];
  return firstModel || null;
}

/**
 * 获取文本模型的供应商节点
 */
function findVendorForModel(modelId: string) {
  const registry = getLlmRegistry();
  return registry.listAvailable().find(n => n.provider.hasModel(modelId));
}

// ============ 内部辅助 ============

/**
 * 创建 ChatOpenAI 实例
 * @param modelId  模型 ID
 * @param options  覆盖参数（temperature, maxTokens）
 */
function createClient(modelId: string, options?: { temperature?: number; maxTokens?: number }): ChatOpenAI {
  const node = findVendorForModel(modelId);
  if (!node) throw new Error(`没有找到模型 ${modelId} 所属的供应商`);

  const provider = node.provider;
  return provider.createClient(modelId, options);
}

/**
 * 获取 ChatOpenAI 实例（带缓存）
 * - 未指定 maxTokens 时使用缓存实例
 * - 指定 maxTokens 时创建新实例（不缓存）
 */
export function getLLM(modelId?: string, maxTokens?: number): ChatOpenAI {
  const id = modelId || getDefaultModelId();
  if (maxTokens !== undefined) {
    return createClient(id, { maxTokens });
  }
  if (!modelCache.has(id)) {
    modelCache.set(id, createClient(id));
  }
  return modelCache.get(id)!;
}

/**
 * 获取支持工具调用的模型
 */
export function getToolCapableModel(preferredModelId?: string): ChatOpenAI {
  if (preferredModelId) {
    const node = findVendorForModel(preferredModelId);
    const cfg = node?.provider.getModelConfig(preferredModelId);
    if (cfg?.supportsToolCall) return getLLM(preferredModelId);
  }

  const toolModels = getToolCapableModels();
  if (toolModels.length === 0) throw new Error('没有可用的支持工具调用的模型');

  // 使用轮转策略选择一个支持工具调用的模型
  const selectedModelId = selectTextModel(undefined, 'round-robin');
  if (!selectedModelId) throw new Error('没有可用的文本模型');
  return getLLM(selectedModelId);
}

/**
 * 获取下一个支持工具调用的模型（用于自动切换）
 */
export function getNextToolCapableModel(currentModelId: string): string | null {
  return getNextToolCapable(currentModelId);
}

// ============ 消息格式转换 ============

function toBaseMessages(messages: ChatMessage[]): BaseMessage[] {
  return messages.map((m) => {
    if (m.role === 'system') return new SystemMessage(m.content);
    if (m.role === 'assistant') return new AIMessage(m.content);
    return new HumanMessage(m.content);
  });
}

// ============ 核心调用接口（向后兼容） ============

/** 重试延迟（ms） */
const RETRY_DELAY_MS = 2000;

/**
 * 根据 modelId 解析出实际的供应商 key
 */
function resolveProviderKey(modelId: string): string {
  const cfg = (config.models as any).getModel?.(modelId);
  if (cfg?.vendor) return cfg.vendor;
  // fallback: 从 registry 查找
  const registry = getLlmRegistry();
  for (const node of registry.listEnabled()) {
    if (node.provider.hasModel(modelId)) return node.provider.vendor;
  }
  return modelId;
}

/**
 * 带重试的调用包装
 * 失败后先同模型重试1次（2s延迟），仍失败再切换供应商
 */
async function invokeWithRetry<T>(
  fn: () => Promise<T>,
  modelId: string,
  onSwitch: (nextModelId: string) => Promise<T>,
  callType: string = 'llm_chat',
): Promise<T> {
  const provider = resolveProviderKey(modelId);

  // 第1次尝试
  try {
    return await withAiLog(fn, { callType, provider, model: modelId });
  } catch (error: any) {
    // 不可切换的错误，直接抛出
    if (!config.models.shouldSwitchModel(error?.message || '')) {
      throw error;
    }

    // 可切换错误：先重试1次（可能是瞬时波动）
    console.log(`[LLM] ${modelId} 调用失败，2s后重试...`);
    await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
    try {
      // 第2次尝试（同模型重试，记录日志）
      const retryResult = await withAiLog(
        fn,
        { callType: `${callType}_retry1`, provider, model: modelId }
      );
      return retryResult;
    } catch (retryError: any) {
      // 重试仍失败，切换供应商
      const nextModel = trySwitchModel(modelId, retryError);
      if (nextModel) {
        return onSwitch(nextModel);
      }
      throw retryError;
    }
  }
}

/**
 * 原始字符串调用（向后兼容）
 * 内部使用注册表 + 熔断器实现自动降级
 */
export async function callLLM(prompt: string, modelId?: string): Promise<string> {
  const id = modelId || getDefaultModelId();
  return invokeWithRetry(
    async () => {
      const llm = getLLM(id);
      const response = await llm.invoke(prompt);
      return cleanLlmResponse(response.content as string);
    },
    id,
    (nextModel) => callLLM(prompt, nextModel),
  );
}

/**
 * 消息格式调用 - 支持 system/user/assistant 角色
 */
export async function callLLMWithMessages(
  messages: ChatMessage[],
  modelId?: string,
  maxTokens?: number
): Promise<string> {
  const id = modelId || getDefaultModelId();

  // 请求日志
  console.log('[AI请求] ===== 开始 =====');
  console.log('[AI请求] 模型:', id, maxTokens !== undefined ? `(maxTokens=${maxTokens})` : '');
  console.log('[AI请求] 消息数:', messages.length);
  const systemMsg = messages.find(m => m.role === 'system');
  if (systemMsg) {
    console.log('[AI请求] System提示词 (前200字):', systemMsg.content.substring(0, 200));
  }

  // 解析 provider
  const provider = resolveProviderKey(id);

  try {
    const llm = getLLM(id, maxTokens);
    const baseMessages = toBaseMessages(messages);
    const prompt = messages.map(m => `${m.role}: ${m.content}`).join('\n');
    const response = await withAiLog(
      () => llm.invoke(baseMessages as any),
      { callType: 'llm_chat', provider, model: id, textLen: messages.reduce((s, m) => s + (m.content?.length || 0), 0), prompt }
    );

    const responseContent = cleanLlmResponse(response.content as string);
    console.log('[AI响应] 内容 (前1000字):', responseContent.substring(0, 1000));
    if (responseContent.length > 1000) {
      console.log('[AI响应] ... (总长度:', responseContent.length, '字)');
    }
    console.log('[AI响应] ===== 结束 =====');

    return responseContent;
  } catch (error: any) {
    // 不可切换的错误，直接抛出
    if (!config.models.shouldSwitchModel(error?.message || '')) {
      throw error;
    }

    // 可切换错误：先重试1次
    console.log(`[LLM] ${id} 消息调用失败，2s后重试...`);
    await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
    try {
      const llm = getLLM(id, maxTokens);
      const baseMessages = toBaseMessages(messages);
      const prompt = messages.map(m => `${m.role}: ${m.content}`).join('\n');
      const response = await withAiLog(
        () => llm.invoke(baseMessages as any),
        { callType: 'llm_chat_retry', provider, model: id, textLen: messages.reduce((s, m) => s + (m.content?.length || 0), 0), prompt }
      );
      const responseContent = cleanLlmResponse(response.content as string);
      console.log('[AI响应] 重试成功 (前1000字):', responseContent.substring(0, 1000));
      console.log('[AI响应] ===== 结束 =====');
      return responseContent;
    } catch (retryError: any) {
      const nextModel = trySwitchModel(id, retryError);
      if (nextModel) {
        console.log(`[LLM] ${id} 重试仍失败，切换到 ${nextModel}`);
        return callLLMWithMessages(messages, nextModel, maxTokens);
      }
      throw retryError;
    }
  }
}

/**
 * 工具调用 - 让 LLM 能调用外部工具
 * 支持模型自动切换
 */
export async function callLLMWithTools(
  messages: ChatMessage[],
  tools: ToolDefinition[],
  modelId?: string,
  maxRounds = 5,
  maxTokens?: number
): Promise<ToolCallResult> {
  const effectiveModelId = modelId || getDefaultModelId();

  if (!effectiveModelId) {
    throw new Error('没有可用的支持工具调用的模型');
  }

  return callLLMWithToolsImpl(messages, tools, effectiveModelId, maxRounds, undefined, maxTokens);
}

async function callLLMWithToolsImpl(
  messages: ChatMessage[],
  tools: ToolDefinition[],
  modelId: string,
  maxRounds: number,
  attemptedModels: Set<string> = new Set(),
  maxTokens?: number
): Promise<ToolCallResult> {
  if (attemptedModels.has(modelId)) {
    const nextModel = getNextToolCapableModel(modelId);
    if (nextModel && !attemptedModels.has(nextModel)) {
      return callLLMWithToolsImpl(messages, tools, nextModel, maxRounds, attemptedModels, maxTokens);
    }
    throw new Error('所有支持工具调用的模型都不可用');
  }
  attemptedModels.add(modelId);

  let llm: ChatOpenAI;
  try {
    llm = getLLM(modelId, maxTokens);
  } catch (err) {
    const nextModel = getNextToolCapableModel(modelId);
    if (nextModel && !attemptedModels.has(nextModel)) {
      return callLLMWithToolsImpl(messages, tools, nextModel, maxRounds, attemptedModels, maxTokens);
    }
    throw err;
  }

  const lcTools = tools.map((t) =>
    tool(t.execute, {
      name: t.name,
      description: t.description,
      schema: t.inputSchema,
    })
  );

  const llmWithTools = llm.bindTools(lcTools as any);
  const conversationMessages: any[] = toBaseMessages(messages);
  const toolCallResults: ToolCallResult['toolCalls'] = [];

  for (let round = 0; round < maxRounds; round++) {
    let response: any;
    try {
      const prompt = conversationMessages.map(m => `${m._getType()}: ${m.content}`).join('\n');
      response = await withAiLog(
        () => llmWithTools.invoke(conversationMessages as any),
        { callType: 'llm_tools', provider: resolveProviderKey(modelId), model: modelId, prompt }
      );
    } catch (err: any) {
      console.warn(`[LLM] 模型 ${modelId} 调用失败: ${err?.message}，尝试下一个模型`);
      const nextModel = getNextToolCapableModel(modelId);
      if (nextModel && !attemptedModels.has(nextModel)) {
        return callLLMWithToolsImpl(messages, tools, nextModel, maxRounds, attemptedModels);
      }
      throw err;
    }
    conversationMessages.push(response);

    if (!response.tool_calls || response.tool_calls.length === 0) {
      return {
        text: response.content as string,
        toolCalls: toolCallResults,
      };
    }

    for (const toolCall of response.tool_calls) {
      const toolDef = tools.find((t) => t.name === toolCall.name);
      if (!toolDef) continue;

      console.log(`[LLM] 工具调用: ${toolCall.name}`, toolCall.args);
      const result = await toolDef.execute(toolCall.args);
      const resultStr = typeof result === 'string' ? result : JSON.stringify(result);

      toolCallResults.push({ name: toolCall.name, input: toolCall.args, result });
      conversationMessages.push(new ToolMessage({ content: resultStr, tool_call_id: toolCall.id! }));
    }
  }

  const lastMsg = conversationMessages[conversationMessages.length - 1];
  return {
    text: typeof lastMsg.content === 'string' ? lastMsg.content : '',
    toolCalls: toolCallResults,
  };
}

/**
 * 流式调用
 */
export async function* callLLMStream(
  prompt: string,
  modelId?: string
): AsyncGenerator<string> {
  const id = modelId || getDefaultModelId();
  // 流式调用也加重试
  try {
    const llm = getLLM(id);
    const stream = await llm.stream(prompt);
    const chunks: string[] = [];
    for await (const chunk of stream) {
      const content = chunk.content as string;
      chunks.push(content);
      yield content;
    }
  } catch (error: any) {
    if (!config.models.shouldSwitchModel(error?.message || '')) {
      throw error;
    }
    // 流式无法完美重试（已部分yield），直接切换供应商
    const nextModel = trySwitchModel(id, error);
    if (nextModel) {
      yield* callLLMStream(prompt, nextModel);
      return;
    }
    throw error;
  }
}

/**
 * 获取可用模型列表
 */
export function getAvailableModels() {
  return config.models.getModelsByType('text');
}

/**
 * 清除模型缓存
 */
export function clearModelCache() {
  modelCache.clear();
}

// ============ 供应商级别故障切换 ============

/**
 * 检查错误是否需要切换供应商
 * 调度策略：同 Key 换模型无意义（额度共享），直接切换到下一个供应商
 * 优先查找同名/别名模型（canonicalModel），保持模型一致性
 */
function trySwitchModel(currentModelId: string, error: any): string | null {
  const errorMessage = error?.message || '';
  const registry = getLlmRegistry();

  // 1. 额度耗尽检测：标记该供应商并切换（4小时自动恢复）
  if (EXHAUSTED_PATTERNS.some(p => errorMessage.toLowerCase().includes(p))) {
    const currentNode = findProviderNodeForModel(currentModelId);
    if (currentNode) {
      // 4小时 TTL 自动恢复
      registry.markExhausted(currentNode.provider.name, errorMessage, 4 * 60 * 60 * 1000);
    }
  }

  // 2. 熔断器开启，需切换供应商
  if (error instanceof CircuitBreakerOpenError) {
    return switchToNextVendorModel(currentModelId);
  }

  // 3. 可恢复错误，直接切换到下一个供应商
  if (config.models.shouldSwitchModel(errorMessage)) {
    return switchToNextVendorModel(currentModelId);
  }

  return null;
}

/**
 * 切换到下一个供应商（平等轮转，无优先级）
 * 优先查找同名/别名模型，保持输出一致性
 * 找不到同名模型时回退到该供应商的第一个文本模型
 */
function switchToNextVendorModel(currentModelId: string): string | null {
  const registry = getLlmRegistry();
  const currentNode = findProviderNodeForModel(currentModelId);
  const available = registry.listAvailable();

  if (available.length === 0) {
    console.warn('[LLM] 所有供应商都不可用（熔断/额度耗尽/禁用）');
    return null;
  }

  // 从当前供应商的下一个开始找
  const startIndex = currentNode
    ? (available.findIndex(n => n.provider.name === currentNode.provider.name) + 1) % available.length
    : 0;

  for (let i = 0; i < available.length; i++) {
    const idx = (startIndex + i) % available.length;
    const node = available[idx];
    if (node === currentNode) continue;

    // 1. 精确匹配：下一个供应商是否也提供同名模型
    if (node.provider.hasModel(currentModelId)) {
      console.log(`[LLM] 同模型切换到供应商 ${node.provider.displayName}，模型 ${currentModelId}`);
      return currentModelId;
    }

    // 2. 别名匹配：下一个供应商中是否有 canonicalModel 指向当前模型的
    for (const modelId of node.provider.textModels) {
      const cfg = node.provider.getModelConfig(modelId);
      if (cfg?.canonicalModel === currentModelId) {
        console.log(`[LLM] 别名模型切换到供应商 ${node.provider.displayName}，模型 ${modelId}`);
        return modelId;
      }
    }

    // 3. 无同名/别名模型，使用该供应商的第一个文本模型
    if (node.provider.textModels.length > 0) {
      const nextId = node.provider.textModels[0];
      console.log(`[LLM] 切换到供应商 ${node.provider.displayName}，模型 ${nextId}`);
      return nextId;
    }
  }

  console.warn('[LLM] 所有供应商都已不可用');
  return null;
}

// ============ 初始化 ============

// 模块加载时自动初始化并启动健康检查
initLlmRegistry();
startHealthCheck();
