import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { runResearch } from '@/lib/token/agents/researcher';

// Mock logger
vi.mock('@/lib/logger', () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

describe('runResearch', () => {
  const originalEnv = process.env.OPENROUTER_API_KEY;

  beforeEach(() => {
    vi.clearAllMocks();
    process.env.OPENROUTER_API_KEY = 'test-openrouter-key';
  });

  afterEach(() => {
    vi.restoreAllMocks();
    if (originalEnv !== undefined) {
      process.env.OPENROUTER_API_KEY = originalEnv;
    } else {
      delete process.env.OPENROUTER_API_KEY;
    }
  });

  const baseRequest = {
    query: 'What is the best 8GB VRAM model?',
    model: 'anthropic/claude-sonnet-4.6',
    fleetContext: 'Device: desktop, 8GB VRAM, models: [qwen3:8b]',
  };

  function mockFetchResponse(
    content: string,
    options: { ok?: boolean; status?: number; costHeader?: string } = {},
  ) {
    const { ok = true, status = 200, costHeader = '0.005' } = options;
    const mockResponse = {
      ok,
      status,
      json: async () => ({
        choices: [{ message: { content } }],
        usage: { prompt_tokens: 200, completion_tokens: 300 },
        model: 'anthropic/claude-sonnet-4.6',
      }),
      text: async () => content,
      headers: new Headers({
        ...(costHeader ? { 'x-openrouter-cost': costHeader } : {}),
      }),
    };
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse));
  }

  it('should return structured research result on success', async () => {
    // Arrange
    mockFetchResponse('## Summary\nQwen3:8b is great for 8GB VRAM.\n\n### Recommendations...');

    // Act
    const result = await runResearch(baseRequest);

    // Assert
    expect(result.content).toContain('Qwen3:8b is great');
    expect(result.model).toBe('anthropic/claude-sonnet-4.6');
    expect(result.inputTokens).toBe(200);
    expect(result.outputTokens).toBe(300);
    expect(result.latencyMs).toBeGreaterThanOrEqual(0);
  });

  it('should throw on missing API key', async () => {
    // Arrange
    delete process.env.OPENROUTER_API_KEY;

    // Act & Assert
    await expect(runResearch(baseRequest)).rejects.toThrow('OPENROUTER_API_KEY not configured');
  });

  it('should throw on API error', async () => {
    // Arrange
    mockFetchResponse('Rate limit exceeded', { ok: false, status: 429 });

    // Act & Assert
    await expect(runResearch(baseRequest)).rejects.toThrow('Research API error (429)');
  });

  it('should extract cost from response headers', async () => {
    // Arrange
    mockFetchResponse('Some research content', { costHeader: '0.042' });

    // Act
    const result = await runResearch(baseRequest);

    // Assert
    expect(result.costUsd).toBe(0.042);
  });

  it('should use default temperature 0.3 and maxTokens 8192 when not provided', async () => {
    // Arrange
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({
        choices: [{ message: { content: 'result' } }],
        usage: {},
        model: 'test',
      }),
      headers: new Headers(),
      text: async () => '',
    });
    vi.stubGlobal('fetch', mockFetch);

    // Act
    await runResearch(baseRequest);

    // Assert
    const body = JSON.parse(mockFetch.mock.calls[0][1].body);
    expect(body.temperature).toBe(0.3);
    expect(body.max_tokens).toBe(8192);
  });

  it('should pass conversation history to messages', async () => {
    // Arrange
    const mockFetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({
        choices: [{ message: { content: 'result' } }],
        usage: {},
        model: 'test',
      }),
      headers: new Headers(),
      text: async () => '',
    });
    vi.stubGlobal('fetch', mockFetch);

    const history = [
      { role: 'user', content: 'previous question' },
      { role: 'assistant', content: 'previous answer' },
    ];

    // Act
    await runResearch({
      ...baseRequest,
      conversationHistory: history,
    });

    // Assert
    const body = JSON.parse(mockFetch.mock.calls[0][1].body);
    // system + 2 history + user query
    expect(body.messages).toHaveLength(4);
    expect(body.messages[0].role).toBe('system');
    expect(body.messages[1].content).toBe('previous question');
    expect(body.messages[2].content).toBe('previous answer');
    expect(body.messages[3].content).toBe('What is the best 8GB VRAM model?');
  });

  it('should return empty content when choices are missing', async () => {
    // Arrange
    const mockResponse = {
      ok: true,
      json: async () => ({
        choices: [],
        usage: {},
        model: 'test',
      }),
      headers: new Headers(),
      text: async () => '',
    };
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse));

    // Act
    const result = await runResearch(baseRequest);

    // Assert
    expect(result.content).toBe('');
  });

  it('should default to 0 tokens and 0 cost when usage/headers are missing', async () => {
    // Arrange
    const mockResponse = {
      ok: true,
      json: async () => ({
        choices: [{ message: { content: 'result' } }],
        model: 'test',
      }),
      headers: new Headers(),
      text: async () => '',
    };
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(mockResponse));

    // Act
    const result = await runResearch(baseRequest);

    // Assert
    expect(result.inputTokens).toBe(0);
    expect(result.outputTokens).toBe(0);
    expect(result.costUsd).toBe(0);
  });
});
