/**
 * LLM 服务模块入口
 * 提供统一的 LLM 服务管理（支持多配置）
 */

import * as fs from 'fs'
import * as path from 'path'
import { randomUUID } from 'crypto'
import { getAiDataDir } from '../../paths'
import type { LLMProvider, ProviderInfo, AIServiceConfig, AIConfigStore } from './types'
import { MAX_CONFIG_COUNT } from './types'
import { aiLogger } from '../logger'
import { encryptApiKey, decryptApiKey, isEncrypted } from './crypto'
import { buildChatLabUserAgentHeaders } from '../../utils/httpHeaders'
import { t } from '../../i18n'
import { completeSimple, type Model as PiModel, type Api as PiApi } from '@mariozechner/pi-ai'

// 新模型系统导出
export { BUILTIN_PROVIDERS, getBuiltinProviderById } from './provider-registry'
export { BUILTIN_MODELS, getBuiltinModelsByProvider, getBuiltinModelById } from './model-catalog'
export {
  loadCustomProviders,
  addCustomProvider,
  updateCustomProvider,
  deleteCustomProvider,
} from './custom-provider-store'
export { loadCustomModels, addCustomModel, updateCustomModel, deleteCustomModel } from './custom-model-store'
export * from './model-types'

// 兼容类型导出
export * from './types'

// ==================== 合并 Registry / Catalog（内置 + 自定义）====================

import { BUILTIN_PROVIDERS, getBuiltinProviderById } from './provider-registry'
import { BUILTIN_MODELS, getBuiltinModelsByProvider } from './model-catalog'
import { loadCustomProviders } from './custom-provider-store'
import { loadCustomModels } from './custom-model-store'
import type { ProviderDefinition, ModelDefinition } from './model-types'

/** 获取完整 provider registry（内置 + 自定义） */
export function getProviderRegistry(): ProviderDefinition[] {
  return [...BUILTIN_PROVIDERS, ...loadCustomProviders()]
}

/** 获取完整 model catalog（内置 + 自定义） */
export function getModelCatalog(): ModelDefinition[] {
  return [...BUILTIN_MODELS, ...loadCustomModels()]
}

/** 获取指定 provider 下的全部模型（内置 + 自定义） */
export function getModelsByProvider(providerId: string): ModelDefinition[] {
  return [...getBuiltinModelsByProvider(providerId), ...loadCustomModels().filter((m) => m.providerId === providerId)]
}

/** 按 id 查找 provider（内置优先） */
export function getProviderDefinitionById(id: string): ProviderDefinition | null {
  return getBuiltinProviderById(id) || loadCustomProviders().find((p) => p.id === id) || null
}

/** 按 providerId + modelId 查找模型定义（内置优先，再查自定义，最后跨 provider 兜底） */
export function findModelDefinition(providerId: string, modelId: string): ModelDefinition | null {
  return (
    getBuiltinModelById(providerId, modelId) ||
    loadCustomModels().find((m) => m.providerId === providerId && m.id === modelId) ||
    BUILTIN_MODELS.find((m) => m.id === modelId) ||
    loadCustomModels().find((m) => m.id === modelId) ||
    null
  )
}

function providerDefinitionToInfo(def: ProviderDefinition): ProviderInfo {
  const models = getBuiltinModelsByProvider(def.id)
  return {
    id: def.id,
    name: def.name,
    defaultBaseUrl: def.defaultBaseUrl,
    models: models
      .filter((m) => !m.capabilities.includes('embedding') && !m.capabilities.includes('ranking'))
      .map((m) => ({ id: m.id, name: m.name, description: m.description })),
  }
}

export const PROVIDERS: ProviderInfo[] = BUILTIN_PROVIDERS.map(providerDefinitionToInfo)

// 配置文件路径
let CONFIG_PATH: string | null = null

function getConfigPath(): string {
  if (CONFIG_PATH) return CONFIG_PATH
  CONFIG_PATH = path.join(getAiDataDir(), 'llm-config.json')
  return CONFIG_PATH
}

// ==================== 旧配置格式（用于迁移）====================

interface LegacyStoredConfig {
  provider: LLMProvider
  apiKey: string
  model?: string
  maxTokens?: number
}

function isLegacyConfig(data: unknown): data is LegacyStoredConfig {
  if (!data || typeof data !== 'object') return false
  const obj = data as Record<string, unknown>
  return 'provider' in obj && 'apiKey' in obj && !('configs' in obj)
}

function migrateLegacyConfig(legacy: LegacyStoredConfig): AIConfigStore {
  const now = Date.now()
  const providerDef = getBuiltinProviderById(legacy.provider)
  const newConfig: AIServiceConfig = {
    id: randomUUID(),
    name: providerDef?.name || legacy.provider,
    provider: legacy.provider,
    apiKey: legacy.apiKey,
    model: legacy.model,
    maxTokens: legacy.maxTokens,
    createdAt: now,
    updatedAt: now,
  }

  return {
    configs: [newConfig],
    defaultAssistant: { configId: newConfig.id, modelId: newConfig.model || '' },
    fastModel: null,
  }
}

// ==================== Schema 版本迁移 ====================

import { addCustomProvider as _addCustomProviderDirect } from './custom-provider-store'
import { addCustomModel as _addCustomModelDirect } from './custom-model-store'
import { getBuiltinModelById } from './model-catalog'

const CURRENT_SCHEMA_VERSION = 3

/**
 * MiniMax 等旧 provider 的兼容映射表
 * 这些旧 provider 不在新 BUILTIN_PROVIDERS 中，需要自动迁移为自定义 provider
 */
const LEGACY_PROVIDER_FALLBACKS: Record<string, { name: string; defaultBaseUrl: string }> = {
  minimax: { name: 'MiniMax', defaultBaseUrl: 'https://api.minimaxi.com/v1' },
}

/**
 * 将旧 AIConfigStore (schemaVersion=1 或无) 迁移到 schemaVersion=2
 * - provider 在新 registry 中存在 → 保持不变
 * - provider 不存在 → 自动创建自定义 provider
 * - model 不在 catalog 中 → 自动创建自定义 model
 * - disableThinking / isReasoningModel → 不迁移（兼容期通过 compat 字段保留）
 */
function migrateToSchemaV2(store: AIConfigStore): AIConfigStore {
  aiLogger.info('LLM', 'Migrating config store to schema v2')

  for (const config of store.configs) {
    const providerId = config.provider

    if (!getBuiltinProviderById(providerId)) {
      const fallback = LEGACY_PROVIDER_FALLBACKS[providerId]
      if (fallback) {
        try {
          _addCustomProviderDirect({
            name: fallback.name,
            kind: 'openai-compatible',
            defaultBaseUrl: fallback.defaultBaseUrl,
            authMode: 'api-key',
            supportsCustomModels: true,
            modelIds: [],
          })
          aiLogger.info('LLM', `Created custom provider for legacy provider: ${providerId}`)
        } catch {
          // already exists
        }
      }
    }

    if (config.model && getBuiltinProviderById(providerId)) {
      const modelInCatalog = getBuiltinModelById(providerId, config.model)
      if (!modelInCatalog) {
        try {
          _addCustomModelDirect({
            id: config.model,
            providerId,
            name: config.model,
            capabilities: ['chat'],
            recommendedFor: ['chat'],
            status: 'stable',
          })
          aiLogger.info('LLM', `Created custom model "${config.model}" under provider "${providerId}"`)
        } catch {
          // already exists
        }
      }
    }
  }

  return {
    ...store,
    configs: store.configs.map((c) => {
      const { disableThinking: _dt, isReasoningModel: _rm, ...rest } = c as AIServiceConfig & Record<string, unknown>
      return rest as AIServiceConfig
    }),
  }
}

/**
 * Schema v2 → v3：activeConfigId → defaultAssistant { configId, modelId }
 */
function migrateToSchemaV3(store: AIConfigStore & { activeConfigId?: string | null }): AIConfigStore {
  aiLogger.info('LLM', 'Migrating config store to schema v3 (dual-slot model selection)')
  const legacyActiveId = store.activeConfigId ?? null
  const resolvedConfig =
    legacyActiveId && store.configs.find((c) => c.id === legacyActiveId)
      ? store.configs.find((c) => c.id === legacyActiveId)!
      : (store.configs[0] ?? null)

  return {
    configs: store.configs,
    defaultAssistant: resolvedConfig ? { configId: resolvedConfig.id, modelId: resolvedConfig.model || '' } : null,
    fastModel: null,
  }
}

/** 解析 ModelSlot：如果 configId 无效，回退到 configs[0] */
function resolveSlot(
  slot: import('./model-types').ModelSlot | null | undefined,
  configs: AIServiceConfig[]
): import('./model-types').ModelSlot | null {
  if (slot && configs.some((c) => c.id === slot.configId)) return slot
  const fallback = configs[0]
  return fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null
}

// ==================== 多配置管理 ====================

/**
 * 加载配置存储（自动处理迁移和解密）
 */
export function loadConfigStore(): AIConfigStore {
  const configPath = getConfigPath()

  if (!fs.existsSync(configPath)) {
    return { configs: [], defaultAssistant: null, fastModel: null }
  }

  try {
    const content = fs.readFileSync(configPath, 'utf-8')
    const data = JSON.parse(content)

    if (isLegacyConfig(data)) {
      aiLogger.info('LLM', 'Old config format detected, migrating')
      const migrated = migrateLegacyConfig(data)
      saveConfigStore(migrated)
      return loadConfigStore()
    }

    let store = data as AIConfigStore & { schemaVersion?: number; activeConfigId?: string | null }
    let needsSchemaSave = false

    // Schema v1 → v2 迁移
    if (!store.schemaVersion || store.schemaVersion < 2) {
      store = { ...migrateToSchemaV2(store), schemaVersion: 2 } as typeof store
      needsSchemaSave = true
    }

    // Schema v2 → v3 迁移
    if ((store.schemaVersion ?? 0) < 3) {
      store = { ...migrateToSchemaV3(store), schemaVersion: CURRENT_SCHEMA_VERSION } as typeof store
      needsSchemaSave = true
    }

    let needsEncryptionMigration = false
    const decryptedConfigs = store.configs.map((config) => {
      if (config.apiKey && !isEncrypted(config.apiKey)) {
        needsEncryptionMigration = true
        aiLogger.info('LLM', `Config "${config.name}" API Key needs encryption migration`)
      }
      return {
        ...config,
        apiKey: config.apiKey ? decryptApiKey(config.apiKey) : '',
      }
    })

    if (needsEncryptionMigration || needsSchemaSave) {
      aiLogger.info('LLM', 'Saving migrated config store')
      saveConfigStoreRaw({
        ...store,
        configs: store.configs.map((config) => ({
          ...config,
          apiKey: config.apiKey ? encryptApiKey(decryptApiKey(config.apiKey)) : '',
        })),
      })
    }

    return {
      ...store,
      configs: decryptedConfigs,
    }
  } catch (error) {
    aiLogger.error('LLM', 'Failed to load configs', error)
    return { configs: [], defaultAssistant: null, fastModel: null }
  }
}

/**
 * 保存配置存储（自动加密 API Key）
 */
export function saveConfigStore(store: AIConfigStore): void {
  const encryptedStore: AIConfigStore = {
    ...store,
    configs: store.configs.map((config) => ({
      ...config,
      apiKey: config.apiKey ? encryptApiKey(config.apiKey) : '',
    })),
  }
  saveConfigStoreRaw(encryptedStore)
}

function saveConfigStoreRaw(store: AIConfigStore): void {
  const configPath = getConfigPath()
  const dir = path.dirname(configPath)

  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true })
  }

  fs.writeFileSync(configPath, JSON.stringify(store, null, 2), 'utf-8')
}

export function getAllConfigs(): AIServiceConfig[] {
  return loadConfigStore().configs
}

/** 获取默认助手 slot（含 configId + modelId） */
export function getDefaultAssistantSlot(): import('./model-types').ModelSlot | null {
  const store = loadConfigStore()
  return resolveSlot(store.defaultAssistant, store.configs)
}

/** 获取默认助手模型配置（AI 对话、工具调用、SQL 助手、上下文压缩）。自动覆盖 config.model 为 slot.modelId */
export function getDefaultAssistantConfig(): AIServiceConfig | null {
  const store = loadConfigStore()
  const slot = resolveSlot(store.defaultAssistant, store.configs)
  if (!slot) return null
  const config = store.configs.find((c) => c.id === slot.configId)
  if (!config) return null
  return { ...config, model: slot.modelId || config.model }
}

/** 获取快速模型 slot */
export function getFastModelSlot(): import('./model-types').ModelSlot | null {
  const store = loadConfigStore()
  if (store.fastModel === null) return null
  return resolveSlot(store.fastModel, store.configs)
}

/** 获取快速模型配置（会话摘要），未配置时回退到默认助手 */
export function getFastModelConfig(): AIServiceConfig | null {
  const store = loadConfigStore()
  if (store.fastModel === null) return getDefaultAssistantConfig()

  const slot = resolveSlot(store.fastModel, store.configs)
  if (slot) {
    const config = store.configs.find((c) => c.id === slot.configId)
    if (config) return { ...config, model: slot.modelId || config.model }
  }
  return getDefaultAssistantConfig()
}

export function getConfigById(id: string): AIServiceConfig | null {
  const store = loadConfigStore()
  return store.configs.find((c) => c.id === id) || null
}

export function addConfig(config: Omit<AIServiceConfig, 'id' | 'createdAt' | 'updatedAt'>): {
  success: boolean
  config?: AIServiceConfig
  error?: string
} {
  const store = loadConfigStore()

  if (store.configs.length >= MAX_CONFIG_COUNT) {
    return { success: false, error: t('llm.maxConfigs', { count: MAX_CONFIG_COUNT }) }
  }

  const now = Date.now()
  const newConfig: AIServiceConfig = {
    ...config,
    id: randomUUID(),
    createdAt: now,
    updatedAt: now,
  }

  store.configs.push(newConfig)

  if (store.configs.length === 1) {
    store.defaultAssistant = { configId: newConfig.id, modelId: newConfig.model || '' }
  }

  saveConfigStore(store)
  return { success: true, config: newConfig }
}

export function updateConfig(
  id: string,
  updates: Partial<Omit<AIServiceConfig, 'id' | 'createdAt' | 'updatedAt'>>
): { success: boolean; error?: string } {
  const store = loadConfigStore()
  const index = store.configs.findIndex((c) => c.id === id)

  if (index === -1) {
    return { success: false, error: t('llm.configNotFound') }
  }

  store.configs[index] = {
    ...store.configs[index],
    ...updates,
    updatedAt: Date.now(),
  }

  saveConfigStore(store)
  return { success: true }
}

export function deleteConfig(id: string): { success: boolean; error?: string } {
  const store = loadConfigStore()
  const index = store.configs.findIndex((c) => c.id === id)

  if (index === -1) {
    return { success: false, error: t('llm.configNotFound') }
  }

  store.configs.splice(index, 1)

  const fallback = store.configs[0]
  if (store.defaultAssistant?.configId === id) {
    store.defaultAssistant = fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null
  }
  if (store.fastModel?.configId === id) {
    store.fastModel = fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null
  }

  saveConfigStore(store)
  return { success: true }
}

/** 设置默认助手模型（configId + modelId） */
export function setDefaultAssistantModel(configId: string, modelId: string): { success: boolean; error?: string } {
  const store = loadConfigStore()
  const config = store.configs.find((c) => c.id === configId)

  if (!config) {
    return { success: false, error: t('llm.configNotFound') }
  }

  store.defaultAssistant = { configId, modelId }
  saveConfigStore(store)
  return { success: true }
}

/** 设置快速模型（configId + modelId），传 null 表示跟随默认助手 */
export function setFastModel(slot: import('./model-types').ModelSlot | null): { success: boolean; error?: string } {
  const store = loadConfigStore()

  if (slot !== null) {
    const config = store.configs.find((c) => c.id === slot.configId)
    if (!config) {
      return { success: false, error: t('llm.configNotFound') }
    }
  }

  store.fastModel = slot
  saveConfigStore(store)
  return { success: true }
}

export function hasActiveConfig(): boolean {
  return getDefaultAssistantConfig() !== null
}

function validateProviderBaseUrl(provider: LLMProvider, baseUrl?: string): void {
  if (!baseUrl) return

  const normalized = baseUrl.replace(/\/+$/, '')

  if (provider === 'deepseek') {
    if (normalized.endsWith('/chat/completions')) {
      throw new Error('DeepSeek Base URL 请填写到 /v1 层级，不要包含 /chat/completions')
    }
    if (!normalized.endsWith('/v1')) {
      throw new Error('DeepSeek Base URL 需要以 /v1 结尾')
    }
  }

  if (provider === 'qwen') {
    if (normalized.endsWith('/chat/completions')) {
      throw new Error('通义千问 Base URL 请填写到 /v1 层级，不要包含 /chat/completions')
    }
    if (!normalized.endsWith('/v1')) {
      throw new Error('通义千问 Base URL 需要以 /v1 结尾')
    }
    if (normalized.includes('dashscope.aliyuncs.com') && !normalized.includes('/compatible-mode/')) {
      throw new Error('通义千问 Base URL 需要包含 /compatible-mode/v1')
    }
  }
}

export function getProviderInfo(provider: LLMProvider): ProviderInfo | null {
  return PROVIDERS.find((p) => p.id === provider) || null
}

// ==================== pi-ai Model 构建 ====================

/**
 * 规范化 Anthropic baseUrl：Anthropic SDK 内部会拼接 /v1/messages，
 * 因此 baseUrl 不应包含 /v1 后缀，否则会导致 /v1/v1/messages 双重路径。
 */
function normalizeAnthropicBaseUrl(url: string): string {
  return url.replace(/\/v1\/?$/, '')
}

/**
 * 规范化 OpenAI Compatible baseUrl：
 * 用户经常忘记在域名后加 /v1，OpenAI SDK 不会自动补全。
 * 如果 URL 没有以 /v1 结尾且路径部分为空或仅有 /，自动补上。
 * 已有具体路径（如 /api/v1、/proxy）的不做修改。
 */
function normalizeOpenAICompatibleBaseUrl(url: string): string {
  if (!url) return url
  const trimmed = url.replace(/\/+$/, '')
  if (trimmed.endsWith('/v1')) return trimmed
  try {
    const parsed = new URL(trimmed)
    // 仅当路径为空或 "/" 时补全 /v1，避免破坏已有的自定义路径
    if (parsed.pathname === '/' || parsed.pathname === '') {
      return trimmed + '/v1'
    }
  } catch {
    // URL 解析失败，不做处理
  }
  return trimmed
}

const DEFAULT_CONTEXT_WINDOW = 128000

export function buildPiModel(config: AIServiceConfig): PiModel<PiApi> {
  const providerDef = getBuiltinProviderById(config.provider)
  const providerInfo = getProviderInfo(config.provider)
  const baseUrl = config.baseUrl || providerDef?.defaultBaseUrl || providerInfo?.defaultBaseUrl || ''
  const modelId = config.model || providerInfo?.models?.[0]?.id || ''

  validateProviderBaseUrl(config.provider, baseUrl)

  const modelDef = findModelDefinition(config.provider, modelId)
  const contextWindow = modelDef?.contextWindow ?? DEFAULT_CONTEXT_WINDOW

  const BUILTIN_PROVIDER_API: Record<string, PiApi> = {
    gemini: 'google-generative-ai',
    anthropic: 'anthropic-messages',
  }

  const apiFormat: PiApi = (config.apiFormat as PiApi) || BUILTIN_PROVIDER_API[config.provider] || 'openai-completions'

  if (apiFormat === 'google-generative-ai') {
    return {
      id: modelId,
      name: modelId,
      api: 'google-generative-ai',
      provider: 'google',
      baseUrl,
      reasoning: false,
      input: ['text'],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow,
      maxTokens: config.maxTokens ?? 8192,
    }
  }

  if (apiFormat === 'anthropic-messages') {
    return {
      id: modelId,
      name: modelId,
      api: 'anthropic-messages',
      provider: 'anthropic',
      baseUrl: normalizeAnthropicBaseUrl(baseUrl),
      reasoning: false,
      input: ['text'],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow,
      maxTokens: config.maxTokens ?? 8192,
    }
  }

  // openai-compatible + openai-completions/openai-responses：自动补全 /v1（用户经常忘记）
  const resolvedBaseUrl =
    config.provider === 'openai-compatible' && (apiFormat === 'openai-completions' || apiFormat === 'openai-responses')
      ? normalizeOpenAICompatibleBaseUrl(baseUrl)
      : baseUrl

  return {
    id: modelId,
    name: modelId,
    api: apiFormat,
    provider: config.provider,
    baseUrl: resolvedBaseUrl,
    headers: config.provider === 'openai-compatible' ? buildChatLabUserAgentHeaders() : undefined,
    reasoning: config.isReasoningModel ?? false,
    input: ['text'],
    cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
    contextWindow,
    maxTokens: config.maxTokens ?? 4096,
    compat: config.disableThinking ? { thinkingFormat: 'qwen' } : undefined,
  }
}

// ==================== 远程模型列表获取 ====================

export interface RemoteModel {
  id: string
  name: string
  ownedBy?: string
  contextWindow?: number
}

export interface FetchRemoteModelsResult {
  success: boolean
  models?: RemoteModel[]
  error?: string
}

/**
 * 根据 API 格式决定 baseUrl 到 /models 端点的映射：
 * - openai-completions / openai-responses → {resolvedBaseUrl}/models
 * - google-generative-ai → {baseUrl}/v1beta/models?key={apiKey}
 * - anthropic-messages → 不支持
 */
export async function fetchRemoteModels(
  provider: string,
  apiKey: string,
  baseUrl?: string,
  apiFormat?: string
): Promise<FetchRemoteModelsResult> {
  const effectiveApiFormat = apiFormat || 'openai-completions'

  if (effectiveApiFormat === 'anthropic-messages') {
    return { success: false, error: 'Anthropic does not support model listing via API' }
  }

  const rawBaseUrl = baseUrl || getBuiltinProviderById(provider)?.defaultBaseUrl || ''
  if (!rawBaseUrl) {
    return { success: false, error: 'No base URL provided' }
  }

  const abortController = new AbortController()
  const timeout = setTimeout(() => abortController.abort(), 15000)

  try {
    let url: string
    const headers: Record<string, string> = {
      ...buildChatLabUserAgentHeaders(),
    }

    if (effectiveApiFormat === 'google-generative-ai') {
      const trimmed = rawBaseUrl.replace(/\/+$/, '').replace(/\/v1(beta)?$/, '')
      url = `${trimmed}/v1beta/models?key=${apiKey}`
    } else {
      // openai-completions / openai-responses: resolve /v1 auto
      let resolved = rawBaseUrl.replace(/\/+$/, '')
      try {
        const parsed = new URL(resolved)
        if (!resolved.endsWith('/v1') && (parsed.pathname === '/' || parsed.pathname === '')) {
          resolved = resolved + '/v1'
        }
      } catch {
        // ignore
      }
      url = `${resolved}/models`
      headers['Authorization'] = `Bearer ${apiKey}`
    }

    aiLogger.info('LLM', 'Fetching remote models', { url: url.replace(/key=[^&]+/, 'key=***'), provider })

    const response = await fetch(url, {
      method: 'GET',
      headers,
      signal: abortController.signal,
    })

    if (!response.ok) {
      const body = await response.text().catch(() => '')
      return { success: false, error: `HTTP ${response.status}: ${body.slice(0, 200)}` }
    }

    const json = await response.json()

    let models: RemoteModel[]

    if (effectiveApiFormat === 'google-generative-ai') {
      const geminiModels = (json.models || []) as Array<{
        name?: string
        displayName?: string
        inputTokenLimit?: number
      }>
      models = geminiModels.map((m) => {
        const id = (m.name || '').replace(/^models\//, '')
        return {
          id,
          name: m.displayName || id,
          ownedBy: 'google',
          contextWindow: m.inputTokenLimit || undefined,
        }
      })
    } else {
      // OpenAI-standard format: { data: [{ id, owned_by, context_length? }] }
      const data = (json.data || []) as Array<{
        id?: string
        owned_by?: string
        context_length?: number
      }>
      models = data
        .filter((m) => m.id)
        .map((m) => ({
          id: m.id!,
          name: m.id!,
          ownedBy: m.owned_by,
          contextWindow: m.context_length || undefined,
        }))
    }

    aiLogger.info('LLM', `Fetched ${models.length} remote models`, { provider })
    return { success: true, models }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error)
    if (message.includes('aborted') || message.includes('AbortError')) {
      return { success: false, error: 'Request timed out (15s)' }
    }
    return { success: false, error: message }
  } finally {
    clearTimeout(timeout)
  }
}

export async function validateApiKey(
  provider: LLMProvider,
  apiKey: string,
  baseUrl?: string,
  model?: string
): Promise<{ success: boolean; error?: string }> {
  try {
    const providerInfo = getProviderInfo(provider)
    const config: AIServiceConfig = {
      id: 'validate-temp',
      name: 'validate-temp',
      provider,
      apiKey,
      baseUrl,
      model: model || providerInfo?.models?.[0]?.id,
      createdAt: 0,
      updatedAt: 0,
    }
    const piModel = buildPiModel(config)

    const abortController = new AbortController()
    const timeout = setTimeout(() => abortController.abort(), 15000)

    try {
      const result = await completeSimple(
        piModel,
        {
          messages: [{ role: 'user', content: 'Hi', timestamp: Date.now() }],
        },
        {
          apiKey,
          maxTokens: 1,
          signal: abortController.signal,
        }
      )
      if (result.stopReason === 'error' || result.stopReason === 'aborted') {
        return { success: false, error: result.errorMessage || 'Connection failed' }
      }
      return { success: true }
    } finally {
      clearTimeout(timeout)
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error)
    if (message.includes('aborted') || message.includes('AbortError')) {
      return { success: false, error: 'Request timed out (15s)' }
    }
    return { success: false, error: message }
  }
}
