import "server-only"

import OpenAI from "openai"

import { extractMessageContent, type AiChatRequest } from "./payload-parser"
import { getAiProviderConfig } from "./provider-config"

/** AI 请求超时（毫秒）— 用于普通非流式调用 */
const AI_TIMEOUT_MS = 30000

/**
 * 测试连接超时（毫秒）
 *
 * 测试连接需要实际调用 AI 模型生成响应，部分 Provider（如本地 Ollama）
 * 首次加载模型可能耗时 30s 以上，因此使用更长的超时时间。
 */
const AI_TEST_TIMEOUT_MS = 120000

/**
 * 流式响应超时（毫秒）
 *
 * 流式生成内容（特别是长文本）可能需要较长时间，
 * 此超时控制整个流式请求的总时长。
 */
const AI_STREAM_TIMEOUT_MS = 180000

/** 可重试的 HTTP 状态码（429 限流 + 5xx 服务端错误） */
const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504])

/** 最大重试次数 */
const MAX_RETRIES = 2

/** 基础重试延迟（毫秒），实际延迟 = base * 2^attempt */
const RETRY_BASE_DELAY_MS = 1000

interface AiClientOptions {
  apiKey: string
  baseUrl?: string
  /** 请求超时（毫秒），默认 AI_TIMEOUT_MS */
  timeout?: number
}

const getAiClient = async (config: AiClientOptions): Promise<OpenAI> => {
  const baseUrl = String(config.baseUrl ?? "https://api.openai.com").replace(/\/+$/, "")
  return new OpenAI({
    apiKey: config.apiKey,
    baseURL: baseUrl.length ? baseUrl : undefined,
    timeout: config.timeout ?? AI_TIMEOUT_MS,
    maxRetries: 0, // 由业务层控制重试
  })
}

/**
 * 判断错误是否可重试（429 限流或 5xx 服务端错误）
 */
const isRetryableError = (error: unknown): boolean => {
  if (error instanceof OpenAI.APIError) {
    return RETRYABLE_STATUS_CODES.has(error.status ?? 0)
  }
  // 网络超时/连接错误也可重试
  if (error instanceof Error) {
    const msg = error.message.toLowerCase()
    return msg.includes("timeout") || msg.includes("econnreset") || msg.includes("socket hang up")
  }
  return false
}

/**
 * 指数退避重试包装器
 *
 * 仅对 429（限流）和 5xx（服务端错误）重试，4xx 不重试。
 * 最大重试 2 次，延迟 1s/2s/4s。
 */
async function withRetry<T>(fn: () => Promise<T>): Promise<T> {
  let lastError: unknown
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error
      if (attempt < MAX_RETRIES && isRetryableError(error)) {
        const delay = RETRY_BASE_DELAY_MS * Math.pow(2, attempt)
        await new Promise((resolve) => setTimeout(resolve, delay))
        continue
      }
      throw error
    }
  }
  throw lastError
}

export const testAiProviderConfig = async (input: { apiKey: string; baseUrl?: string; model: string }): Promise<boolean> => {
  const client = await getAiClient({
    apiKey: input.apiKey,
    baseUrl: input.baseUrl,
    timeout: AI_TEST_TIMEOUT_MS,
  })
  const result = await client.chat.completions.create({
    model: input.model,
    messages: [{ role: "user", content: "ping" }],
    temperature: 0,
    max_tokens: 1,
  } as Parameters<typeof client.chat.completions.create>[0])
  const hasChoices = "choices" in result && Array.isArray(result.choices) && result.choices.length > 0
  if (!hasChoices) throw new Error("Empty response from provider. Check API URL, model, and API key.")
  return true
}

export const testAiProviderById = async (
  providerId: string,
  overrides?: { baseUrl?: string; model?: string }
): Promise<boolean> => {
  const config = await getAiProviderConfig(providerId)
  const client = await getAiClient({
    apiKey: config.apiKey,
    baseUrl: overrides?.baseUrl ?? config.baseUrl,
    timeout: AI_TEST_TIMEOUT_MS,
  })
  const result = await client.chat.completions.create({
    model: overrides?.model ?? config.model,
    messages: [{ role: "user", content: "ping" }],
    temperature: 0,
    max_tokens: 1,
  } as Parameters<typeof client.chat.completions.create>[0])
  const hasChoices = "choices" in result && Array.isArray(result.choices) && result.choices.length > 0
  if (!hasChoices) throw new Error("Empty response from provider. Check API URL, model, and API key.")
  return true
}

export const createAiChatCompletion = async (input: AiChatRequest): Promise<{ content: string; usage: unknown }> => {
  const config = await getAiProviderConfig(input.providerId)
  const client = await getAiClient(config)

  const result = await withRetry(() =>
    client.chat.completions.create({
      model: config.model || input.model,
      messages: input.messages,
      temperature: input.temperature,
      ...(typeof input.maxTokens === "number" ? { max_tokens: input.maxTokens } : {}),
      ...(input.thinking ? { thinking: input.thinking } : {}),
    } as Parameters<typeof client.chat.completions.create>[0])
  ) as Awaited<ReturnType<typeof client.chat.completions.create>>

  const hasChoices = "choices" in result && Array.isArray(result.choices) && result.choices.length > 0
  if (!hasChoices) throw new Error("Empty response from provider. Check API URL, model, and API key.")

  const content = extractMessageContent(result.choices?.[0]?.message)
  if (!content.trim()) throw new Error("Empty response content. Check model output settings.")

  const usage = "usage" in result ? result.usage ?? null : null
  return { content, usage }
}

/**
 * 流式 AI 聊天补全
 *
 * 返回 AsyncGenerator，逐 token 产出内容。
 * 用于 SSE 流式响应，降低用户感知延迟。
 *
 * 注意：流式调用不使用 withRetry，因为流一旦开始无法重试。
 * 超时由 OpenAI SDK 的 timeout 配置控制（使用 AI_STREAM_TIMEOUT_MS）。
 */
export async function* createAiChatCompletionStream(
  input: AiChatRequest
): AsyncGenerator<string, void, unknown> {
  const config = await getAiProviderConfig(input.providerId)
  const client = await getAiClient({
    apiKey: config.apiKey,
    baseUrl: config.baseUrl,
    timeout: AI_STREAM_TIMEOUT_MS,
  })
  const stream = await client.chat.completions.create({
    model: config.model || input.model,
    messages: input.messages,
    temperature: input.temperature,
    ...(typeof input.maxTokens === "number" ? { max_tokens: input.maxTokens } : {}),
    stream: true,
  })

  for await (const chunk of stream) {
    const delta = chunk.choices?.[0]?.delta
    const content = extractMessageContent(delta)
    if (content) {
      yield content
    }
  }
}
