import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawn, spawnSync } from 'node:child_process';
import { Agent, fetch as undiciFetch } from 'undici';

export const CUSTOM_PROVIDER_ID = '__custom__';

export const LLM_PROVIDER_CATALOG = [
  {
    id: CUSTOM_PROVIDER_ID,
    label: '自定义 OpenAI 兼容',
    kind: 'custom',
    apiKeyEnv: null,
    defaultModel: '',
    models: [],
  },
  {
    id: 'custom_deepseek',
    label: 'DeepSeek',
    kind: 'builtin',
    apiKeyEnv: 'DEEPSEEK_API_KEY',
    defaultModel: 'deepseek-chat',
    models: ['deepseek-chat', 'deepseek-reasoner'],
  },
  {
    id: 'openai',
    label: 'OpenAI',
    kind: 'builtin',
    apiKeyEnv: 'OPENAI_API_KEY',
    defaultModel: 'gpt-4o',
    models: ['gpt-4o', 'gpt-4o-mini'],
  },
  {
    id: 'openrouter',
    label: 'OpenRouter',
    kind: 'builtin',
    apiKeyEnv: 'OPENROUTER_API_KEY',
    defaultModel: 'anthropic/claude-sonnet-4',
    models: ['anthropic/claude-sonnet-4', 'openai/gpt-4o'],
  },
  {
    id: 'anthropic',
    label: 'Anthropic',
    kind: 'builtin',
    apiKeyEnv: 'ANTHROPIC_API_KEY',
    defaultModel: 'claude-sonnet-4-20250514',
    models: ['claude-sonnet-4-20250514', 'claude-3-5-haiku-20241022'],
  },
  {
    id: 'custom_qwen',
    label: 'Qwen VL (通义千问)',
    kind: 'builtin',
    apiKeyEnv: null,
    // stored as custom provider; apiUrl is injected at createKey time
    apiUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
    defaultModel: 'qwen-vl-max',
    models: ['qwen-vl-max', 'qwen-vl-max-latest', 'qwen-vl-plus', 'qwen-plus', 'qwen-turbo'],
  },
];

export const LLM_EXECUTOR_CATALOG = [
  {
    id: 'goose',
    label: 'Goose',
    description: '策略判断、分析、总结和轻量任务编排',
    purposes: ['default'],
  },
  {
    id: 'aider',
    label: 'Aider',
    description: '小范围代码修改和补丁式修复',
    purposes: ['default'],
  },
  {
    id: 'openhands',
    label: 'OpenHands',
    description: '复杂仓库任务、多文件改造和命令执行',
    purposes: ['default'],
  },
];

const catalogById = Object.fromEntries(LLM_PROVIDER_CATALOG.map((item) => [item.id, item]));
const executorById = Object.fromEntries(LLM_EXECUTOR_CATALOG.map((item) => [item.id, item]));

const BUILTIN_PROVIDER_TEST_URLS = {
  custom_deepseek: process.env.DEEPSEEK_API_BASE_URL ?? 'https://api.deepseek.com/v1',
  openai: process.env.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1',
  openrouter: process.env.OPENROUTER_API_BASE_URL ?? 'https://openrouter.ai/api/v1',
};

const insecureDispatcher = new Agent({
  connect: { rejectUnauthorized: false },
});

function resolveEncryptionKey(explicitKey) {
  const raw =
    explicitKey ??
    process.env.H5_SETTINGS_ENCRYPTION_KEY ??
    process.env.TKMIND_SERVER__SECRET_KEY ??
    'local-dev-secret';
  return crypto.createHash('sha256').update(raw).digest();
}

export function encryptSecret(plaintext, encryptionKey) {
  const key = resolveEncryptionKey(encryptionKey);
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  return {
    ciphertext: encrypted.toString('base64'),
    iv: iv.toString('base64'),
    tag: cipher.getAuthTag().toString('base64'),
  };
}

export function decryptSecret({ ciphertext, iv, tag }, encryptionKey) {
  const key = resolveEncryptionKey(encryptionKey);
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(iv, 'base64'));
  decipher.setAuthTag(Buffer.from(tag, 'base64'));
  const plain = Buffer.concat([
    decipher.update(Buffer.from(ciphertext, 'base64')),
    decipher.final(),
  ]);
  return plain.toString('utf8');
}

export function maskApiKey(apiKey) {
  if (!apiKey) return '';
  if (apiKey.length <= 8) return '*'.repeat(apiKey.length);
  const head = apiKey.slice(0, 4);
  const tail = apiKey.slice(-4);
  return `${head}${'*'.repeat(Math.max(apiKey.length - 8, 4))}${tail}`;
}

export function parseModelList(raw) {
  if (Array.isArray(raw)) {
    return [...new Set(raw.map((item) => String(item).trim()).filter(Boolean))];
  }
  return [
    ...new Set(
      String(raw ?? '')
        .split(/[\n,]/)
        .map((item) => item.trim())
        .filter(Boolean),
    ),
  ];
}

export function normalizeApiUrl(raw) {
  const trimmed = String(raw ?? '').trim();
  if (!trimmed) return '';
  if (/^https?:\/\//i.test(trimmed)) return trimmed.replace(/\/+$/, '');
  return `http://${trimmed.replace(/\/+$/, '')}`;
}

export function resolveApiBaseUrl(apiUrl) {
  const normalized = normalizeApiUrl(apiUrl);
  if (!normalized) return '';
  if (normalized.endsWith('/chat/completions')) {
    return normalized.slice(0, -'/chat/completions'.length).replace(/\/+$/, '');
  }
  return normalized.replace(/\/+$/, '');
}

export function resolveChatCompletionsUrl(apiUrl) {
  const normalized = normalizeApiUrl(apiUrl);
  if (!normalized) return '';
  if (normalized.endsWith('/chat/completions')) return normalized;
  if (normalized.endsWith('/v1')) return `${normalized}/chat/completions`;
  return `${normalized}/chat/completions`;
}

export const RELAY_BOOTSTRAP = {
  name: process.env.H5_RELAY_BOOTSTRAP_NAME ?? 'Relay Buyer Ollama',
  apiUrl:
    process.env.H5_RELAY_BOOTSTRAP_URL ??
    'http://127.0.0.1:18300/relay/buyer/v1/chat/completions',
  apiKey:
    process.env.H5_RELAY_BOOTSTRAP_API_KEY ??
    'UqyHPKSSEZq0-oPnl8sru-7hZcJ2anPUL1yAVk866Vo',
  models: parseModelList(process.env.H5_RELAY_BOOTSTRAP_MODELS ?? 'qwen2.5:3b'),
  defaultModel: process.env.H5_RELAY_BOOTSTRAP_MODEL ?? 'qwen2.5:3b',
  relayProvider: process.env.H5_RELAY_BOOTSTRAP_PROVIDER ?? 'ollama',
};

export const LOCAL_LLM_FALLBACK = {
  enabled: process.env.H5_LOCAL_LLM_FALLBACK !== '0',
  providerId: process.env.H5_LOCAL_LLM_PROVIDER_ID ?? 'custom_local_ollama_7b',
  displayName: process.env.H5_LOCAL_LLM_NAME ?? 'Local Ollama 7B',
  apiUrl:
    process.env.H5_LOCAL_LLM_URL ?? 'http://127.0.0.1:11434/v1/chat/completions',
  apiKey: process.env.H5_LOCAL_LLM_API_KEY ?? 'ollama',
  model: process.env.H5_LOCAL_LLM_MODEL ?? 'qwen2.5:3b',
};

let cachedLocalFallbackProviderId = null;

export async function testRelayConnection(
  { apiUrl, apiKey, model, relayProvider },
  fetchImpl = undiciFetch,
) {
  const url = resolveChatCompletionsUrl(apiUrl);
  if (!url) return { ok: false, message: 'API 地址无效' };
  if (!apiKey) return { ok: false, message: '缺少 API Key' };
  if (!model) return { ok: false, message: '缺少模型' };

  const started = Date.now();
  const body = {
    model,
    messages: [{ role: 'user', content: 'Hello' }],
    stream: false,
    ...(relayProvider ? { provider: relayProvider } : {}),
  };

  const upstream = await fetchImpl(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify(body),
    dispatcher: url.startsWith('https://') ? insecureDispatcher : undefined,
  });
  const latencyMs = Date.now() - started;
  const text = await upstream.text().catch(() => '');
  if (!upstream.ok) {
    let detail = text.slice(0, 500) || '(空响应)';
    if (detail === '{}') {
      detail = '空 JSON 响应';
    }
    const statusHint =
      upstream.status === 401
        ? 'Bearer Token 无效或已过期'
        : upstream.status === 404
          ? '地址不存在，请检查 API URL'
          : upstream.status === 400
            ? '请求参数被拒绝，请检查 model / provider'
            : `HTTP ${upstream.status}`;
    return {
      ok: false,
      latencyMs,
      message: `Relay ${upstream.status} ${statusHint}：${detail}`,
    };
  }

  let data;
  try {
    data = JSON.parse(text);
  } catch {
    return { ok: false, latencyMs, message: '响应不是 JSON' };
  }

  const reply =
    data?.choices?.[0]?.message?.content ??
    data?.message?.content ??
    data?.output ??
    null;
  return {
    ok: true,
    latencyMs,
    model,
    reply: reply ? String(reply).slice(0, 300) : '(联通成功，无文本内容)',
  };
}

export async function testLocalLlmConnection(fetchImpl = undiciFetch) {
  if (!LOCAL_LLM_FALLBACK.enabled) {
    return { ok: false, message: '本地 LLM fallback 已禁用' };
  }
  return testRelayConnection(
    {
      apiUrl: LOCAL_LLM_FALLBACK.apiUrl,
      apiKey: LOCAL_LLM_FALLBACK.apiKey,
      model: LOCAL_LLM_FALLBACK.model,
      relayProvider: null,
    },
    fetchImpl,
  );
}

function parseModelsJson(raw) {
  if (!raw) return [];
  try {
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parseModelList(parsed) : [];
  } catch {
    return [];
  }
}

function rowToPublic(row, catalogItem, apiKeyForMask) {
  const providerKind = row.provider_kind ?? 'builtin';
  const models =
    providerKind === 'custom'
      ? parseModelsJson(row.models_json)
      : (catalogItem?.models ?? []);
  return {
    id: row.id,
    providerId: row.provider_id,
    providerKind,
    providerLabel:
      providerKind === 'custom'
        ? row.name
        : (catalogItem?.label ?? row.provider_id),
    name: row.name,
    defaultModel: row.default_model,
    models,
    apiUrl: row.api_url ?? null,
    basePath: row.base_path ?? null,
    engine: row.engine ?? 'openai',
    relayProvider: row.relay_provider ?? null,
    goosedProviderId: row.goosed_provider_id ?? null,
    status: row.status,
    isSelected: Boolean(row.is_selected),
    isVisionSelected: Boolean(row.is_vision_selected),
    apiKeyMasked: maskApiKey(apiKeyForMask),
    createdAt: Number(row.created_at),
    updatedAt: Number(row.updated_at),
  };
}

function profileFromRow(row, decryptRow) {
  const providerKind = row.provider_kind ?? 'builtin';
  const models =
    providerKind === 'custom'
      ? parseModelsJson(row.models_json)
      : [row.default_model];
  return {
    providerKind,
    providerId: row.provider_id,
    goosedProviderId: row.goosed_provider_id ?? null,
    name: row.name,
    defaultModel: row.default_model,
    apiKey: decryptRow(row),
    apiUrl: row.api_url ?? null,
    basePath: row.base_path ?? null,
    engine: row.engine ?? 'openai',
    relayProvider: row.relay_provider ?? null,
    models,
  };
}

function normalizeExecutor(raw) {
  const executor = String(raw ?? '').trim().toLowerCase();
  return executorById[executor] ? executor : null;
}

function normalizePurpose(raw) {
  return String(raw ?? 'default').trim() || 'default';
}

function rowToExecutorBinding(row, keyPublic = null) {
  const executor = normalizeExecutor(row.executor);
  const meta = executorById[executor] ?? null;
  return {
    id: row.id,
    executor,
    executorLabel: meta?.label ?? row.executor,
    executorDescription: meta?.description ?? '',
    purpose: row.purpose ?? 'default',
    providerKeyId: row.provider_key_id ?? null,
    providerName: keyPublic?.name ?? null,
    providerLabel: keyPublic?.providerLabel ?? null,
    providerId: keyPublic?.providerId ?? null,
    model: row.model ?? '',
    enabled: Boolean(row.enabled),
    availableModels: keyPublic?.models ?? [],
    createdAt: row.created_at ? Number(row.created_at) : null,
    updatedAt: row.updated_at ? Number(row.updated_at) : null,
  };
}

function executorEnvForProfile(executor, profile, model, { includeSecret = false } = {}) {
  const apiKeyValue = includeSecret ? profile.apiKey : '[hidden]';
  const baseUrl = resolveApiBaseUrl(profile.apiUrl);
  const providerId = profile.providerId;
  const common = {
    TKMIND_EXECUTOR: executor,
    TKMIND_EXECUTOR_PROVIDER: providerId,
    TKMIND_EXECUTOR_MODEL: model,
  };
  if (baseUrl) common.TKMIND_EXECUTOR_API_BASE = baseUrl;
  if (includeSecret) common.TKMIND_EXECUTOR_API_KEY = profile.apiKey;

  if (executor === 'aider') {
    const env = {
      ...common,
      AIDER_MODEL: model,
    };
    if (providerId === 'anthropic') {
      env.AIDER_ANTHROPIC_API_KEY = apiKeyValue;
      env.ANTHROPIC_API_KEY = apiKeyValue;
    } else {
      env.AIDER_OPENAI_API_KEY = apiKeyValue;
      env.OPENAI_API_KEY = apiKeyValue;
      if (providerId === 'openrouter') env.OPENROUTER_API_KEY = apiKeyValue;
      if (providerId === 'custom_deepseek') env.DEEPSEEK_API_KEY = apiKeyValue;
    }
    if (baseUrl) {
      env.AIDER_OPENAI_API_BASE = baseUrl;
      env.OPENAI_API_BASE = baseUrl;
    }
    return env;
  }

  if (executor === 'openhands') {
    return {
      ...common,
      LLM_MODEL: model,
      LLM_API_KEY: apiKeyValue,
      ...(baseUrl ? { LLM_BASE_URL: baseUrl } : {}),
    };
  }

  return {
    ...common,
    GOOSE_PROVIDER: profile.providerKind === 'custom'
      ? (profile.goosedProviderId ?? profile.providerId)
      : profile.providerId,
    GOOSE_MODEL: model,
    TKMIND_PROVIDER: profile.providerKind === 'custom'
      ? (profile.goosedProviderId ?? profile.providerId)
      : profile.providerId,
    TKMIND_MODEL: model,
  };
}

function launchLogDir() {
  return path.join(os.tmpdir(), 'memindadm-launches');
}

function normalizeLaunchMode(mode) {
  const normalized = String(mode ?? 'headless').trim().toLowerCase();
  return normalized === 'serve' ? 'serve' : 'headless';
}

function commandExists(command) {
  const probe = spawnSync('sh', ['-lc', `command -v ${JSON.stringify(command).slice(1, -1)}`], {
    stdio: 'ignore',
  });
  return probe.status === 0;
}

function spawnDetachedExecutor(plan, { logDir = launchLogDir() } = {}) {
  if (!plan?.ok) {
    return plan;
  }
  if (!plan.command) {
    return {
      ok: false,
      executor: plan.executor ?? null,
      message: '启动命令缺失',
    };
  }
  if (!commandExists(plan.command)) {
    return {
      ok: false,
      executor: plan.executor ?? null,
      message: `${plan.command} 未安装或不在 PATH 中`,
    };
  }
  fs.mkdirSync(logDir, { recursive: true });
  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
  const logFile = path.join(
    logDir,
    `${plan.executor ?? 'executor'}-${stamp}-${crypto.randomUUID().slice(0, 8)}.log`,
  );
  const fd = fs.openSync(logFile, 'a');
  try {
    const child = spawn(plan.command, plan.args ?? [], {
      cwd: plan.cwd ?? process.cwd(),
      env: {
        ...process.env,
        ...(plan.env ?? {}),
      },
      detached: true,
      stdio: ['ignore', fd, fd],
    });
    child.unref();
    return {
      ok: true,
      executor: plan.executor ?? null,
      pid: child.pid ?? null,
      command: plan.command,
      args: plan.args ?? [],
      cwd: plan.cwd ?? process.cwd(),
      logFile,
      notes: plan.notes ?? [],
    };
  } catch (err) {
    return {
      ok: false,
      executor: plan.executor ?? null,
      message: err instanceof Error ? err.message : '启动失败',
      logFile,
    };
  } finally {
    try {
      fs.closeSync(fd);
    } catch {}
  }
}

function isProcessAlive(pid) {
  if (!pid || Number.isNaN(Number(pid))) return false;
  try {
    process.kill(Number(pid), 0);
    return true;
  } catch (err) {
    if (err && typeof err === 'object' && 'code' in err && err.code === 'EPERM') return true;
    return false;
  }
}

function launchStateKey(executor, purpose = 'default') {
  return `${normalizeExecutor(executor)}:${normalizePurpose(purpose)}`;
}

export function buildExecutorLaunchPlan(runtime, options = {}) {
  if (!runtime?.ok) {
    return {
      ok: false,
      executor: runtime?.executor ?? null,
      message: runtime?.message ?? '运行配置未就绪',
    };
  }
  const mode = normalizeLaunchMode(options.mode ?? 'headless');
  const cwd = String(options.cwd ?? process.cwd());
  const instruction = String(options.instruction ?? '').trim();
  const env = runtime.env ?? {};

  if (runtime.executor === 'goose') {
    return {
      ok: false,
      executor: 'goose',
      message: 'Goose 作为现有服务入口，不通过本方法启动',
    };
  }

  if (runtime.executor === 'aider') {
    const hasInstruction = Boolean(instruction);
    return {
      ok: true,
      executor: 'aider',
      cwd,
      command: 'aider',
      args: [
        '--model',
        runtime.model,
        ...(hasInstruction ? ['--message', instruction] : []),
        '--yes-always',
      ],
      env,
      notes: [
        'Aider 通过统一模型中心提供的 API Key / Base URL 运行。',
        '实际启动时建议在仓库目录中执行。',
      ],
    };
  }

  if (runtime.executor === 'openhands') {
    if (mode === 'headless' && !instruction) {
      return {
        ok: false,
        executor: 'openhands',
        message: 'OpenHands headless 模式需要 instruction',
      };
    }
    const args =
      mode === 'serve'
        ? ['serve', '--mount-cwd']
        : ['--headless', '--json', '--override-with-envs', '--task', instruction];
    return {
      ok: true,
      executor: 'openhands',
      cwd,
      command: 'openhands',
      args,
      env,
      notes: [
        mode === 'serve'
          ? 'OpenHands GUI Server 依赖 Docker，适合人工交互调试。'
          : 'Headless 模式适合后续和后台调度对接。',
      ],
    };
  }

  return {
    ok: false,
    executor: runtime.executor,
    message: '不支持的执行器',
  };
}

async function goosedApiFetch(apiTarget, apiSecret, pathname, init = {}, fetchImpl = undiciFetch) {
  const url = new URL(pathname, apiTarget);
  const headers = {
    ...(init.headers ?? {}),
    'X-Secret-Key': apiSecret,
  };
  if (init.body && !headers['Content-Type']) {
    headers['Content-Type'] = 'application/json';
  }
  const dispatcher = apiTarget.startsWith('https://') ? insecureDispatcher : undefined;
  return fetchImpl(url, { ...init, headers, dispatcher });
}

async function writeGoosedConfig(apiTarget, apiSecret, key, value, isSecret, fetchImpl) {
  const upstream = await goosedApiFetch(
    apiTarget,
    apiSecret,
    '/config/upsert',
    {
      method: 'POST',
      body: JSON.stringify({ key, value, is_secret: isSecret }),
    },
    fetchImpl,
  );
  if (upstream.ok) return { ok: true, skipped: false };
  if (upstream.status === 404) return { ok: false, skipped: true };
  const text = await upstream.text().catch(() => '');
  throw new Error(`同步 TKMind Agent 配置 ${key} 失败: ${text || upstream.status}`);
}

export async function ensureLocalFallbackProviderOnGoosed(
  apiTarget,
  apiSecret,
  fetchImpl = undiciFetch,
) {
  if (!LOCAL_LLM_FALLBACK.enabled) {
    throw new Error('本地 LLM fallback 已禁用');
  }
  const profile = {
    providerKind: 'custom',
    name: LOCAL_LLM_FALLBACK.displayName,
    apiUrl: LOCAL_LLM_FALLBACK.apiUrl,
    apiKey: LOCAL_LLM_FALLBACK.apiKey,
    models: [LOCAL_LLM_FALLBACK.model],
    defaultModel: LOCAL_LLM_FALLBACK.model,
    goosedProviderId: cachedLocalFallbackProviderId,
    engine: 'openai',
  };
  const goosedProviderId = await upsertCustomProviderOnGoosed(
    apiTarget,
    apiSecret,
    profile,
    fetchImpl,
  );
  cachedLocalFallbackProviderId = goosedProviderId;
  return goosedProviderId;
}

export async function updateSessionProvider(
  apiFetchImpl,
  sessionId,
  provider,
  model,
) {
  const upstream = await apiFetchImpl('/agent/update_provider', {
    method: 'POST',
    body: JSON.stringify({
      session_id: sessionId,
      provider,
      model,
    }),
  });
  if (!upstream.ok) {
    const text = await upstream.text().catch(() => '');
    throw new Error(text || `切换会话 Provider 失败: ${upstream.status}`);
  }
}

async function upsertCustomProviderOnGoosed(apiTarget, apiSecret, profile, fetchImpl) {
  const headers = profile.relayProvider
    ? { 'X-Provider': profile.relayProvider }
    : undefined;
  const body = {
    engine: profile.engine || 'openai',
    display_name: profile.name,
    api_url: profile.apiUrl,
    api_key: profile.apiKey,
    models: profile.models,
    supports_streaming: true,
    requires_auth: true,
    ...(profile.basePath ? { base_path: profile.basePath } : {}),
    ...(headers ? { headers } : {}),
  };

  if (profile.goosedProviderId) {
    const upstream = await goosedApiFetch(
      apiTarget,
      apiSecret,
      `/config/custom-providers/${encodeURIComponent(profile.goosedProviderId)}`,
      { method: 'PUT', body: JSON.stringify(body) },
      fetchImpl,
    );
    if (!upstream.ok) {
      const text = await upstream.text().catch(() => '');
      throw new Error(`更新 TKMind Agent 自定义 provider 失败: ${text || upstream.status}`);
    }
    return profile.goosedProviderId;
  }

  const upstream = await goosedApiFetch(
    apiTarget,
    apiSecret,
    '/config/custom-providers',
    { method: 'POST', body: JSON.stringify(body) },
    fetchImpl,
  );
  if (!upstream.ok) {
    const text = await upstream.text().catch(() => '');
    throw new Error(`创建 TKMind Agent 自定义 provider 失败: ${text || upstream.status}`);
  }
  const data = await upstream.json();
  return data.provider_name;
}

async function removeCustomProviderOnGoosed(apiTarget, apiSecret, goosedProviderId, fetchImpl) {
  if (!goosedProviderId) return;
  const upstream = await goosedApiFetch(
    apiTarget,
    apiSecret,
    `/config/custom-providers/${encodeURIComponent(goosedProviderId)}`,
    { method: 'DELETE' },
    fetchImpl,
  );
  if (!upstream.ok && upstream.status !== 404) {
    const text = await upstream.text().catch(() => '');
    throw new Error(`删除 TKMind Agent 自定义 provider 失败: ${text || upstream.status}`);
  }
}

async function syncBuiltinProfileToGoosed(apiTarget, apiSecret, profile, fetchImpl) {
  const catalogItem = catalogById[profile.providerId];
  if (!catalogItem?.apiKeyEnv) {
    throw new Error(`未知内置 provider: ${profile.providerId}`);
  }
  await writeGoosedConfig(
    apiTarget,
    apiSecret,
    catalogItem.apiKeyEnv,
    profile.apiKey,
    true,
    fetchImpl,
  );
  await writeGoosedConfig(
    apiTarget,
    apiSecret,
    'GOOSE_PROVIDER',
    profile.providerId,
    false,
    fetchImpl,
  );
  await writeGoosedConfig(
    apiTarget,
    apiSecret,
    'GOOSE_MODEL',
    profile.defaultModel,
    false,
    fetchImpl,
  );
  await writeGoosedConfig(
    apiTarget,
    apiSecret,
    'TKMIND_PROVIDER',
    profile.providerId,
    false,
    fetchImpl,
  );
  await writeGoosedConfig(
    apiTarget,
    apiSecret,
    'TKMIND_MODEL',
    profile.defaultModel,
    false,
    fetchImpl,
  );
}

export async function syncProfileToGoosed(apiTarget, apiSecret, profile, fetchImpl = undiciFetch) {
  if (profile.providerKind === 'custom') {
    const goosedProviderId = await upsertCustomProviderOnGoosed(
      apiTarget,
      apiSecret,
      profile,
      fetchImpl,
    );
    await writeGoosedConfig(
      apiTarget,
      apiSecret,
      'GOOSE_PROVIDER',
      goosedProviderId,
      false,
      fetchImpl,
    );
    await writeGoosedConfig(
      apiTarget,
      apiSecret,
      'GOOSE_MODEL',
      profile.defaultModel,
      false,
      fetchImpl,
    );
    await writeGoosedConfig(
      apiTarget,
      apiSecret,
      'TKMIND_PROVIDER',
      goosedProviderId,
      false,
      fetchImpl,
    );
    await writeGoosedConfig(
      apiTarget,
      apiSecret,
      'TKMIND_MODEL',
      profile.defaultModel,
      false,
      fetchImpl,
    );
    return goosedProviderId;
  }

  await syncBuiltinProfileToGoosed(apiTarget, apiSecret, profile, fetchImpl);
  return profile.providerId;
}

function isCustomPayload(payload) {
  return payload?.providerId === CUSTOM_PROVIDER_ID || payload?.providerKind === 'custom';
}

function validateCustomPayload(payload) {
  const name = String(payload?.name ?? '').trim();
  const apiKey = String(payload?.apiKey ?? '').trim();
  const apiUrl = normalizeApiUrl(payload?.apiUrl);
  const models = parseModelList(payload?.models);
  const defaultModel = String(payload?.defaultModel ?? '').trim() || models[0] || '';
  if (!name) return { ok: false, message: '请填写配置名称' };
  if (!apiUrl) return { ok: false, message: '请填写 API 地址' };
  if (!apiKey) return { ok: false, message: '请填写 API Key / Bearer Token' };
  if (models.length === 0) return { ok: false, message: '请至少填写一个模型' };
  if (!models.includes(defaultModel)) {
    return { ok: false, message: '默认模型必须在模型列表中' };
  }
  return {
    ok: true,
    value: {
      name,
      apiKey,
      apiUrl,
      models,
      defaultModel,
      basePath: String(payload?.basePath ?? '').trim() || null,
      engine: String(payload?.engine ?? 'openai').trim() || 'openai',
      relayProvider: String(payload?.relayProvider ?? '').trim() || null,
    },
  };
}

export function createLlmProviderService(
  pool,
  { apiTarget, apiSecret, encryptionKey, apiFetchImpl = undiciFetch } = {},
) {
  const launchStates = new Map();

  function catalogItem(providerId) {
    return catalogById[providerId] ?? null;
  }

  function decryptRow(row) {
    return decryptSecret(
      {
        ciphertext: row.api_key_ciphertext,
        iv: row.api_key_iv,
        tag: row.api_key_tag,
      },
      encryptionKey,
    );
  }

  async function getRowById(id) {
    const [rows] = await pool.query('SELECT * FROM h5_llm_provider_keys WHERE id = ? LIMIT 1', [
      id,
    ]);
    return rows[0] ?? null;
  }

  async function getSelectedRow() {
    const [rows] = await pool.query(
      'SELECT * FROM h5_llm_provider_keys WHERE is_selected = 1 AND status = ? LIMIT 1',
      ['active'],
    );
    return rows[0] ?? null;
  }

  async function getExecutorBindingRow(executor, purpose = 'default') {
    const normalizedExecutor = normalizeExecutor(executor);
    if (!normalizedExecutor) return null;
    const [rows] = await pool.query(
      'SELECT * FROM h5_llm_executor_bindings WHERE executor = ? AND purpose = ? LIMIT 1',
      [normalizedExecutor, normalizePurpose(purpose)],
    );
    return rows[0] ?? null;
  }

  async function clearSelected() {
    await pool.query('UPDATE h5_llm_provider_keys SET is_selected = 0, updated_at = ?', [
      Date.now(),
    ]);
  }

  async function getVisionRow() {
    const [rows] = await pool.query(
      'SELECT * FROM h5_llm_provider_keys WHERE is_vision_selected = 1 AND status = ? LIMIT 1',
      ['active'],
    );
    return rows[0] ?? null;
  }

  async function clearVisionSelected() {
    await pool.query('UPDATE h5_llm_provider_keys SET is_vision_selected = 0, updated_at = ?', [
      Date.now(),
    ]);
  }

  // Lightweight in-memory flag so proxyFallback can skip DB query on text-only messages.
  // Invalidated on every setVisionKey / clearVisionKey call.
  let visionKeyConfiguredCache = null;

  // Cache last sync per row to avoid redundant Goosed /config/upsert calls on every request.
  // Key: `${row.id}:${row.updated_at}`, value: { goosedProviderId, syncedAt }.
  // Each sync makes 4-5 HTTP calls to Goosed; under load these accumulate open FDs on the
  // Goosed side and can exhaust the OS file descriptor limit (EMFILE / error 24).
  const syncRowCache = new Map();
  const SYNC_ROW_CACHE_TTL_MS = 30_000;

  async function syncRow(row, fetchImpl = apiFetchImpl) {
    const cacheKey = `${row.id}:${row.updated_at}`;
    const cached = syncRowCache.get(cacheKey);
    if (cached && Date.now() - cached.syncedAt < SYNC_ROW_CACHE_TTL_MS && fetchImpl === apiFetchImpl) {
      return cached.goosedProviderId;
    }
    const profile = profileFromRow(row, decryptRow);
    const goosedProviderId = await syncProfileToGoosed(
      apiTarget,
      apiSecret,
      profile,
      fetchImpl,
    );
    if (profile.providerKind === 'custom' && goosedProviderId !== row.goosed_provider_id) {
      await pool.query(
        'UPDATE h5_llm_provider_keys SET goosed_provider_id = ?, provider_id = ?, updated_at = ? WHERE id = ?',
        [goosedProviderId, goosedProviderId, Date.now(), row.id],
      );
    }
    if (fetchImpl === apiFetchImpl) {
      syncRowCache.set(cacheKey, { goosedProviderId, syncedAt: Date.now() });
    }
    return goosedProviderId;
  }

  async function syncRowWithModel(row, model, fetchImpl = apiFetchImpl) {
    const originalModel = row.default_model;
    row.default_model = model || row.default_model;
    try {
      return await syncRow(row, fetchImpl);
    } finally {
      row.default_model = originalModel;
    }
  }

  async function resolveExecutorProvider(executor, purpose = 'default', fetchImpl = apiFetchImpl) {
    const binding = await getExecutorBindingRow(executor, purpose);
    if (!binding?.enabled) {
      return { ok: false, message: `${executorById[executor]?.label ?? executor} 未启用模型绑定` };
    }
    if (!binding.provider_key_id) {
      return { ok: false, message: `${executorById[executor]?.label ?? executor} 未绑定 Provider` };
    }
    const row = await getRowById(binding.provider_key_id);
    if (!row || row.status !== 'active') {
      return { ok: false, message: '绑定的 Provider 不存在或已禁用' };
    }
    const publicRow = rowToPublic(row, catalogItem(row.provider_id), decryptRow(row));
    if (!publicRow.models.includes(binding.model)) {
      return { ok: false, message: '绑定模型不在当前 Provider 支持列表中' };
    }
    try {
      const goosedProviderId = await syncRowWithModel(row, binding.model, fetchImpl);
      const profile = profileFromRow(row, decryptRow);
      const providerId =
        profile.providerKind === 'custom'
          ? (goosedProviderId ?? profile.goosedProviderId ?? profile.providerId)
          : profile.providerId;
      return {
        ok: true,
        providerId,
        model: binding.model,
        source: 'executor_binding',
        executor,
        purpose: binding.purpose,
      };
    } catch (err) {
      return {
        ok: false,
        message: err instanceof Error ? err.message : '同步执行器模型配置失败',
      };
    }
  }

  async function resolveExecutorRuntimeConfig(
    executor,
    { purpose = 'default', includeSecret = false } = {},
  ) {
    const normalizedExecutor = normalizeExecutor(executor);
    if (!normalizedExecutor) return { ok: false, message: '不支持的执行器' };
    const binding = await getExecutorBindingRow(normalizedExecutor, purpose);
    if (!binding?.enabled) {
      return {
        ok: false,
        executor: normalizedExecutor,
        purpose,
        message: `${executorById[normalizedExecutor]?.label ?? normalizedExecutor} 未启用模型绑定`,
      };
    }
    if (!binding.provider_key_id) {
      return {
        ok: false,
        executor: normalizedExecutor,
        purpose,
        message: `${executorById[normalizedExecutor]?.label ?? normalizedExecutor} 未绑定 Provider`,
      };
    }
    const row = await getRowById(binding.provider_key_id);
    if (!row || row.status !== 'active') {
      return {
        ok: false,
        executor: normalizedExecutor,
        purpose,
        message: '绑定的 Provider 不存在或已禁用',
      };
    }
    const keyPublic = rowToPublic(row, catalogItem(row.provider_id), decryptRow(row));
    if (!keyPublic.models.includes(binding.model)) {
      return {
        ok: false,
        executor: normalizedExecutor,
        purpose,
        message: '绑定模型不在当前 Provider 支持列表中',
      };
    }
    const profile = profileFromRow(row, decryptRow);
    const runtimeProfile = includeSecret
      ? profile
      : { ...profile, apiKey: '' };
    return {
      ok: true,
      executor: normalizedExecutor,
      executorLabel: executorById[normalizedExecutor]?.label ?? normalizedExecutor,
      purpose: binding.purpose,
      providerKeyId: row.id,
      providerName: row.name,
      providerKind: profile.providerKind,
      providerId: profile.providerId,
      providerLabel: keyPublic.providerLabel,
      goosedProviderId: profile.goosedProviderId,
      model: binding.model,
      apiUrl: profile.apiUrl,
      apiBaseUrl: resolveApiBaseUrl(profile.apiUrl),
      apiKeyMasked: keyPublic.apiKeyMasked,
      env: executorEnvForProfile(normalizedExecutor, runtimeProfile, binding.model, {
        includeSecret,
      }),
    };
  }

  function getLaunchState(executor, purpose = 'default') {
    const key = launchStateKey(executor, purpose);
    const current = launchStates.get(key);
    if (!current) return null;
    if (current.pid && !isProcessAlive(current.pid)) {
      current.running = false;
      current.stoppedAt = current.stoppedAt ?? Date.now();
      current.exitReason = current.exitReason ?? 'not_running';
      launchStates.set(key, current);
    }
    return current;
  }

  function setLaunchState(executor, purpose, state) {
    const key = launchStateKey(executor, purpose);
    launchStates.set(key, {
      ...state,
      executor: normalizeExecutor(executor),
      purpose: normalizePurpose(purpose),
    });
    return launchStates.get(key);
  }

  function stateToPublic(state) {
    if (!state) return null;
    return {
      executor: state.executor,
      purpose: state.purpose,
      pid: state.pid ?? null,
      running: Boolean(state.running),
      command: state.command ?? null,
      args: state.args ?? [],
      cwd: state.cwd ?? null,
      logFile: state.logFile ?? null,
      startedAt: state.startedAt ?? null,
      stoppedAt: state.stoppedAt ?? null,
      exitReason: state.exitReason ?? null,
      mode: state.mode ?? null,
      instruction: state.instruction ?? null,
    };
  }

  async function stopLaunchState(state, { force = false } = {}) {
    if (!state?.pid) {
      return { ok: false, message: '未找到运行中的执行器进程' };
    }
    if (!isProcessAlive(state.pid)) {
      state.running = false;
      state.stoppedAt = state.stoppedAt ?? Date.now();
      state.exitReason = state.exitReason ?? 'not_running';
      return { ok: true, stopped: true, state: stateToPublic(state) };
    }
    try {
      process.kill(state.pid, 'SIGTERM');
      if (!force) {
        await new Promise((resolve) => setTimeout(resolve, 1500));
      }
      if (force || isProcessAlive(state.pid)) {
        try {
          process.kill(state.pid, 'SIGKILL');
        } catch {}
      }
      state.running = false;
      state.stoppedAt = Date.now();
      state.exitReason = force ? 'force_kill' : 'sigterm';
      return { ok: true, stopped: true, state: stateToPublic(state) };
    } catch (err) {
      return {
        ok: false,
        message: err instanceof Error ? err.message : '停止失败',
      };
    }
  }

  async function testSelectedRow(row) {
    const profile = profileFromRow(row, decryptRow);
    if (profile.providerKind === 'custom') {
      try {
        return await testRelayConnection(
          {
            apiUrl: profile.apiUrl,
            apiKey: profile.apiKey,
            model: profile.defaultModel,
            relayProvider: profile.relayProvider,
          },
          apiFetchImpl,
        );
      } catch (err) {
        return {
          ok: false,
          message: err instanceof Error ? err.message : String(err),
        };
      }
    }
    return { ok: true, model: profile.defaultModel };
  }

  async function resolveSelectedProvider(fetchImpl = apiFetchImpl) {
    const row = await getSelectedRow();
    if (!row) {
      return { ok: false, message: '请先启用 LLM 配置' };
    }
    const profile = profileFromRow(row, decryptRow);
    const selectedTest = await testSelectedRow(row);
    if (!selectedTest.ok) {
      return {
        ok: false,
        message: selectedTest.message ?? '当前选中的 LLM 不可用',
      };
    }
    try {
      const goosedProviderId = await syncRow(row, fetchImpl);
      const providerId =
        profile.providerKind === 'custom'
          ? (goosedProviderId ?? profile.goosedProviderId ?? profile.providerId)
          : profile.providerId;
      return {
        ok: true,
        providerId,
        model: profile.defaultModel,
        source: 'selected',
      };
    } catch (err) {
      return {
        ok: false,
        message: err instanceof Error ? err.message : '同步 LLM 配置失败',
      };
    }
  }

  /** Local fallback is only for DeepSeek creditsExhausted — never auto-selected at boot/session start. */
  async function resolveLocalFallbackProvider(fetchImpl = apiFetchImpl) {
    let localTest;
    try {
      localTest = await testLocalLlmConnection(apiFetchImpl);
    } catch (err) {
      localTest = {
        ok: false,
        message: err instanceof Error ? err.message : String(err),
      };
    }
    if (!localTest.ok) {
      return {
        ok: false,
        message: localTest.message ?? '本地 LLM 不可用',
      };
    }
    const providerId = await ensureLocalFallbackProviderOnGoosed(
      apiTarget,
      apiSecret,
      fetchImpl,
    );
    return {
      ok: true,
      providerId,
      model: LOCAL_LLM_FALLBACK.model,
      source: 'local_fallback',
    };
  }

  const goosedApi = (pathname, init) => goosedApiFetch(apiTarget, apiSecret, pathname, init, apiFetchImpl);

  return {
    catalog: LLM_PROVIDER_CATALOG,
    executorCatalog: LLM_EXECUTOR_CATALOG,

    async listKeys() {
      const [rows] = await pool.query(
        'SELECT * FROM h5_llm_provider_keys ORDER BY is_selected DESC, updated_at DESC',
      );
      return rows.map((row) =>
        rowToPublic(row, catalogItem(row.provider_id), decryptRow(row)),
      );
    },

    async listExecutorBindings() {
      const keys = await this.listKeys();
      const keyMap = new Map(keys.map((key) => [key.id, key]));
      const [rows] = await pool.query(
        'SELECT * FROM h5_llm_executor_bindings ORDER BY FIELD(executor, "goose", "aider", "openhands"), purpose',
      );
      const rowMap = new Map(
        rows.map((row) => [`${row.executor}:${row.purpose}`, row]),
      );
      return LLM_EXECUTOR_CATALOG.map((executor) => {
        const row = rowMap.get(`${executor.id}:default`);
        if (!row) {
          return {
            id: null,
            executor: executor.id,
            executorLabel: executor.label,
            executorDescription: executor.description,
            purpose: 'default',
            providerKeyId: null,
            providerName: null,
            providerLabel: null,
            providerId: null,
            model: '',
            enabled: false,
            availableModels: [],
            createdAt: null,
            updatedAt: null,
          };
        }
        return rowToExecutorBinding(row, keyMap.get(row.provider_key_id) ?? null);
      });
    },

    async setExecutorBinding(executor, payload = {}) {
      const normalizedExecutor = normalizeExecutor(executor);
      if (!normalizedExecutor) return { ok: false, message: '不支持的执行器' };
      const purpose = normalizePurpose(payload.purpose);
      const enabled = payload.enabled !== false;
      const keyId = String(payload.keyId ?? payload.providerKeyId ?? '').trim() || null;
      const model = String(payload.model ?? '').trim();

      if (enabled && !keyId) return { ok: false, message: '请选择 Provider' };
      if (enabled && !model) return { ok: false, message: '请选择模型' };

      let keyPublic = null;
      if (keyId) {
        const row = await getRowById(keyId);
        if (!row) return { ok: false, message: 'Provider 不存在' };
        if (row.status !== 'active') return { ok: false, message: 'Provider 已禁用' };
        keyPublic = rowToPublic(row, catalogItem(row.provider_id), decryptRow(row));
        if (model && !keyPublic.models.includes(model)) {
          return { ok: false, message: '模型不在该 Provider 支持列表中' };
        }
      }

      const existing = await getExecutorBindingRow(normalizedExecutor, purpose);
      const now = Date.now();
      if (existing) {
        await pool.query(
          `UPDATE h5_llm_executor_bindings
           SET provider_key_id = ?, model = ?, enabled = ?, updated_at = ?
           WHERE id = ?`,
          [keyId, model, enabled ? 1 : 0, now, existing.id],
        );
      } else {
        await pool.query(
          `INSERT INTO h5_llm_executor_bindings
           (id, executor, purpose, provider_key_id, model, enabled, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
          [crypto.randomUUID(), normalizedExecutor, purpose, keyId, model, enabled ? 1 : 0, now, now],
        );
      }

      const row = await getExecutorBindingRow(normalizedExecutor, purpose);
      return { ok: true, binding: rowToExecutorBinding(row, keyPublic) };
    },

    async getExecutorRuntimeConfig(executor, options = {}) {
      return resolveExecutorRuntimeConfig(executor, options);
    },

    async listExecutorRuntimeConfigs(options = {}) {
      const configs = await Promise.all(
        LLM_EXECUTOR_CATALOG.map((executor) =>
          resolveExecutorRuntimeConfig(executor.id, options),
        ),
      );
      return configs;
    },

    async getExecutorLaunchPlan(executor, options = {}) {
      const runtime = await resolveExecutorRuntimeConfig(executor, {
        purpose: options.purpose ?? 'default',
        includeSecret: options.includeSecret ?? false,
      });
      return buildExecutorLaunchPlan(runtime, options);
    },

    async listExecutorLaunchPlans(options = {}) {
      const runtimes = await this.listExecutorRuntimeConfigs({
        purpose: options.purpose ?? 'default',
        includeSecret: options.includeSecret ?? false,
      });
      return runtimes.map((runtime) => buildExecutorLaunchPlan(runtime, options));
    },

    async listExecutorLaunchStates(options = {}) {
      const purpose = normalizePurpose(options.purpose ?? 'default');
      return LLM_EXECUTOR_CATALOG.map((item) => stateToPublic(getLaunchState(item.id, purpose)));
    },

    async getExecutorLaunchState(executor, options = {}) {
      const purpose = normalizePurpose(options.purpose ?? 'default');
      return stateToPublic(getLaunchState(executor, purpose));
    },

    async launchExecutor(executor, options = {}) {
      const normalizedExecutor = normalizeExecutor(executor);
      if (!normalizedExecutor) {
        return { ok: false, message: '不支持的执行器' };
      }
      const purpose = normalizePurpose(options.purpose ?? 'default');
      const mode = normalizeLaunchMode(options.mode ?? (normalizedExecutor === 'openhands' ? 'serve' : 'headless'));
      const instruction = String(options.instruction ?? '').trim();
      const currentState = getLaunchState(normalizedExecutor, purpose);
      if (currentState?.running) {
        return {
          ok: true,
          executor: normalizedExecutor,
          purpose,
          running: true,
          reused: true,
          launch: stateToPublic(currentState),
        };
      }

      if (normalizedExecutor === 'goose') {
        return {
          ok: false,
          executor: 'goose',
          message: 'Goose 作为现有服务入口，不通过后台直接启动',
        };
      }

      if (normalizedExecutor === 'aider' && !instruction) {
        return {
          ok: false,
          executor: 'aider',
          message: 'Aider 启动需要 instruction',
        };
      }

      if (normalizedExecutor === 'openhands' && mode === 'headless' && !instruction) {
        return {
          ok: false,
          executor: 'openhands',
          message: 'OpenHands headless 启动需要 instruction',
        };
      }

      const runtime = await resolveExecutorRuntimeConfig(normalizedExecutor, {
        purpose,
        includeSecret: true,
      });
      const plan = buildExecutorLaunchPlan(runtime, {
        purpose,
        mode,
        cwd: options.cwd,
        instruction,
        includeSecret: true,
      });
      if (!plan.ok) {
        return plan;
      }
      const launch = spawnDetachedExecutor(plan, { logDir: options.logDir });
      if (!launch.ok) return launch;
      const state = setLaunchState(normalizedExecutor, purpose, {
        pid: launch.pid ?? null,
        running: true,
        command: launch.command,
        args: launch.args ?? [],
        cwd: launch.cwd ?? plan.cwd,
        logFile: launch.logFile,
        startedAt: Date.now(),
        mode,
        instruction: instruction || null,
      });
      return {
        ...launch,
        purpose,
        running: true,
        launch: stateToPublic(state),
      };
    },

    async stopExecutor(executor, options = {}) {
      const normalizedExecutor = normalizeExecutor(executor);
      if (!normalizedExecutor) {
        return { ok: false, message: '不支持的执行器' };
      }
      const purpose = normalizePurpose(options.purpose ?? 'default');
      const state = getLaunchState(normalizedExecutor, purpose);
      if (!state) {
        return { ok: false, executor: normalizedExecutor, purpose, message: '未找到执行记录' };
      }
      const result = await stopLaunchState(state, { force: Boolean(options.force) });
      if (!result.ok) return { ok: false, executor: normalizedExecutor, purpose, message: result.message };
      setLaunchState(normalizedExecutor, purpose, state);
      return {
        ok: true,
        executor: normalizedExecutor,
        purpose,
        launch: result.state,
      };
    },

    async restartExecutor(executor, options = {}) {
      const normalizedExecutor = normalizeExecutor(executor);
      if (!normalizedExecutor) {
        return { ok: false, message: '不支持的执行器' };
      }
      const purpose = normalizePurpose(options.purpose ?? 'default');
      const state = getLaunchState(normalizedExecutor, purpose);
      if (!state) {
        return { ok: false, executor: normalizedExecutor, purpose, message: '未找到可重启的执行记录' };
      }
      await stopLaunchState(state, { force: true });
      const launchOptions = {
        purpose,
        mode: options.mode ?? state.mode ?? (normalizedExecutor === 'openhands' ? 'serve' : 'headless'),
        cwd: options.cwd ?? state.cwd ?? process.cwd(),
        instruction: options.instruction ?? state.instruction ?? '',
        logDir: options.logDir,
      };
      return this.launchExecutor(normalizedExecutor, launchOptions);
    },

    async createKey(payload) {
      const custom = isCustomPayload(payload);
      let providerKind = 'builtin';
      let providerId = String(payload?.providerId ?? '').trim();
      let insertName = String(payload?.name ?? '').trim();
      let apiKey = String(payload?.apiKey ?? '').trim();
      let defaultModel = String(payload?.defaultModel ?? '').trim();
      let apiUrl = null;
      let basePath = null;
      let engine = 'openai';
      let relayProvider = null;
      let modelsJson = null;
      let meta = catalogItem(providerId);

      if (custom) {
        const validated = validateCustomPayload(payload);
        if (!validated.ok) return validated;
        providerKind = 'custom';
        providerId = CUSTOM_PROVIDER_ID;
        meta = catalogById[CUSTOM_PROVIDER_ID];
        insertName = validated.value.name;
        apiKey = validated.value.apiKey;
        apiUrl = validated.value.apiUrl;
        basePath = validated.value.basePath;
        engine = validated.value.engine;
        relayProvider = validated.value.relayProvider;
        defaultModel = validated.value.defaultModel;
        modelsJson = JSON.stringify(validated.value.models);
      } else if (providerId === 'custom_qwen') {
        // Qwen VL: displayed as builtin in the UI but stored as a custom OpenAI-compatible
        // provider so Goose receives images (custom_deepseek is in PROVIDERS_WITHOUT_VISION).
        if (!meta) return { ok: false, message: '不支持的 provider' };
        if (!insertName) return { ok: false, message: '请填写配置名称' };
        if (!apiKey) return { ok: false, message: '请填写 API Key' };
        providerKind = 'custom';
        apiUrl = meta.apiUrl ?? 'https://dashscope.aliyuncs.com/compatible-mode/v1';
        const qwenModels = meta.models;
        defaultModel = defaultModel || meta.defaultModel || qwenModels[0];
        if (!qwenModels.includes(defaultModel)) defaultModel = qwenModels[0];
        modelsJson = JSON.stringify(qwenModels);
      } else {
        if (!meta) return { ok: false, message: '不支持的 provider' };
        if (!insertName) return { ok: false, message: '请填写配置名称' };
        if (!apiKey) return { ok: false, message: '请填写 API Key' };
        defaultModel = defaultModel || meta.defaultModel;
        if (!meta.models.includes(defaultModel)) {
          return { ok: false, message: '不支持的模型' };
        }
      }

      const [existing] = await pool.query(
        'SELECT id FROM h5_llm_provider_keys WHERE name = ? LIMIT 1',
        [insertName],
      );
      if (existing.length > 0) return { ok: false, message: '配置名称已存在' };

      const [countRows] = await pool.query('SELECT COUNT(*) AS total FROM h5_llm_provider_keys');
      const shouldSelect = Number(countRows[0]?.total ?? 0) === 0;
      const encrypted = encryptSecret(apiKey, encryptionKey);
      const now = Date.now();
      const id = crypto.randomUUID();
      if (shouldSelect) await clearSelected();

      await pool.query(
        `INSERT INTO h5_llm_provider_keys
         (id, provider_id, provider_kind, api_url, base_path, models_json, goosed_provider_id, engine, relay_provider,
          name, api_key_ciphertext, api_key_iv, api_key_tag, default_model, status, is_selected, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?)`,
        [
          id,
          providerId,
          providerKind,
          apiUrl,
          basePath,
          modelsJson,
          engine,
          relayProvider,
          insertName,
          encrypted.ciphertext,
          encrypted.iv,
          encrypted.tag,
          defaultModel,
          shouldSelect ? 1 : 0,
          now,
          now,
        ],
      );

      let row = await getRowById(id);
      if (shouldSelect) {
        await syncRow(row);
        row = await getRowById(id);
      }

      return { ok: true, key: rowToPublic(row, meta, apiKey) };
    },

    async updateKey(id, payload) {
      const row = await getRowById(id);
      if (!row) return { ok: false, message: '配置不存在' };
      const providerKind = row.provider_kind ?? 'builtin';

      const nextName =
        payload?.name !== undefined ? String(payload.name).trim() : row.name;
      const nextApiKey =
        payload?.apiKey !== undefined ? String(payload.apiKey).trim() : null;
      const nextStatus =
        payload?.status === 'disabled' || payload?.status === 'active'
          ? payload.status
          : row.status;

      if (!nextName) return { ok: false, message: '请填写配置名称' };

      const [existing] = await pool.query(
        'SELECT id FROM h5_llm_provider_keys WHERE name = ? AND id <> ? LIMIT 1',
        [nextName, id],
      );
      if (existing.length > 0) return { ok: false, message: '配置名称已存在' };

      if (row.is_selected && nextStatus === 'disabled') {
        return { ok: false, message: '当前启用的配置不能禁用，请先切换到其他配置' };
      }

      let nextModel = row.default_model;
      let nextApiUrl = row.api_url;
      let nextBasePath = row.base_path;
      let nextEngine = row.engine ?? 'openai';
      let nextRelayProvider = row.relay_provider;
      let nextModelsJson = row.models_json;

      if (providerKind === 'custom') {
        const models = payload?.models !== undefined
          ? parseModelList(payload.models)
          : parseModelsJson(row.models_json);
        nextModel =
          payload?.defaultModel !== undefined
            ? String(payload.defaultModel).trim()
            : row.default_model;
        nextApiUrl =
          payload?.apiUrl !== undefined
            ? normalizeApiUrl(payload.apiUrl)
            : row.api_url;
        nextBasePath =
          payload?.basePath !== undefined
            ? String(payload.basePath).trim() || null
            : row.base_path;
        nextEngine =
          payload?.engine !== undefined
            ? String(payload.engine).trim() || 'openai'
            : row.engine;
        nextRelayProvider =
          payload?.relayProvider !== undefined
            ? String(payload.relayProvider).trim() || null
            : row.relay_provider;
        if (models.length === 0) return { ok: false, message: '请至少保留一个模型' };
        if (!models.includes(nextModel)) {
          return { ok: false, message: '默认模型必须在模型列表中' };
        }
        if (!nextApiUrl) return { ok: false, message: '请填写 API 地址' };
        nextModelsJson = JSON.stringify(models);
      } else {
        const meta = catalogItem(row.provider_id);
        if (!meta) return { ok: false, message: '不支持的 provider' };
        nextModel =
          payload?.defaultModel !== undefined
            ? String(payload.defaultModel).trim()
            : row.default_model;
        if (!meta.models.includes(nextModel)) {
          return { ok: false, message: '不支持的模型' };
        }
      }

      const encrypted = nextApiKey
        ? encryptSecret(nextApiKey, encryptionKey)
        : {
            ciphertext: row.api_key_ciphertext,
            iv: row.api_key_iv,
            tag: row.api_key_tag,
          };

      const now = Date.now();
      await pool.query(
        `UPDATE h5_llm_provider_keys
         SET name = ?, api_url = ?, base_path = ?, models_json = ?, engine = ?, relay_provider = ?,
             api_key_ciphertext = ?, api_key_iv = ?, api_key_tag = ?,
             default_model = ?, status = ?, updated_at = ?
         WHERE id = ?`,
        [
          nextName,
          nextApiUrl,
          nextBasePath,
          nextModelsJson,
          nextEngine,
          nextRelayProvider,
          encrypted.ciphertext,
          encrypted.iv,
          encrypted.tag,
          nextModel,
          nextStatus,
          now,
          id,
        ],
      );

      if (row.is_selected) {
        await syncRow(await getRowById(id));
      }

      const updated = await getRowById(id);
      return {
        ok: true,
        key: rowToPublic(updated, catalogItem(updated.provider_id), nextApiKey || decryptRow(row)),
      };
    },

    async selectKey(id) {
      const row = await getRowById(id);
      if (!row) return { ok: false, message: '配置不存在' };
      if (row.status !== 'active') {
        return { ok: false, message: '已禁用的配置不能启用' };
      }
      await clearSelected();
      const now = Date.now();
      await pool.query(
        'UPDATE h5_llm_provider_keys SET is_selected = 1, updated_at = ? WHERE id = ?',
        [now, id],
      );
      await syncRow(row);
      const updated = await getRowById(id);
      return {
        ok: true,
        key: rowToPublic(updated, catalogItem(updated.provider_id), decryptRow(updated)),
      };
    },

    async deleteKey(id) {
      const row = await getRowById(id);
      if (!row) return { ok: false, message: '配置不存在' };
      if (row.is_selected) {
        return { ok: false, message: '当前启用的配置不能删除，请先切换到其他配置' };
      }
      if ((row.provider_kind ?? 'builtin') === 'custom' && row.goosed_provider_id) {
        await removeCustomProviderOnGoosed(
          apiTarget,
          apiSecret,
          row.goosed_provider_id,
          apiFetchImpl,
        );
      }
      await pool.query('DELETE FROM h5_llm_provider_keys WHERE id = ?', [id]);
      return { ok: true };
    },

    async syncSelectedToGoosed() {
      let resolved = await resolveExecutorProvider('goose');
      if (!resolved.ok) {
        resolved = await resolveSelectedProvider();
      }
      if (!resolved.ok) {
        return { ok: false, synced: false, message: resolved.message };
      }
      return {
        ok: true,
        synced: true,
        source: resolved.source,
        providerId: resolved.providerId,
        model: resolved.model,
      };
    },

    async applyBestProviderForSession(sessionId, fetchImpl = apiFetchImpl) {
      let resolved = await resolveExecutorProvider('goose', 'default', fetchImpl);
      if (!resolved.ok) {
        resolved = await resolveSelectedProvider(fetchImpl);
      }
      if (!resolved.ok) {
        return resolved;
      }
      const sessionGoosedApi = (pathname, init) =>
        goosedApiFetch(apiTarget, apiSecret, pathname, init, fetchImpl);
      await updateSessionProvider(sessionGoosedApi, sessionId, resolved.providerId, resolved.model);
      return resolved;
    },

    /** Switch session to local Ollama (credits exhausted or relay 500 payload limit). */
    async applyLocalFallbackForSession(sessionId, fetchImpl = apiFetchImpl) {
      const resolved = await resolveLocalFallbackProvider(fetchImpl);
      if (!resolved.ok) {
        return resolved;
      }
      const sessionGoosedApi = (pathname, init) =>
        goosedApiFetch(apiTarget, apiSecret, pathname, init, fetchImpl);
      await updateSessionProvider(sessionGoosedApi, sessionId, resolved.providerId, resolved.model);
      return resolved;
    },

    async hasVisionKey() {
      if (visionKeyConfiguredCache !== null) return visionKeyConfiguredCache;
      const row = await getVisionRow();
      visionKeyConfiguredCache = row !== null;
      return visionKeyConfiguredCache;
    },

    async getVisionSettings() {
      const row = await getVisionRow();
      if (!row) {
        return { keyId: null, keyName: null, providerLabel: null, visionModel: null, availableModels: [] };
      }
      const publicRow = rowToPublic(row, catalogItem(row.provider_id), decryptRow(row));
      return {
        keyId: publicRow.id,
        keyName: publicRow.name,
        providerLabel: publicRow.providerLabel,
        visionModel: publicRow.defaultModel,
        availableModels: publicRow.models,
      };
    },

    async setVisionKey(keyId, model) {
      const nextModel = String(model ?? '').trim();
      if (!nextModel) return { ok: false, message: '请选择视觉模型' };
      const row = await getRowById(keyId);
      if (!row) return { ok: false, message: '配置不存在' };
      if (row.status !== 'active') return { ok: false, message: '该配置已禁用' };
      const publicRow = rowToPublic(row, catalogItem(row.provider_id), decryptRow(row));
      if (!publicRow.models.includes(nextModel)) {
        return { ok: false, message: '模型不在该配置支持列表中' };
      }
      await clearVisionSelected();
      const now = Date.now();
      await pool.query(
        'UPDATE h5_llm_provider_keys SET is_vision_selected = 1, default_model = ?, updated_at = ? WHERE id = ?',
        [nextModel, now, keyId],
      );
      visionKeyConfiguredCache = true;
      return { ok: true, vision: await this.getVisionSettings() };
    },

    async clearVisionKey() {
      await clearVisionSelected();
      visionKeyConfiguredCache = false;
      return { ok: true };
    },

    async applyVisionProviderForSession(sessionId, fetchImpl = apiFetchImpl) {
      const row = await getVisionRow();
      if (!row) return { ok: false, message: '未配置图片任务模型' };
      const sessionGoosedApi = (pathname, init) =>
        goosedApiFetch(apiTarget, apiSecret, pathname, init, fetchImpl);
      let goosedProviderId;
      try {
        goosedProviderId = await syncRow(row, fetchImpl);
        if (!goosedProviderId) {
          goosedProviderId = profileFromRow(row, decryptRow).goosedProviderId ?? row.provider_id;
        }
      } catch (err) {
        return { ok: false, message: err instanceof Error ? err.message : '同步视觉模型失败' };
      }
      await updateSessionProvider(sessionGoosedApi, sessionId, goosedProviderId, row.default_model);
      return { ok: true, providerId: goosedProviderId, model: row.default_model, source: 'vision' };
    },

    // Calls the vision provider directly (not through Goose) to analyze images.
    // Returns the model's text description, or null on failure.
    async analyzeImagesWithVision(imageItems, userText) {
      const row = await getVisionRow();
      if (!row) return null;
      const apiUrl = String(row.api_url ?? '').trim();
      if (!apiUrl) return null;
      const apiKey = decryptRow(row);
      if (!apiKey) return null;
      const model = String(row.default_model ?? 'qwen-vl-max').trim();

      const content = [
        ...imageItems.map((item) => ({
          type: 'image_url',
          image_url: { url: `data:${item.mimeType};base64,${item.data}` },
        })),
        { type: 'text', text: String(userText || '请描述这张图片的内容').trim() },
      ];
      try {
        const resp = await undiciFetch(`${apiUrl}/chat/completions`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${apiKey}`,
          },
          body: JSON.stringify({
            model,
            messages: [{ role: 'user', content }],
            max_tokens: 1200,
          }),
          signal: AbortSignal.timeout(30_000),
        });
        if (!resp.ok) return null;
        const json = await resp.json();
        return json?.choices?.[0]?.message?.content ?? null;
      } catch {
        return null;
      }
    },

    async getGlobalSettings() {
      const row = await getSelectedRow();
      if (!row) {
        return {
          keyId: null,
          keyName: null,
          providerLabel: null,
          globalModel: null,
          availableModels: [],
        };
      }
      const publicRow = rowToPublic(row, catalogItem(row.provider_id), decryptRow(row));
      return {
        keyId: publicRow.id,
        keyName: publicRow.name,
        providerLabel: publicRow.providerLabel,
        globalModel: publicRow.defaultModel,
        availableModels: publicRow.models,
      };
    },

    async setGlobalModel(model) {
      const nextModel = String(model ?? '').trim();
      if (!nextModel) return { ok: false, message: '请选择全局模型' };
      const row = await getSelectedRow();
      if (!row) return { ok: false, message: '请先启用一个 LLM 配置' };

      const publicRow = rowToPublic(row, catalogItem(row.provider_id), decryptRow(row));
      if (!publicRow.models.includes(nextModel)) {
        return { ok: false, message: '模型不在当前 Provider 支持列表中' };
      }

      await pool.query(
        'UPDATE h5_llm_provider_keys SET default_model = ?, updated_at = ? WHERE id = ?',
        [nextModel, Date.now(), row.id],
      );
      await syncRow(await getRowById(row.id));
      return { ok: true, global: await this.getGlobalSettings() };
    },

    async testDraft(payload) {
      const custom = isCustomPayload(payload);
      if (!custom) {
        return { ok: false, message: '联通测试目前支持自定义 OpenAI 兼容配置' };
      }
      const validated = validateCustomPayload(payload);
      if (!validated.ok) return validated;
      const model = String(payload?.testModel ?? payload?.defaultModel ?? validated.value.defaultModel).trim();
      if (!validated.value.models.includes(model)) {
        return { ok: false, message: '测试模型不在模型列表中' };
      }
      return testRelayConnection(
        {
          apiUrl: validated.value.apiUrl,
          apiKey: validated.value.apiKey,
          model,
          relayProvider: validated.value.relayProvider,
        },
        apiFetchImpl,
      );
    },

	    async testKey(id, testModel) {
	      const row = await getRowById(id);
	      if (!row) return { ok: false, message: '配置不存在' };
	      const model = String(testModel ?? row.default_model).trim();
	      const providerKind = row.provider_kind ?? 'builtin';
	      const meta = catalogItem(row.provider_id);
	      const models = providerKind === 'custom'
	        ? parseModelsJson(row.models_json)
	        : (meta?.models ?? []);
	      if (!models.includes(model)) {
		return { ok: false, message: '测试模型不在配置列表中' };
	      }
	      if (providerKind !== 'custom') {
	        const apiUrl = BUILTIN_PROVIDER_TEST_URLS[row.provider_id];
	        if (!apiUrl) {
	          return { ok: false, message: '该内置 Provider 暂不支持联通测试' };
	        }
	        return testRelayConnection(
	          {
	            apiUrl,
	            apiKey: decryptRow(row),
	            model,
	            relayProvider: null,
	          },
	          apiFetchImpl,
	        );
	      }
	      return testRelayConnection(
		{
		  apiUrl: row.api_url,
          apiKey: decryptRow(row),
          model,
          relayProvider: row.relay_provider,
        },
        apiFetchImpl,
      );
    },

    async ensureBootstrapRelay() {
      const [existing] = await pool.query(
        'SELECT id FROM h5_llm_provider_keys WHERE name = ? LIMIT 1',
        [RELAY_BOOTSTRAP.name],
      );
      if (existing.length > 0) {
        return { ok: true, created: false, keyId: existing[0].id };
      }

      const result = await this.createKey({
        providerId: CUSTOM_PROVIDER_ID,
        name: RELAY_BOOTSTRAP.name,
        apiKey: RELAY_BOOTSTRAP.apiKey,
        apiUrl: RELAY_BOOTSTRAP.apiUrl,
        models: RELAY_BOOTSTRAP.models,
        defaultModel: RELAY_BOOTSTRAP.defaultModel,
        relayProvider: RELAY_BOOTSTRAP.relayProvider,
      });
      if (!result.ok) return result;
      if (result.key && !result.key.isSelected) {
        await this.selectKey(result.key.id);
      }
      return { ok: true, created: true, key: result.key };
    },
  };
}
