import { describe, it, expect, vi, afterEach } from 'vitest';
import { callReflectionLlm } from './llm-client.js';
import type { ReflectionLlmConfig } from './llm-client.js';

const cfg: ReflectionLlmConfig = {
  endpoint: 'http://localhost:11434/v1',
  model: 'test-model',
};

afterEach(() => {
  vi.unstubAllGlobals();
});

describe('callReflectionLlm', () => {
  it('happy path: parses tool_call arguments and extracts token usage', async () => {
    const validResult = {
      memory_changes: [],
      piece_changes: { should_edit: false },
      reasoning: 'x',
    };
    const mockResponse = {
      choices: [
        {
          message: {
            tool_calls: [
              {
                function: {
                  name: 'submit_reflection',
                  arguments: JSON.stringify(validResult),
                },
              },
            ],
          },
        },
      ],
      usage: {
        prompt_tokens: 42,
        completion_tokens: 17,
      },
    };

    vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockResponse),
    }));

    const result = await callReflectionLlm(cfg, 'system prompt', 'user prompt');

    expect(result.parsed.memory_changes).toEqual([]);
    expect(result.parsed.piece_changes.should_edit).toBe(false);
    expect(result.parsed.reasoning).toBe('x');
    expect(result.tokensIn).toBe(42);
    expect(result.tokensOut).toBe(17);
    expect(result.durationMs).toBeGreaterThanOrEqual(0);
  });

  it('error path: throws when no tool_calls present', async () => {
    const mockResponse = {
      choices: [
        {
          message: {},
        },
      ],
    };

    vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockResponse),
    }));

    await expect(callReflectionLlm(cfg, 'system prompt', 'user prompt'))
      .rejects.toThrow('no tool_call');
  });
});
