/**
 * Tests for createFallbackChain.
 */

import { describe, it, expect } from 'vitest';
import { createFallbackChain } from '../fallback.js';
import { MockLLMProvider } from '../providers/mock.js';
import type { ChatCompletionResponse } from '../types.js';

const makeResponse = (content: string): ChatCompletionResponse => ({
  content,
  model: 'mock',
  finishReason: 'stop',
  usage: { promptTokens: 1, completionTokens: 1, totalTokens: 2 },
});

describe('createFallbackChain', () => {
  it('isConfigured returns true when at least one provider is configured', () => {
    const a = new MockLLMProvider();
    const chain = createFallbackChain([a]);
    expect(chain.isConfigured()).toBe(true);
  });

  it('isConfigured returns false when no providers are configured', () => {
    const unconfigured = {
      isConfigured: () => false,
      chatCompletion: async () => {
        throw new Error('not configured');
      },
    };
    const chain = createFallbackChain([unconfigured]);
    expect(chain.isConfigured()).toBe(false);
  });

  it('returns response from first configured provider', async () => {
    const a = new MockLLMProvider([makeResponse('from-a')]);
    const b = new MockLLMProvider([makeResponse('from-b')]);
    const chain = createFallbackChain([a, b]);

    const result = await chain.chatCompletion({ messages: [{ role: 'user', content: 'hi' }] });
    expect(result.content).toBe('from-a');
    expect(b.calls).toHaveLength(0);
  });

  it('falls back to second provider when first throws', async () => {
    const a = {
      isConfigured: () => true,
      chatCompletion: async (): Promise<ChatCompletionResponse> => {
        throw new Error('a failed');
      },
    };
    const b = new MockLLMProvider([makeResponse('from-b')]);
    const chain = createFallbackChain([a, b]);

    const result = await chain.chatCompletion({ messages: [{ role: 'user', content: 'hi' }] });
    expect(result.content).toBe('from-b');
  });

  it('skips unconfigured providers', async () => {
    const unconfigured = {
      isConfigured: () => false,
      chatCompletion: async (): Promise<ChatCompletionResponse> => {
        throw new Error('should not be called');
      },
    };
    const b = new MockLLMProvider([makeResponse('from-b')]);
    const chain = createFallbackChain([unconfigured, b]);

    const result = await chain.chatCompletion({ messages: [{ role: 'user', content: 'hi' }] });
    expect(result.content).toBe('from-b');
  });

  it('throws with all error messages when every provider fails', async () => {
    const a = {
      isConfigured: () => true,
      chatCompletion: async (): Promise<ChatCompletionResponse> => {
        throw new Error('a failed');
      },
    };
    const b = {
      isConfigured: () => true,
      chatCompletion: async (): Promise<ChatCompletionResponse> => {
        throw new Error('b failed');
      },
    };
    const chain = createFallbackChain([a, b]);

    await expect(
      chain.chatCompletion({ messages: [{ role: 'user', content: 'hi' }] })
    ).rejects.toThrow('All providers failed: a failed | b failed');
  });

  it('throws "No providers configured" when list is empty', async () => {
    const chain = createFallbackChain([]);
    await expect(
      chain.chatCompletion({ messages: [{ role: 'user', content: 'hi' }] })
    ).rejects.toThrow('No providers configured');
  });
});
