import { beforeEach, afterEach, describe, expect, it } from 'vitest';
import { ProviderService } from '../provider.service.js';

const ENV_KEYS = [
  'ANTHROPIC_API_KEY',
  'OPENAI_API_KEY',
  'ZAI_API_KEY',
  'OLLAMA_BASE_URL',
  'OLLAMA_HOST',
  'OLLAMA_MODELS',
  'MOSAIC_CUSTOM_PROVIDERS',
] as const;

type EnvKey = (typeof ENV_KEYS)[number];

describe('ProviderService', () => {
  const savedEnv = new Map<EnvKey, string | undefined>();

  beforeEach(() => {
    for (const key of ENV_KEYS) {
      savedEnv.set(key, process.env[key]);
      delete process.env[key];
    }
  });

  afterEach(() => {
    for (const key of ENV_KEYS) {
      const value = savedEnv.get(key);
      if (value === undefined) {
        delete process.env[key];
      } else {
        process.env[key] = value;
      }
    }
  });

  it('skips API-key providers when env vars are missing (no models become available)', async () => {
    const service = new ProviderService(null);
    await service.onModuleInit();

    // Pi's built-in registry may include model definitions for all providers, but
    // without API keys none of them should be available (usable).
    const availableModels = service.listAvailableModels();
    const availableProviderIds = new Set(availableModels.map((m) => m.provider));

    expect(availableProviderIds).not.toContain('anthropic');
    expect(availableProviderIds).not.toContain('openai');
    expect(availableProviderIds).not.toContain('zai');

    // Providers list may show built-in providers, but they should not be marked available
    const providers = service.listProviders();
    for (const p of providers.filter((p) => ['anthropic', 'openai', 'zai'].includes(p.id))) {
      expect(p.available).toBe(false);
    }
  });

  it('registers Anthropic provider with correct models when ANTHROPIC_API_KEY is set', async () => {
    process.env['ANTHROPIC_API_KEY'] = 'test-anthropic';

    const service = new ProviderService(null);
    await service.onModuleInit();

    const providers = service.listProviders();
    const anthropic = providers.find((p) => p.id === 'anthropic');
    expect(anthropic).toBeDefined();
    expect(anthropic!.available).toBe(true);
    expect(anthropic!.models.map((m) => m.id)).toEqual([
      'claude-opus-4-6',
      'claude-sonnet-4-6',
      'claude-haiku-4-5',
    ]);
    // All Anthropic models have 200k context window
    for (const m of anthropic!.models) {
      expect(m.contextWindow).toBe(200000);
    }
  });

  it('registers OpenAI provider with correct models when OPENAI_API_KEY is set', async () => {
    process.env['OPENAI_API_KEY'] = 'test-openai';

    const service = new ProviderService(null);
    await service.onModuleInit();

    const providers = service.listProviders();
    const openai = providers.find((p) => p.id === 'openai');
    expect(openai).toBeDefined();
    expect(openai!.available).toBe(true);
    expect(openai!.models.map((m) => m.id)).toEqual(['codex-gpt-5-4']);
  });

  it('registers Z.ai provider with correct models when ZAI_API_KEY is set', async () => {
    process.env['ZAI_API_KEY'] = 'test-zai';

    const service = new ProviderService(null);
    await service.onModuleInit();

    const providers = service.listProviders();
    const zai = providers.find((p) => p.id === 'zai');
    expect(zai).toBeDefined();
    expect(zai!.available).toBe(true);
    // Pi's registry may include additional glm variants; verify our registered model is present
    expect(zai!.models.map((m) => m.id)).toContain('glm-5');
  });

  it('registers all three providers when all keys are set', async () => {
    process.env['ANTHROPIC_API_KEY'] = 'test-anthropic';
    process.env['OPENAI_API_KEY'] = 'test-openai';
    process.env['ZAI_API_KEY'] = 'test-zai';

    const service = new ProviderService(null);
    await service.onModuleInit();

    const providerIds = service.listProviders().map((p) => p.id);
    expect(providerIds).toContain('anthropic');
    expect(providerIds).toContain('openai');
    expect(providerIds).toContain('zai');
  });

  it('can find registered Anthropic models by provider+id', async () => {
    process.env['ANTHROPIC_API_KEY'] = 'test-anthropic';

    const service = new ProviderService(null);
    await service.onModuleInit();

    const sonnet = service.findModel('anthropic', 'claude-sonnet-4-6');
    expect(sonnet).toBeDefined();
    expect(sonnet!.provider).toBe('anthropic');
    expect(sonnet!.id).toBe('claude-sonnet-4-6');
  });

  it('can find registered Z.ai models by provider+id', async () => {
    process.env['ZAI_API_KEY'] = 'test-zai';

    const service = new ProviderService(null);
    await service.onModuleInit();

    const glm = service.findModel('zai', 'glm-4.5');
    expect(glm).toBeDefined();
    expect(glm!.provider).toBe('zai');
    expect(glm!.id).toBe('glm-4.5');
  });
});
