import { describe, it, expect } from 'vitest'
import { fromOpenAICompletion } from '../src/llm/openai-common.js'
import type { ChatCompletion } from 'openai/resources/chat/completions/index.js'

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeCompletion(overrides: {
  content?: string | null
  tool_calls?: ChatCompletion.Choice['message']['tool_calls']
  finish_reason?: string
}): ChatCompletion {
  return {
    id: 'chatcmpl-test',
    object: 'chat.completion',
    created: Date.now(),
    model: 'test-model',
    choices: [
      {
        index: 0,
        message: {
          role: 'assistant',
          content: overrides.content ?? null,
          tool_calls: overrides.tool_calls,
          refusal: null,
        },
        finish_reason: (overrides.finish_reason ?? 'stop') as 'stop' | 'tool_calls',
        logprobs: null,
      },
    ],
    usage: {
      prompt_tokens: 10,
      completion_tokens: 20,
      total_tokens: 30,
    },
  }
}

const TOOL_NAMES = ['bash', 'file_read', 'file_write']

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe('fromOpenAICompletion fallback extraction', () => {
  it('returns normal tool_calls when present (no fallback)', () => {
    const completion = makeCompletion({
      content: 'Let me run a command.',
      tool_calls: [
        {
          id: 'call_123',
          type: 'function',
          function: {
            name: 'bash',
            arguments: '{"command": "ls"}',
          },
        },
      ],
      finish_reason: 'tool_calls',
    })

    const response = fromOpenAICompletion(completion, TOOL_NAMES)
    const toolBlocks = response.content.filter(b => b.type === 'tool_use')
    expect(toolBlocks).toHaveLength(1)
    expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.name).toBe('bash')
    expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.id).toBe('call_123')
    expect(response.stop_reason).toBe('tool_use')
  })

  it('extracts tool calls from text when tool_calls is absent', () => {
    const completion = makeCompletion({
      content: 'I will run this:\n{"name": "bash", "arguments": {"command": "pwd"}}',
      finish_reason: 'stop',
    })

    const response = fromOpenAICompletion(completion, TOOL_NAMES)
    const toolBlocks = response.content.filter(b => b.type === 'tool_use')
    expect(toolBlocks).toHaveLength(1)
    expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.name).toBe('bash')
    expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.input).toEqual({ command: 'pwd' })
    // stop_reason should be corrected to tool_use
    expect(response.stop_reason).toBe('tool_use')
  })

  it('does not fallback when knownToolNames is not provided', () => {
    const completion = makeCompletion({
      content: '{"name": "bash", "arguments": {"command": "ls"}}',
      finish_reason: 'stop',
    })

    const response = fromOpenAICompletion(completion)
    const toolBlocks = response.content.filter(b => b.type === 'tool_use')
    expect(toolBlocks).toHaveLength(0)
    expect(response.stop_reason).toBe('end_turn')
  })

  it('does not fallback when knownToolNames is empty', () => {
    const completion = makeCompletion({
      content: '{"name": "bash", "arguments": {"command": "ls"}}',
      finish_reason: 'stop',
    })

    const response = fromOpenAICompletion(completion, [])
    const toolBlocks = response.content.filter(b => b.type === 'tool_use')
    expect(toolBlocks).toHaveLength(0)
    expect(response.stop_reason).toBe('end_turn')
  })

  it('returns plain text when no tool calls found in text', () => {
    const completion = makeCompletion({
      content: 'Hello! How can I help you today?',
      finish_reason: 'stop',
    })

    const response = fromOpenAICompletion(completion, TOOL_NAMES)
    const toolBlocks = response.content.filter(b => b.type === 'tool_use')
    expect(toolBlocks).toHaveLength(0)
    expect(response.stop_reason).toBe('end_turn')
  })

  it('preserves text block alongside extracted tool blocks', () => {
    const completion = makeCompletion({
      content: 'Let me check:\n{"name": "file_read", "arguments": {"path": "/tmp/x"}}',
      finish_reason: 'stop',
    })

    const response = fromOpenAICompletion(completion, TOOL_NAMES)
    const textBlocks = response.content.filter(b => b.type === 'text')
    const toolBlocks = response.content.filter(b => b.type === 'tool_use')
    expect(textBlocks).toHaveLength(1)
    expect(toolBlocks).toHaveLength(1)
  })

  it('does not double-extract when native tool_calls already present', () => {
    // Text also contains a tool call JSON, but native tool_calls is populated.
    // The fallback should NOT run.
    const completion = makeCompletion({
      content: '{"name": "file_read", "arguments": {"path": "/tmp/y"}}',
      tool_calls: [
        {
          id: 'call_native',
          type: 'function',
          function: {
            name: 'bash',
            arguments: '{"command": "ls"}',
          },
        },
      ],
      finish_reason: 'tool_calls',
    })

    const response = fromOpenAICompletion(completion, TOOL_NAMES)
    const toolBlocks = response.content.filter(b => b.type === 'tool_use')
    // Should only have the native one, not the text-extracted one
    expect(toolBlocks).toHaveLength(1)
    expect(toolBlocks[0]!.type === 'tool_use' && toolBlocks[0]!.id).toBe('call_native')
  })
})
