import { describe, expect, it } from 'vitest'
import {
  AuthError,
  QuotaExhaustedError,
  RateLimitError,
  TransientError,
  type AIRequest,
} from './index.js'
import {
  classifyOpenAiCompatError,
  makeOpenAiCompatAdapter,
  mapContentToOpenAi,
  mapMessagesToOpenAi,
  mapOpenAiResponse,
} from './openai-compat.js'

describe('openai-compat adapter', () => {
  const req: AIRequest = {
    model: 'gpt-4o',
    messages: [
      {
        role: 'user',
        content: [
          { type: 'text', text: 'describe' },
          {
            type: 'image',
            image: { mediaType: 'image/png', data: 'abc123' },
          },
        ],
      },
    ],
    maxTokens: 100,
  }

  it('maps request content blocks to OpenAI wire shape', () => {
    const wire = mapMessagesToOpenAi(req.messages)
    expect(wire[0]?.content).toEqual([
      { type: 'text', text: 'describe' },
      { type: 'image_url', image_url: { url: 'data:image/png;base64,abc123' } },
    ])
  })

  it('round-trips image blocks through mapContentToOpenAi', () => {
    const blocks = mapContentToOpenAi(req.messages[0]!.content as never)
    expect(blocks).toEqual([
      { type: 'text', text: 'describe' },
      { type: 'image_url', image_url: { url: 'data:image/png;base64,abc123' } },
    ])
  })

  it('maps response usage to promptTokens/completionTokens without cost', async () => {
    const fakeFetch: typeof fetch = async () =>
      new Response(
        JSON.stringify({
          model: 'gpt-4o',
          choices: [{ message: { content: 'hello' }, finish_reason: 'stop' }],
          usage: { prompt_tokens: 11, completion_tokens: 7 },
        }),
        { status: 200, headers: { 'Content-Type': 'application/json' } },
      )

    const adapter = makeOpenAiCompatAdapter({ apiKey: 'k', fetch: fakeFetch })
    const result = await adapter.call(req)
    expect(result.content).toBe('hello')
    expect(result.usage).toEqual({ promptTokens: 11, completionTokens: 7 })
    expect(Object.keys(result.usage)).toEqual(['promptTokens', 'completionTokens'])
  })

  it('classifies provider HTTP errors with retryable flags', () => {
    expect(classifyOpenAiCompatError(429, 'rate', 'openai-compat')).toBeInstanceOf(RateLimitError)
    expect(classifyOpenAiCompatError(429, 'quota exceeded', 'openai-compat')).toBeInstanceOf(
      QuotaExhaustedError,
    )
    expect(classifyOpenAiCompatError(500, 'boom', 'openai-compat')).toBeInstanceOf(TransientError)
    expect(classifyOpenAiCompatError(401, 'bad key', 'openai-compat')).toBeInstanceOf(AuthError)
    expect(classifyOpenAiCompatError(401, 'bad key', 'openai-compat').retryable).toBe(false)
  })

  it('maps mapOpenAiResponse without cost fields', () => {
    const mapped = mapOpenAiResponse(
      {
        model: 'gpt-4o',
        choices: [{ message: { content: 'x' }, finish_reason: 'stop' }],
        usage: { prompt_tokens: 2, completion_tokens: 3 },
      },
      'openai-compat',
      'gpt-4o',
    )
    expect(mapped.usage).toEqual({ promptTokens: 2, completionTokens: 3 })
    expect('cost' in mapped.usage).toBe(false)
  })
})
