import { getProviderById } from './providers.js';

function buildHeaders(provider, apiKey, extra = {}) {
  const headers = {
    'Content-Type': 'application/json',
    [provider.authHeader]: provider.authPrefix + apiKey
  };
  if (provider.id === 'anthropic') {
    headers['anthropic-version'] = '2023-06-01';
    headers['anthropic-dangerous-direct-browser-access'] = 'true';
  }
  return { ...headers, ...extra };
}

function buildOpenAIUrl(provider, settings, endpoint = 'chat/completions') {
  let baseUrl = provider.baseUrl;
  if (provider.customBaseUrl && settings.customBaseUrls && settings.customBaseUrls[provider.id]) {
    baseUrl = settings.customBaseUrls[provider.id];
  }
  return `${baseUrl}/${endpoint}`;
}

function buildGoogleUrl(provider, apiKey, model, action = 'generateContent') {
  return `${provider.baseUrl}/models/${model}:${action}?key=${apiKey}`;
}

function buildAnthropicUrl(provider, endpoint = 'messages') {
  return `${provider.baseUrl}/${endpoint}`;
}

function convertToOpenAIMessages(messages, systemPrompt) {
  const result = [];
  if (systemPrompt) {
    result.push({ role: 'system', content: systemPrompt });
  }
  for (const msg of messages) {
    const converted = { role: msg.role, content: msg.content };
    if (msg.tool_calls) converted.tool_calls = msg.tool_calls;
    if (msg.tool_call_id) converted.tool_call_id = msg.tool_call_id;
    result.push(converted);
  }
  return result;
}

function convertToAnthropicMessages(messages, systemPrompt) {
  const system = systemPrompt || '';
  const result = [];
  for (const msg of messages) {
    if (msg.role === 'system') continue;
    result.push({ role: msg.role, content: msg.content });
  }
  return { system, messages: result };
}

function convertToGoogleContents(messages, systemPrompt) {
  const contents = [];
  for (const msg of messages) {
    if (msg.role === 'system') continue;
    const role = msg.role === 'assistant' ? 'model' : 'user';
    contents.push({ role, parts: [{ text: msg.content }] });
  }
  const result = {};
  if (systemPrompt) {
    result.systemInstruction = { parts: [{ text: systemPrompt }] };
  }
  result.contents = contents;
  return result;
}

function convertToolsToOpenAI(tools) {
  if (!tools || tools.length === 0) return undefined;
  return tools.map(t => ({
    type: 'function',
    function: {
      name: t.name,
      description: t.description,
      parameters: t.parameters || { type: 'object', properties: {} }
    }
  }));
}

function convertToolsToAnthropic(tools) {
  if (!tools || tools.length === 0) return undefined;
  return tools.map(t => ({
    name: t.name,
    description: t.description,
    input_schema: t.parameters || { type: 'object', properties: {} }
  }));
}

function convertToolsToGoogle(tools) {
  if (!tools || tools.length === 0) return undefined;
  const declarations = tools.map(t => ({
    name: t.name,
    description: t.description,
    parameters: t.parameters || { type: 'object', properties: {} }
  }));
  return [{ functionDeclarations: declarations }];
}

function parseOpenAIStreamChunk(line) {
  if (!line.startsWith('data: ')) return null;
  const data = line.slice(6).trim();
  if (data === '[DONE]') return { done: true };
  try {
    const json = JSON.parse(data);
    const choice = json.choices && json.choices[0];
    if (!choice) return null;
    const delta = choice.delta || {};
    const content = delta.content || '';
    const rawToolCalls = delta.tool_calls || [];
    const toolCalls = rawToolCalls.map((tc, i) => {
      const args = tc.function?.arguments;
      return {
        id: tc.id || null,
        index: tc.index ?? i,
        type: tc.type || 'function',
        function: {
          name: tc.function?.name || '',
          arguments: args !== undefined && args !== '' ? args : null
        }
      };
    });
    const finishReason = choice.finish_reason;
    return { content, toolCalls, finishReason, done: false };
  } catch (e) {
    return null;
  }
}

function parseAnthropicStreamChunk(line) {
  if (!line.startsWith('data: ')) return null;
  const data = line.slice(6).trim();
  try {
    const json = JSON.parse(data);
    if (json.type === 'content_block_delta') {
      if (json.delta.type === 'text_delta') {
        return { content: json.delta.text, done: false };
      }
      if (json.delta.type === 'input_json_delta') {
        return { toolInputDelta: json.delta.partial_json, done: false };
      }
    }
    if (json.type === 'message_stop') {
      return { done: true };
    }
    if (json.type === 'content_block_start') {
      if (json.content_block.type === 'tool_use') {
        return { toolStart: { id: json.content_block.id, name: json.content_block.name }, done: false };
      }
    }
    return null;
  } catch (e) {
    return null;
  }
}

function parseGoogleStreamChunk(line) {
  const trimmed = line.trim();
  if (!trimmed || trimmed === 'data:') return null;
  const data = trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed;
  if (!data) return null;
  try {
    const json = JSON.parse(data);
    if (json.error) return null;
    const candidates = json.candidates || [];
    if (candidates.length === 0) return null;
    const candidate = candidates[0];
    const parts = candidate.content?.parts || [];
    let content = '';
    let toolCalls = [];
    for (const part of parts) {
      if (part.text) content += part.text;
      if (part.functionCall) {
        const fc = part.functionCall;
        toolCalls.push({
          id: `google_${Date.now()}_${Math.random().toString(36).slice(2)}`,
          type: 'function',
          function: { name: fc.name, arguments: typeof fc.args === 'string' ? fc.args : JSON.stringify(fc.args || {}) }
        });
      }
    }
    const finishReason = candidate.finishReason || null;
    return { content, toolCalls: toolCalls.length ? toolCalls : null, finishReason, done: false };
  } catch (e) {
    return null;
  }
}

async function* streamOpenAI(provider, apiKey, model, messages, tools, settings) {
  const url = buildOpenAIUrl(provider, settings);
  const openaiMessages = convertToOpenAIMessages(messages, settings.systemPrompt);
  const openaiTools = convertToolsToOpenAI(tools);
  const body = {
    model,
    messages: openaiMessages,
    stream: true,
    max_tokens: settings.maxTokens || 4096,
    temperature: settings.temperature ?? 0.7
  };
  if (openaiTools) body.tools = openaiTools;

  const response = await fetch(url, {
    method: 'POST',
    headers: buildHeaders(provider, apiKey),
    body: JSON.stringify(body),
    signal: settings.signal
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`API Error ${response.status}: ${err}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = 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 chunk = parseOpenAIStreamChunk(line);
      if (chunk) yield chunk;
    }
  }
  if (buffer.trim()) {
    const chunk = parseOpenAIStreamChunk(buffer);
    if (chunk) yield chunk;
  }
  yield { done: true };
}

async function* streamAnthropic(provider, apiKey, model, messages, tools, settings) {
  const url = buildAnthropicUrl(provider);
  const { system, anthropicMessages } = convertToAnthropicMessages(messages, settings.systemPrompt);
  const anthropicTools = convertToolsToAnthropic(tools);
  const body = {
    model,
    messages: anthropicMessages,
    stream: true,
    max_tokens: settings.maxTokens || 4096,
    temperature: settings.temperature ?? 0.7
  };
  if (system) body.system = system;
  if (anthropicTools) body.tools = anthropicTools;

  const response = await fetch(url, {
    method: 'POST',
    headers: buildHeaders(provider, apiKey),
    body: JSON.stringify(body),
    signal: settings.signal
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`API Error ${response.status}: ${err}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  let currentTool = null;
  let toolCalls = [];

  while (true) {
    const { done, value } = 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 chunk = parseAnthropicStreamChunk(line);
      if (!chunk) continue;
      if (chunk.toolStart) {
        currentTool = { id: chunk.toolStart.id, name: chunk.toolStart.name, arguments: '' };
        toolCalls.push(currentTool);
      }
      if (chunk.toolInputDelta && currentTool) {
        currentTool.arguments += chunk.toolInputDelta;
      }
      if (chunk.content) {
        yield { content: chunk.content, done: false };
      }
      if (chunk.done) {
        if (toolCalls.length > 0) {
          const parsedCalls = toolCalls.map(tc => ({
            id: tc.id,
            type: 'function',
            function: { name: tc.name, arguments: tc.arguments || '{}' }
          }));
          yield { toolCalls: parsedCalls, finishReason: 'tool_calls', done: false };
        }
        yield { done: true };
      }
    }
  }
}

async function* streamGoogle(provider, apiKey, model, messages, tools, settings) {
  const url = buildGoogleUrl(provider, apiKey, model, 'streamGenerateContent');
  const googleBody = convertToGoogleContents(messages, settings.systemPrompt);
  const googleTools = convertToolsToGoogle(tools);
  const body = {
    ...googleBody,
    generationConfig: {
      maxOutputTokens: settings.maxTokens || 4096,
      temperature: settings.temperature ?? 0.7
    }
  };
  if (googleTools) body.tools = googleTools;

  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
    signal: settings.signal
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`API Error ${response.status}: ${err}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = 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) {
      if (line.startsWith('data: ')) {
        const chunk = parseGoogleStreamChunk(line.slice(6));
        if (chunk) yield chunk;
      }
    }
  }
  yield { done: true };
}

async function* streamChat(providerId, apiKey, model, messages, tools, settings) {
  const provider = getProviderById(providerId);
  if (!provider) throw new Error(`Unknown provider: ${providerId}`);
  if (!apiKey) throw new Error(`No API key for ${provider.name}`);

  let baseUrl = provider.baseUrl;
  if (provider.customBaseUrl && settings.customBaseUrls?.[provider.id]) {
    baseUrl = settings.customBaseUrls[provider.id];
  }
  const effectiveProvider = { ...provider, baseUrl };

  switch (provider.format) {
    case 'openai':
      yield* streamOpenAI(effectiveProvider, apiKey, model, messages, tools, settings);
      break;
    case 'anthropic':
      yield* streamAnthropic(effectiveProvider, apiKey, model, messages, tools, settings);
      break;
    case 'google':
      yield* streamGoogle(effectiveProvider, apiKey, model, messages, tools, settings);
      break;
    default:
      yield* streamOpenAI(effectiveProvider, apiKey, model, messages, tools, settings);
  }
}

async function testConnection(providerId, apiKey, settings) {
  const provider = getProviderById(providerId);
  if (!provider) throw new Error(`Unknown provider: ${providerId}`);
  if (!apiKey) throw new Error('No API key provided');

  let baseUrl = provider.baseUrl;
  if (provider.customBaseUrl && settings.customBaseUrls?.[providerId]) {
    baseUrl = settings.customBaseUrls[providerId];
  }

  const modelsUrl = provider.modelsUrl ? provider.modelsUrl.replace(provider.baseUrl, baseUrl) : null;

  if (modelsUrl) {
    const response = await fetch(modelsUrl, {
      method: 'GET',
      headers: buildHeaders(provider, apiKey)
    });
    if (response.status === 401 || response.status === 403) {
      return { success: true, message: 'Endpoint erreichbar (Auth erforderlich)' };
    }
    if (response.ok) {
      return { success: true, message: 'Verbindung erfolgreich!' };
    }
    throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  }

  if (provider.format === 'google') {
    const url = `${baseUrl}/models?key=${apiKey}`;
    const response = await fetch(url);
    if (response.status === 401 || response.status === 403) {
      return { success: true, message: 'Endpoint erreichbar (Auth erforderlich)' };
    }
    if (response.ok) return { success: true, message: 'Verbindung erfolgreich!' };
    throw new Error(`HTTP ${response.status}`);
  }

  const chatUrl = `${baseUrl}/chat/completions`;
  const response = await fetch(chatUrl, {
    method: 'POST',
    headers: buildHeaders(provider, apiKey),
    body: JSON.stringify({
      model: provider.models[0] || 'default',
      messages: [{ role: 'user', content: 'hi' }],
      max_tokens: 1,
      stream: false
    })
  });
  if (response.status === 401 || response.status === 403) {
    return { success: true, message: 'Endpoint erreichbar (Auth erforderlich)' };
  }
  if (response.ok) return { success: true, message: 'Verbindung erfolgreich!' };
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

export { streamChat, testConnection, convertToolsToOpenAI, convertToolsToAnthropic, convertToolsToGoogle };
