import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  AIProvider,
  ProviderOptions,
  ProviderResult,
  ProviderError,
  ProviderUnavailableError,
} from '../add/src/providers/provider.js';
import { AnthropicProvider } from '../add/src/providers/anthropic.js';
import { OpenAIProvider } from '../add/src/providers/openai.js';
import { GeminiProvider } from '../add/src/providers/gemini.js';
import {
  createProvider,
  createProviderFromEnv,
  getAvailableProviders,
  getDefaultProvider,
} from '../add/src/providers/index.js';

describe('Provider Interface', () => {
  it('should define required methods', () => {
    const mockProvider: AIProvider = {
      name: 'mock',
      type: 'claude',
      execute: vi.fn(),
      stream: vi.fn(),
      isAvailable: vi.fn(),
      getModels: vi.fn(),
      getDefaultModel: vi.fn(),
    };

    expect(mockProvider.name).toBe('mock');
    expect(mockProvider.type).toBe('claude');
    expect(typeof mockProvider.execute).toBe('function');
    expect(typeof mockProvider.stream).toBe('function');
    expect(typeof mockProvider.isAvailable).toBe('function');
    expect(typeof mockProvider.getModels).toBe('function');
    expect(typeof mockProvider.getDefaultModel).toBe('function');
  });
});

describe('AnthropicProvider', () => {
  let originalEnv: NodeJS.ProcessEnv;

  beforeEach(() => {
    originalEnv = { ...process.env };
    delete process.env.ANTHROPIC_API_KEY;
    delete process.env.AGENT_CODE_OAUTH_TOKEN;
  });

  afterEach(() => {
    process.env = originalEnv;
  });

  it('should not be available without API key', () => {
    const provider = new AnthropicProvider();
    expect(provider.isAvailable()).toBe(false);
  });

  it('should be available with API key', () => {
    process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
    const provider = new AnthropicProvider();
    expect(provider.isAvailable()).toBe(true);
  });

  it('should be available with OAuth token', () => {
    process.env.AGENT_CODE_OAUTH_TOKEN = 'test-token';
    const provider = new AnthropicProvider();
    expect(provider.isAvailable()).toBe(true);
  });

  it('should return correct default model', () => {
    const provider = new AnthropicProvider();
    expect(provider.getDefaultModel()).toBe('claude-sonnet-4-20250514');
  });

  it('should return list of models', async () => {
    const provider = new AnthropicProvider();
    const models = await provider.getModels();
    expect(models).toContain('claude-sonnet-4-20250514');
    expect(models).toContain('claude-opus-4-20250514');
  });

  it('should throw when not available', async () => {
    const provider = new AnthropicProvider();
    await expect(provider.execute('test')).rejects.toThrow(
      ProviderUnavailableError,
    );
  });
});

describe('OpenAIProvider', () => {
  let originalEnv: NodeJS.ProcessEnv;

  beforeEach(() => {
    originalEnv = { ...process.env };
    delete process.env.OPENAI_API_KEY;
  });

  afterEach(() => {
    process.env = originalEnv;
  });

  it('should not be available without API key', () => {
    const provider = new OpenAIProvider();
    expect(provider.isAvailable()).toBe(false);
  });

  it('should be available with API key', () => {
    process.env.OPENAI_API_KEY = 'sk-test';
    const provider = new OpenAIProvider();
    expect(provider.isAvailable()).toBe(true);
  });

  it('should return correct default model', () => {
    const provider = new OpenAIProvider();
    expect(provider.getDefaultModel()).toBe('gpt-4o');
  });

  it('should return list of models', async () => {
    const provider = new OpenAIProvider();
    const models = await provider.getModels();
    expect(models).toContain('gpt-4o');
    expect(models).toContain('gpt-4-turbo');
  });

  it('should throw when not available', async () => {
    const provider = new OpenAIProvider();
    await expect(provider.execute('test')).rejects.toThrow(
      ProviderUnavailableError,
    );
  });
});

describe('GeminiProvider', () => {
  let originalEnv: NodeJS.ProcessEnv;

  beforeEach(() => {
    originalEnv = { ...process.env };
    delete process.env.GOOGLE_API_KEY;
  });

  afterEach(() => {
    process.env = originalEnv;
  });

  it('should not be available without API key', () => {
    const provider = new GeminiProvider();
    expect(provider.isAvailable()).toBe(false);
  });

  it('should be available with API key', () => {
    process.env.GOOGLE_API_KEY = 'test-key';
    const provider = new GeminiProvider();
    expect(provider.isAvailable()).toBe(true);
  });

  it('should return correct default model', () => {
    const provider = new GeminiProvider();
    expect(provider.getDefaultModel()).toBe('gemini-pro');
  });

  it('should return list of models', async () => {
    const provider = new GeminiProvider();
    const models = await provider.getModels();
    expect(models).toContain('gemini-pro');
    expect(models).toContain('gemini-1.5-pro');
  });

  it('should throw when not available', async () => {
    const provider = new GeminiProvider();
    await expect(provider.execute('test')).rejects.toThrow(
      ProviderUnavailableError,
    );
  });
});

describe('Provider Factory', () => {
  let originalEnv: NodeJS.ProcessEnv;

  beforeEach(() => {
    originalEnv = { ...process.env };
    delete process.env.ANTHROPIC_API_KEY;
    delete process.env.OPENAI_API_KEY;
    delete process.env.GOOGLE_API_KEY;
    delete process.env.AI_PROVIDER;
  });

  afterEach(() => {
    process.env = originalEnv;
  });

  it('should create provider by type', () => {
    const claude = createProvider('claude');
    expect(claude.name).toBe('Anthropic');
    expect(claude.type).toBe('claude');

    const openai = createProvider('openai');
    expect(openai.name).toBe('OpenAI');
    expect(openai.type).toBe('openai');

    const gemini = createProvider('gemini');
    expect(gemini.name).toBe('Gemini');
    expect(gemini.type).toBe('gemini');
  });

  it('should get available providers', () => {
    expect(getAvailableProviders()).toEqual([]);

    process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
    expect(getAvailableProviders()).toEqual(['claude']);

    process.env.OPENAI_API_KEY = 'sk-test';
    expect(getAvailableProviders()).toEqual(['claude', 'openai']);

    process.env.GOOGLE_API_KEY = 'test-key';
    expect(getAvailableProviders()).toEqual(['claude', 'openai', 'gemini']);
  });

  it('should get default provider', () => {
    process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
    expect(getDefaultProvider()).toBe('claude');

    delete process.env.ANTHROPIC_API_KEY;
    process.env.OPENAI_API_KEY = 'sk-test';
    expect(getDefaultProvider()).toBe('openai');

    delete process.env.OPENAI_API_KEY;
    process.env.GOOGLE_API_KEY = 'test-key';
    expect(getDefaultProvider()).toBe('gemini');
  });

  it('should throw when no providers available', () => {
    expect(() => getDefaultProvider()).toThrow(ProviderError);
  });

  it('should create provider from environment', () => {
    process.env.ANTHROPIC_API_KEY = 'sk-ant-test';
    process.env.AI_PROVIDER = 'claude';

    const provider = createProviderFromEnv();
    expect(provider.name).toBe('Anthropic');
    expect(provider.type).toBe('claude');
  });

  it('should throw on invalid provider type', () => {
    process.env.AI_PROVIDER = 'invalid';
    expect(() => createProviderFromEnv()).toThrow(ProviderError);
  });
});

describe('ProviderError', () => {
  it('should create error with all fields', () => {
    const error = new ProviderError('Test error', 'claude', 'TEST_CODE', 500);
    expect(error.message).toBe('Test error');
    expect(error.provider).toBe('claude');
    expect(error.code).toBe('TEST_CODE');
    expect(error.statusCode).toBe(500);
    expect(error.name).toBe('ProviderError');
  });

  it('should create error with minimal fields', () => {
    const error = new ProviderError('Test error', 'claude');
    expect(error.message).toBe('Test error');
    expect(error.provider).toBe('claude');
    expect(error.code).toBeUndefined();
    expect(error.statusCode).toBeUndefined();
  });
});

describe('ProviderUnavailableError', () => {
  it('should create unavailable error', () => {
    const error = new ProviderUnavailableError(
      'claude',
      'API key not configured',
    );
    expect(error.message).toBe(
      'Provider claude unavailable: API key not configured',
    );
    expect(error.provider).toBe('claude');
    expect(error.code).toBe('UNAVAILABLE');
    expect(error.name).toBe('ProviderUnavailableError');
  });
});
