// Описание провайдеров и низкоуровневый вызов чата (стриминг).
//
// Поддержка совпадает с Aris Backend Gateway (backend/app/ai_gateway.py):
//   российские:        gigachat (Сбер), yandexgpt (Яндекс)
//   openai-совместимые: openai, deepseek, ollama, vllm, lmstudio, openai_compatible
//   свои форматы:       anthropic (Claude), gemini (Google)
//
// Модель BYO-key: ключ принадлежит пользователю и не покидает его браузер,
// уходит напрямую выбранному провайдеру.

export const PROVIDERS = {
  // --- Российские (рекомендуются; см. требование импортозамещения) ---------
  gigachat: {
    label: "GigaChat (Сбер) — РФ",
    kind: "gigachat",
    baseUrl: "https://gigachat.devices.sberbank.ru/api/v1",
    oauthUrl: "https://ngw.devices.sberbank.ru:9443/api/v2/oauth",
    defaultModel: "GigaChat",
    russian: true,
  },
  yandexgpt: {
    label: "YandexGPT (Яндекс) — РФ",
    kind: "yandexgpt",
    baseUrl: "https://llm.api.cloud.yandex.net",
    defaultModel: "yandexgpt",
    russian: true,
  },

  // --- OpenAI-совместимые --------------------------------------------------
  openai: {
    label: "OpenAI",
    kind: "openai_compat",
    baseUrl: "https://api.openai.com/v1",
    defaultModel: "gpt-4o-mini",
  },
  deepseek: {
    label: "DeepSeek",
    kind: "openai_compat",
    baseUrl: "https://api.deepseek.com/v1",
    defaultModel: "deepseek-chat",
  },
  ollama: {
    label: "Ollama (локально)",
    kind: "openai_compat",
    baseUrl: "http://127.0.0.1:11434/v1",
    defaultModel: "llama3.1",
    local: true,
  },
  vllm: {
    label: "vLLM (локально)",
    kind: "openai_compat",
    baseUrl: "http://127.0.0.1:8000/v1",
    defaultModel: "",
    local: true,
  },
  lmstudio: {
    label: "LM Studio (локально)",
    kind: "openai_compat",
    baseUrl: "http://127.0.0.1:1234/v1",
    defaultModel: "",
    local: true,
  },
  openai_compatible: {
    label: "OpenAI Compatible API",
    kind: "openai_compat",
    baseUrl: "",
    defaultModel: "",
  },

  // --- Свои форматы --------------------------------------------------------
  anthropic: {
    label: "Claude (Anthropic)",
    kind: "anthropic",
    baseUrl: "https://api.anthropic.com/v1",
    defaultModel: "claude-sonnet-4-6",
  },
  gemini: {
    label: "Gemini (Google)",
    kind: "gemini",
    baseUrl: "https://generativelanguage.googleapis.com/v1beta",
    defaultModel: "gemini-2.0-flash",
  },
};

export function providerList() {
  return Object.entries(PROVIDERS).map(([id, p]) => ({ id, ...p }));
}

/**
 * Стриминговый чат. Вызывает onDelta(textChunk) по мере генерации.
 * messages: [{role, content}]. Возвращает полный текст.
 */
export async function streamChat(opts, onDelta, signal) {
  const { provider, apiKey, baseUrl, model, temperature, messages, scope, folderId } = opts;
  const meta = PROVIDERS[provider];
  if (!meta) throw new Error(`Неизвестный провайдер: ${provider}`);
  const url = (baseUrl || meta.baseUrl || "").replace(/\/+$/, "");
  const mdl = model || meta.defaultModel;

  if (meta.kind === "openai_compat") {
    return streamOpenAICompat({ url, apiKey, model: mdl, temperature, messages }, onDelta, signal);
  }
  if (meta.kind === "gigachat") {
    // У пользователя — «ключ авторизации» (Basic). Меняем его на access_token (OAuth).
    const token = await gigachatToken(apiKey, scope, meta.oauthUrl);
    return streamOpenAICompat({ url, apiKey: token, model: mdl, temperature, messages }, onDelta, signal);
  }
  if (meta.kind === "anthropic") {
    return streamAnthropic({ url, apiKey, model: mdl, temperature, messages }, onDelta, signal);
  }
  if (meta.kind === "gemini") {
    return streamGemini({ url, apiKey, model: mdl, temperature, messages }, onDelta, signal);
  }
  if (meta.kind === "yandexgpt") {
    return callYandex({ url, apiKey, model: mdl, temperature, messages, folderId }, onDelta, signal);
  }
  throw new Error(`Тип провайдера не поддержан: ${meta.kind}`);
}

// --- GigaChat OAuth (обмен ключа авторизации на access_token, с кэшем) -----

let _gigaToken = { key: "", token: "", exp: 0 };

async function gigachatToken(authKey, scope, oauthUrl) {
  if (!authKey) throw new Error("GigaChat: не задан ключ авторизации (Authorization key из личного кабинета).");
  const now = Date.now();
  if (_gigaToken.token && _gigaToken.key === authKey && _gigaToken.exp - 60000 > now) return _gigaToken.token;

  const res = await fetch(oauthUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Accept: "application/json",
      RqUID: crypto.randomUUID(),
      Authorization: `Basic ${authKey}`,
    },
    body: `scope=${encodeURIComponent(scope || "GIGACHAT_API_PERS")}`,
  });
  if (!res.ok) {
    const hint = res.status === 0 || res.status === 525
      ? " (возможно, не установлен корневой сертификат Минцифры — см. README)"
      : "";
    throw new Error(`GigaChat OAuth ${res.status}: ${await res.text()}${hint}`);
  }
  const d = await res.json();
  _gigaToken = { key: authKey, token: d.access_token, exp: d.expires_at || now + 25 * 60000 };
  return _gigaToken.token;
}

// --- OpenAI / DeepSeek / Ollama / vLLM / LM Studio / GigaChat --------------

async function streamOpenAICompat({ url, apiKey, model, temperature, messages }, onDelta, signal) {
  const res = await fetch(`${url}/chat/completions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
    },
    body: JSON.stringify({ model, temperature, stream: true, messages }),
    signal,
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return readSSE(res, onDelta, (json) => json.choices?.[0]?.delta?.content || "");
}

// --- Anthropic / Claude ----------------------------------------------------

async function streamAnthropic({ url, apiKey, model, temperature, messages }, onDelta, signal) {
  const system = messages.filter((m) => m.role === "system").map((m) => m.content).join("\n\n");
  const turns = messages.filter((m) => m.role !== "system");
  const res = await fetch(`${url}/messages`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": apiKey,
      "anthropic-version": "2023-06-01",
      "anthropic-dangerous-direct-browser-access": "true",
    },
    body: JSON.stringify({ model, max_tokens: 4096, temperature, stream: true, system, messages: turns }),
    signal,
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return readSSE(res, onDelta, (json) =>
    json.type === "content_block_delta" ? json.delta?.text || "" : "");
}

// --- Gemini ----------------------------------------------------------------

async function streamGemini({ url, apiKey, model, temperature, messages }, onDelta, signal) {
  const contents = messages
    .filter((m) => m.role !== "system")
    .map((m) => ({ role: m.role === "assistant" ? "model" : "user", parts: [{ text: m.content }] }));
  const sys = messages.filter((m) => m.role === "system").map((m) => m.content).join("\n\n");
  const res = await fetch(
    `${url}/models/${model}:streamGenerateContent?alt=sse&key=${encodeURIComponent(apiKey)}`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        contents,
        systemInstruction: sys ? { parts: [{ text: sys }] } : undefined,
        generationConfig: { temperature },
      }),
      signal,
    },
  );
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return readSSE(res, onDelta, (json) => json.candidates?.[0]?.content?.parts?.[0]?.text || "");
}

// --- YandexGPT (нестриминговый REST) --------------------------------------

async function callYandex({ url, apiKey, model, temperature, messages, folderId }, onDelta) {
  // modelUri: либо полный "gpt://<folderId>/<model>/latest", либо собираем из folderId.
  let modelUri = model && model.startsWith("gpt://") ? model : null;
  if (!modelUri) {
    if (!folderId) throw new Error("YandexGPT: укажите Folder ID каталога в настройках.");
    modelUri = `gpt://${folderId}/${model || "yandexgpt"}/latest`;
  }
  const res = await fetch(`${url}/foundationModels/v1/completion`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Api-Key ${apiKey}`,
      ...(folderId ? { "x-folder-id": folderId } : {}),
    },
    body: JSON.stringify({
      modelUri,
      completionOptions: { stream: false, temperature, maxTokens: 4000 },
      messages: messages.map((m) => ({ role: m.role, text: m.content })),
    }),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  const data = await res.json();
  const text = data.result?.alternatives?.[0]?.message?.text || "";
  onDelta(text);
  return text;
}

// --- Общий парсер Server-Sent Events --------------------------------------

async function readSSE(res, onDelta, extract) {
  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let full = "";
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop() || "";
    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed.startsWith("data:")) continue;
      const payload = trimmed.slice(5).trim();
      if (payload === "[DONE]" || payload === "") continue;
      try {
        const chunk = extract(JSON.parse(payload));
        if (chunk) {
          full += chunk;
          onDelta(chunk);
        }
      } catch {
        /* пропускаем неполные/служебные строки */
      }
    }
  }
  return full;
}
