/**
 * AI 调用日志 —— 记录每次大模型 / TTS API 请求，方便对账
 *
 * 写入策略：fire-and-forget，不阻塞业务请求
 */

import { prisma } from '../models';
import { TOKEN_COST_CONFIG, PRICING_COEFFICIENT } from '../modules/subscription/subscription.service';
import { getLlmContext } from './llm-context';

export interface LogCallParams {
  callType: string;     // llm_chat | llm_tools | tts_synthesize | tts_create | tts_poll
  provider: string;     // minimax | bailian | volcengine
  model: string;        // MiniMax-M2.7 | cosyvoice-v3-flash | qwen3-tts-instruct-flash
  textLen?: number;     // 输入文本长度（字符）
  prompt?: string;      // 提示词内容（LLM only）
  inputTokens?: number; // 实际输入 Token 数
  outputTokens?: number;// 实际输出 Token 数（LLM）或 文本长度（TTS）
  duration?: number;    // 耗时(ms)
  success?: boolean;
  errorMsg?: string;
  userId?: number;
  bookId?: number;
  chapterId?: number;
}

/**
 * 根据 callType + model + token 数估算成本
 * LLM: 按百万token单价计算
 * TTS: 按万字符单价计算（cosyvoice ¥1/万字符）
 */
function estimateCost(params: LogCallParams): number {
  const inputTokens = params.inputTokens || 0;
  const outputTokens = params.outputTokens || 0;
  const textLen = params.textLen || 0;

  // LLM 调用：按 token 计费
  if (params.callType.startsWith('llm_')) {
    const modelKey = params.model.includes('qwen') ? 'qwen_plus' :
                     params.model.includes('deepseek') ? 'deepseek_v3' : 'qwen_plus';
    const modelConfig = TOKEN_COST_CONFIG.models[modelKey] || TOKEN_COST_CONFIG.models.qwen_plus;
    const inputCost = (inputTokens / 1_000_000) * modelConfig.inputCostPerMToken;
    const outputCost = (outputTokens / 1_000_000) * modelConfig.outputCostPerMToken;
    return Math.round((inputCost + outputCost) * 10000) / 10000;
  }

  // TTS 实际合成调用：按字符数计费（cosyvoice ¥1/万字符）
  // tts_task_completed/failed 是任务汇总日志，不计费
  if (params.callType === 'tts_synthesize' || params.callType === 'tts_create') {
    const chars = outputTokens || textLen;
    return Math.round((chars / 10_000) * 1.0 * 10000) / 10000;
  }

  return 0;
}

/**
 * 从 LangChain AIMessage 响应中提取 token 用量
 * 支持 OpenAI-compatible API（usage.prompt_tokens / completion_tokens）
 */
export function extractTokenUsage(response: any): { inputTokens: number; outputTokens: number } | null {
  try {
    // LangChain AIMessage 包含 response_metadata
    const metadata = response?.response_metadata || response?.lc_kwargs?.response_metadata || {};
    const usage = metadata?.tokenUsage || metadata?.usage || metadata?.token_usage || {};

    // 尝试多种可能的字段名
    const promptTokens = usage?.prompt_tokens || usage?.promptTokens || usage?.input_tokens || usage?.inputTokens;
    const completionTokens = usage?.completion_tokens || usage?.completionTokens || usage?.output_tokens || usage?.outputTokens || usage?.total_tokens;

    if (promptTokens || completionTokens) {
      return {
        inputTokens: promptTokens || 0,
        outputTokens: completionTokens || 0,
      };
    }
  } catch {}

  return null;
}

/**
 * 估算 Token 数（当 API 未返回实际用量时）
 * 中文：1 字 ≈ 1.5 token
 */
function estimateTokens(text: string): number {
  return Math.ceil(text.length * 1.5);
}

/**
 * 记录一次 AI 调用（异步写入，不阻塞）
 */
export function logAiCall(params: LogCallParams): void {
  // 从异步上下文自动填充 userId / bookId / chapterId（如果调用方未显式传入）
  const ctx = getLlmContext();
  if (ctx) {
    if (params.userId === undefined && ctx.userId !== undefined) params.userId = ctx.userId;
    if (params.bookId === undefined && ctx.bookId !== undefined) params.bookId = ctx.bookId;
    if (params.chapterId === undefined && ctx.chapterId !== undefined) params.chapterId = ctx.chapterId;
  }

  const estimatedCost = estimateCost(params);

  console.log(
    `[AiCallLog] ${params.callType} | ${params.provider} | ${params.model} | ` +
    `${params.success ? 'OK' : 'FAIL'} | ` +
    `in:${params.inputTokens || 0}tk out:${params.outputTokens || 0}tk ` +
    `text:${params.textLen || 0}字 | cost:¥${estimatedCost}`
  );

  prisma.aiCallLog.create({
    data: {
      callType: params.callType,
      provider: params.provider,
      model: params.model,
      userId: params.userId ?? null,
      bookId: params.bookId ?? null,
      chapterId: params.chapterId ?? null,
      textLen: params.textLen ?? 0,
      prompt: params.prompt?.substring(0, 5000) ?? null,
      inputTokens: params.inputTokens ?? 0,
      outputTokens: params.outputTokens ?? 0,
      estimatedCost,
      duration: params.duration ?? 0,
      success: params.success ?? true,
      errorMsg: params.errorMsg ?? null,
    },
  }).then(() => {
    // Success
  }).catch(err => {
    console.error('[AiCallLog] ❌ 写入数据库失败:', err.message);
  });
}

/**
 * 包装异步函数，自动计时 + 记录日志 + 提取 token 用量
 *
 * 用法：
 *   const result = await withAiLog(
 *     () => llm.invoke(messages),
 *     { callType: 'llm_chat', provider: 'bailian', model: 'qwen3.6-plus', textLen: 500, prompt }
 *   );
 */
export async function withAiLog<T>(
  fn: () => Promise<T>,
  params: LogCallParams,
): Promise<T> {
  const t0 = Date.now();
  try {
    const result = await fn();

    // 尝试从响应中提取 token 用量
    const tokenUsage = extractTokenUsage(result);
    const logParams: LogCallParams = {
      ...params,
      duration: Date.now() - t0,
    };

    if (tokenUsage) {
      // 有实际 token 数据
      logParams.inputTokens = tokenUsage.inputTokens;
      logParams.outputTokens = tokenUsage.outputTokens;
    } else if (params.callType.startsWith('llm_')) {
      // LLM 调用但无 token 数据：估算
      const promptText = params.prompt || '';
      const outputText = typeof result === 'string' ? result :
        (result as any)?.content && typeof (result as any).content === 'string' ? (result as any).content : '';
      logParams.inputTokens = estimateTokens(promptText);
      logParams.outputTokens = estimateTokens(outputText);
    } else if (params.callType.startsWith('tts_')) {
      // TTS 调用：outputTokens = 文本长度（用于成本计算）
      logParams.outputTokens = params.textLen || 0;
    }

    logAiCall(logParams);
    return result;
  } catch (err: any) {
    logAiCall({ ...params, duration: Date.now() - t0, success: false, errorMsg: err.message });
    throw err;
  }
}

/**
 * 无包装直接记录（用于不需要包裹异步函数的情况）
 */
export function logTtsCall(params: LogCallParams): void {
  // TTS 成本基于文本长度
  const logParams: LogCallParams = {
    ...params,
    outputTokens: params.textLen || 0, // TTS 用 outputTokens 存文本长度
  };
  logAiCall(logParams);
}
