import {
  AuthError,
  FatalError,
  QuotaExhaustedError,
  RateLimitError,
  TransientError,
  type AIContentBlock,
  type AIRequest,
  type AIMessage,
} from './index.js'

export type OpenAiCompatCreds = {
  apiKey: string
  baseUrl?: string
  fetch?: typeof fetch
}

type OpenAiChatMessage = {
  role: 'system' | 'user' | 'assistant'
  content: string | Array<{ type: string; text?: string; image_url?: { url: string } }>
}

type OpenAiChatRequest = {
  model: string
  messages: OpenAiChatMessage[]
  max_tokens?: number
  temperature?: number
  stop?: string | string[]
}

type OpenAiChatResponse = {
  model: string
  choices: Array<{ message: { content: string | null }; finish_reason: string | null }>
  usage?: { prompt_tokens: number; completion_tokens: number }
}

export function mapMessagesToOpenAi(messages: AIMessage[]): OpenAiChatMessage[] {
  return messages.map((message) => ({
    role: message.role,
    content: mapContentToOpenAi(message.content),
  }))
}

export function mapContentToOpenAi(
  content: string | AIContentBlock[],
): string | Array<{ type: string; text?: string; image_url?: { url: string } }> {
  if (typeof content === 'string') return content
  return content.map((block) => {
    if (block.type === 'text') {
      return { type: 'text', text: block.text }
    }
    const img = block.image
    return {
      type: 'image_url',
      image_url: { url: `data:${img.mediaType};base64,${img.data}` },
    }
  })
}

export function mapOpenAiResponse(
  body: OpenAiChatResponse,
  provider: string,
  model: string,
) {
  const choice = body.choices[0]
  return {
    content: choice?.message.content ?? '',
    model: body.model || model,
    provider,
    usage: {
      promptTokens: body.usage?.prompt_tokens ?? 0,
      completionTokens: body.usage?.completion_tokens ?? 0,
    },
    finishReason: choice?.finish_reason ?? undefined,
  }
}

export function classifyOpenAiCompatError(status: number, message: string, provider: string) {
  if (status === 429) {
    if (/quota/i.test(message)) {
      return new QuotaExhaustedError(message, provider)
    }
    return new RateLimitError(message, provider)
  }
  if (status === 401 || status === 403) {
    return new AuthError(message, provider)
  }
  if (status >= 500) {
    return new TransientError(message, provider)
  }
  return new FatalError(message, provider)
}

export function makeOpenAiCompatAdapter(creds: OpenAiCompatCreds) {
  const baseUrl = creds.baseUrl ?? 'https://api.openai.com/v1'
  const fetchImpl = creds.fetch ?? fetch

  return {
    provider: 'openai-compat',
    supportsImage: true,
    async call(req: AIRequest) {
      const payload: OpenAiChatRequest = {
        model: req.model,
        messages: mapMessagesToOpenAi(req.messages),
        ...(req.maxTokens !== undefined ? { max_tokens: req.maxTokens } : {}),
        ...(req.temperature !== undefined ? { temperature: req.temperature } : {}),
        ...(req.stop !== undefined ? { stop: req.stop } : {}),
      }

      const response = await fetchImpl(`${baseUrl}/chat/completions`, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${creds.apiKey}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload),
      })

      const text = await response.text()
      let body: unknown
      try {
        body = JSON.parse(text)
      } catch {
        throw new TransientError(`Invalid JSON from provider: ${text.slice(0, 120)}`, 'openai-compat')
      }

      if (!response.ok) {
        const message =
          typeof body === 'object' &&
          body !== null &&
          'error' in body &&
          typeof (body as { error?: { message?: string } }).error?.message === 'string'
            ? (body as { error: { message: string } }).error.message
            : text
        throw classifyOpenAiCompatError(response.status, message, 'openai-compat')
      }

      return mapOpenAiResponse(body as OpenAiChatResponse, 'openai-compat', req.model)
    },
  }
}
