/**
 * Local Model Provider
 * Supports Ollama, LM Studio, and other local LLM servers
 */

export interface LocalModelConfig {
  provider: 'ollama' | 'lm-studio' | 'custom';
  baseUrl?: string | undefined; // Default: http://localhost:11434 (Ollama) or http://localhost:1234 (LM Studio)
  model: string; // Model name (e.g., "llama2", "mistral", "gpt-3.5-turbo")
  temperature?: number | undefined;
  maxTokens?: number | undefined;
  topP?: number | undefined;
  frequencyPenalty?: number | undefined;
  presencePenalty?: number | undefined;
}

export interface LocalModelResponse {
  result: string;
  model: string;
  usage?: {
    totalTokens?: number;
    promptTokens?: number;
    completionTokens?: number;
  };
  cost?: number; // Always 0 for local models
}

/**
 * Execute a local model request
 */
export async function executeLocalModel(
  config: LocalModelConfig,
  messages: Array<{ role: string; content: string }>
): Promise<LocalModelResponse> {
  const baseUrl = config.baseUrl || getDefaultBaseUrl(config.provider);
  
  if (config.provider === 'ollama') {
    return executeOllama(baseUrl, config, messages);
  } else if (config.provider === 'lm-studio') {
    return executeLMStudio(baseUrl, config, messages);
  } else if (config.provider === 'custom') {
    return executeCustom(baseUrl, config, messages);
  } else {
    throw new Error(`Unsupported local model provider: ${config.provider}`);
  }
}

function getDefaultBaseUrl(provider: 'ollama' | 'lm-studio' | 'custom'): string {
  switch (provider) {
    case 'ollama':
      return 'http://localhost:11434';
    case 'lm-studio':
      return 'http://localhost:1234';
    case 'custom':
      return 'http://localhost:8000';
    default:
      return 'http://localhost:11434';
  }
}

/**
 * Execute Ollama model
 */
async function executeOllama(
  baseUrl: string,
  config: LocalModelConfig,
  messages: Array<{ role: string; content: string }>
): Promise<LocalModelResponse> {
  // Ollama uses a simple chat API
  // Convert messages to Ollama format (it uses a single prompt string)
  const prompt = formatMessagesForOllama(messages);
  
  const response = await fetch(`${baseUrl}/api/generate`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: config.model,
      prompt: prompt,
      stream: false,
      options: {
        temperature: config.temperature ?? 0.7,
        top_p: config.topP,
        num_predict: config.maxTokens, // Ollama uses num_predict instead of max_tokens
      },
    }),
  });

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

  const data: any = await response.json();
  
  return {
    result: data.response || 'No response',
    model: config.model,
    usage: {
      totalTokens: data.eval_count ? (data.prompt_eval_count || 0) + data.eval_count : undefined,
      promptTokens: data.prompt_eval_count,
      completionTokens: data.eval_count,
    },
    cost: 0, // Local models are free
  };
}

/**
 * Execute LM Studio model (OpenAI-compatible API)
 */
async function executeLMStudio(
  baseUrl: string,
  config: LocalModelConfig,
  messages: Array<{ role: string; content: string }>
): Promise<LocalModelResponse> {
  // LM Studio uses OpenAI-compatible API
  const response = await fetch(`${baseUrl}/v1/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: config.model,
      messages: messages,
      temperature: config.temperature ?? 0.7,
      max_tokens: config.maxTokens,
      top_p: config.topP,
      frequency_penalty: config.frequencyPenalty,
      presence_penalty: config.presencePenalty,
    }),
  });

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

  const data: any = await response.json();
  const usage = data.usage || {};

  return {
    result: data.choices?.[0]?.message?.content || 'No response',
    model: data.model || config.model,
    usage: {
      totalTokens: usage.total_tokens,
      promptTokens: usage.prompt_tokens,
      completionTokens: usage.completion_tokens,
    },
    cost: 0, // Local models are free
  };
}

/**
 * Execute custom model (OpenAI-compatible API)
 */
async function executeCustom(
  baseUrl: string,
  config: LocalModelConfig,
  messages: Array<{ role: string; content: string }>
): Promise<LocalModelResponse> {
  // Custom providers should use OpenAI-compatible API
  const response = await fetch(`${baseUrl}/v1/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: config.model,
      messages: messages,
      temperature: config.temperature ?? 0.7,
      max_tokens: config.maxTokens,
      top_p: config.topP,
      frequency_penalty: config.frequencyPenalty,
      presence_penalty: config.presencePenalty,
    }),
  });

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

  const data: any = await response.json();
  const usage = data.usage || {};

  return {
    result: data.choices?.[0]?.message?.content || 'No response',
    model: data.model || config.model,
    usage: {
      totalTokens: usage.total_tokens,
      promptTokens: usage.prompt_tokens,
      completionTokens: usage.completion_tokens,
    },
    cost: 0,
  };
}

/**
 * Format messages for Ollama (simple prompt format)
 */
function formatMessagesForOllama(messages: Array<{ role: string; content: string }>): string {
  // Ollama doesn't support system messages well, so we combine them
  let prompt = '';
  
  for (const msg of messages) {
    if (msg.role === 'system') {
      prompt += `System: ${msg.content}\n\n`;
    } else if (msg.role === 'user') {
      prompt += `User: ${msg.content}\n\n`;
    } else if (msg.role === 'assistant') {
      prompt += `Assistant: ${msg.content}\n\n`;
    }
  }
  
  prompt += 'Assistant:';
  return prompt;
}

