// Ollama API Client
// Connects to local Granite 3.1 MoE model for AI-powered features

import { OLLAMA_CONFIG } from '@/lib/constants'

interface ChatMessage {
  role: 'system' | 'user' | 'assistant'
  content: string
}

interface ChatOptions {
  temperature?: number
  maxTokens?: number
  topP?: number
  frequencyPenalty?: number
  presencePenalty?: number
}

interface OllamaResponse {
  model: string
  response: string
  done: boolean
  totalDuration?: number
  evalCount?: number
}

interface OllamaModel {
  name: string
  model: string
  modified_at: string
  size: number
  digest: string
  details: {
    parent_model: string
    format: string
    family: string
    families: string[]
    parameter_size: string
    quantization_level: string
  }
}

interface EmbeddingResponse {
  embedding: number[]
}

/**
 * Build the Ollama API URL
 */
function getBaseUrl(): string {
  return OLLAMA_CONFIG.baseUrl.replace(/\/$/, '')
}

/**
 * Main chat completion using Ollama API
 */
export async function chatCompletion(
  messages: ChatMessage[],
  model?: string,
  options?: ChatOptions
): Promise<string> {
  const baseUrl = getBaseUrl()
  const selectedModel = model || OLLAMA_CONFIG.defaultModel

  try {
    const response = await fetch(`${baseUrl}/api/chat`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        model: selectedModel,
        messages,
        stream: false,
        options: {
          temperature: options?.temperature ?? OLLAMA_CONFIG.temperature,
          num_predict: options?.maxTokens ?? OLLAMA_CONFIG.maxTokens,
          top_p: options?.topP,
          frequency_penalty: options?.frequencyPenalty,
          presence_penalty: options?.presencePenalty,
        },
      }),
    })

    if (!response.ok) {
      throw new Error(`Ollama API error: ${response.status} ${response.statusText}`)
    }

    const data: OllamaResponse = await response.json()
    return data.response
  } catch (error) {
    // Try fallback model if default fails
    if (!model && OLLAMA_CONFIG.fallbackModel) {
      console.warn(
        `Model ${selectedModel} failed, trying fallback ${OLLAMA_CONFIG.fallbackModel}:`,
        error instanceof Error ? error.message : error
      )
      return chatCompletion(messages, OLLAMA_CONFIG.fallbackModel, options)
    }
    throw error
  }
}

/**
 * Generate text embedding for semantic search
 */
export async function generateEmbedding(text: string): Promise<number[]> {
  const baseUrl = getBaseUrl()

  const response = await fetch(`${baseUrl}/api/embeddings`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'nomic-embed-text',
      prompt: text,
    }),
  })

  if (!response.ok) {
    throw new Error(`Embedding API error: ${response.status} ${response.statusText}`)
  }

  const data: EmbeddingResponse = await response.json()
  return data.embedding
}

/**
 * List available Ollama models
 */
export async function listModels(): Promise<OllamaModel[]> {
  const baseUrl = getBaseUrl()

  const response = await fetch(`${baseUrl}/api/tags`, {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' },
  })

  if (!response.ok) {
    throw new Error(`Failed to list models: ${response.status}`)
  }

  const data = await response.json()
  return data.models || []
}

/**
 * Check if Ollama server is healthy and responsive
 */
export async function isHealthy(): Promise<boolean> {
  try {
    const baseUrl = getBaseUrl()
    const controller = new AbortController()
    const timeout = setTimeout(() => controller.abort(), 5000)

    const response = await fetch(`${baseUrl}/api/tags`, {
      method: 'GET',
      signal: controller.signal,
    })

    clearTimeout(timeout)
    return response.ok
  } catch {
    return false
  }
}

/**
 * Streaming chat completion - calls onChunk callback for each text chunk
 */
export async function streamChat(
  messages: ChatMessage[],
  onChunk: (chunk: string) => void,
  model?: string,
  options?: ChatOptions
): Promise<string> {
  const baseUrl = getBaseUrl()
  const selectedModel = model || OLLAMA_CONFIG.defaultModel
  let fullResponse = ''

  try {
    const response = await fetch(`${baseUrl}/api/chat`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        model: selectedModel,
        messages,
        stream: true,
        options: {
          temperature: options?.temperature ?? OLLAMA_CONFIG.temperature,
          num_predict: options?.maxTokens ?? OLLAMA_CONFIG.maxTokens,
        },
      }),
    })

    if (!response.ok || !response.body) {
      throw new Error(`Ollama streaming error: ${response.status}`)
    }

    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      const chunk = decoder.decode(value, { stream: true })
      const lines = chunk.split('\n').filter(Boolean)

      for (const line of lines) {
        try {
          const parsed: OllamaResponse = JSON.parse(line)
          if (parsed.response) {
            fullResponse += parsed.response
            onChunk(parsed.response)
          }
          if (parsed.done) break
        } catch {
          // Skip malformed JSON lines
        }
      }
    }

    return fullResponse
  } catch (error) {
    // Try fallback model if default fails
    if (!model && OLLAMA_CONFIG.fallbackModel) {
      console.warn(
        `Stream model ${selectedModel} failed, trying fallback ${OLLAMA_CONFIG.fallbackModel}`
      )
      return streamChat(messages, onChunk, OLLAMA_CONFIG.fallbackModel, options)
    }
    throw error
  }
}

/**
 * Simple generate endpoint wrapper
 */
export async function generate(
  prompt: string,
  model?: string,
  system?: string
): Promise<string> {
  const messages: ChatMessage[] = []
  if (system) {
    messages.push({ role: 'system', content: system })
  }
  messages.push({ role: 'user', content: prompt })
  return chatCompletion(messages, model)
}
