/**
 * Tests for LLM Service connection validation enhancement (Story 4.2)
 *
 * Tests the enhanced validateConnection method that returns detailed
 * ConnectionValidationResult instead of a simple boolean.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { LLMService } from './llm-service';
import { ApiErrorType, createValidationSuccess, createValidationError } from '@/types/settings';

// Mock fetch globally
const mockFetch = vi.fn();
global.fetch = mockFetch;

describe('LLM Service - Connection Validation (Story 4.2)', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('validateConnection with detailed results', () => {
    const testBaseUrl = 'https://api.openai.com/v1';
    const testApiKey = 'sk-test-key';
    const testModel = 'gpt-4o';

    it('should return success result for valid connection', async () => {
      // Mock proxy success response
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => ({
          success: true,
          data: { text: 'hi' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result).toEqual({
        isValid: true,
      });
    });

    it('should return INVALID_KEY for 401 Unauthorized', async () => {
      // Mock proxy error response
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 401,
        json: async () => ({
          success: false,
          error: { code: 'invalid_api_key', message: 'Invalid API key' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.INVALID_KEY);
      expect(result.errorMessage).toBeTruthy();
      expect(result.suggestedAction).toBeTruthy();
    });

    it('should return INVALID_KEY for 403 Forbidden', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 403,
        json: async () => ({
          success: false,
          error: { code: 'access_denied', message: 'Access denied' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.INVALID_KEY);
    });

    it('should return MODEL_NOT_FOUND for 404 with model error', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 404,
        json: async () => ({
          success: false,
          error: { code: 'model_not_found', message: 'Model not found' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.MODEL_NOT_FOUND);
    });

    it('should return INVALID_URL for 404 without model error', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 404,
        json: async () => ({
          success: false,
          error: { code: 'not_found', message: 'Endpoint not found' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.INVALID_URL);
    });

    it('should return QUOTA_EXCEEDED for 429 rate limit', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 429,
        json: async () => ({
          success: false,
          error: { code: 'rate_limit_exceeded', message: 'Rate limit exceeded' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.QUOTA_EXCEEDED);
    });

    it('should return NETWORK_ERROR for fetch failures', async () => {
      mockFetch.mockRejectedValueOnce(new TypeError('Failed to fetch'));

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.NETWORK_ERROR);
    });

    it('should return NETWORK_ERROR for timeout errors', async () => {
      const timeoutError = new Error('Request timeout');
      (timeoutError as any).name = 'AbortError';

      mockFetch.mockRejectedValueOnce(timeoutError);

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.NETWORK_ERROR);
    });

    it('should return UNKNOWN for other 5xx errors', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: async () => ({
          success: false,
          error: { code: 'internal_error', message: 'Internal server error' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.UNKNOWN);
    });

    it('should return UNKNOWN for 400 bad request with details', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
        json: async () => ({
          success: false,
          error: { code: 'invalid_request', message: 'Invalid request' }
        }),
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.isValid).toBe(false);
      expect(result.errorType).toBe(ApiErrorType.UNKNOWN);
      expect(result.errorMessage).toBeTruthy();
    });

    it('should include raw error in result for debugging', async () => {
      const rawErrorResponse = {
        success: false,
        error: { code: 'invalid_api_key', message: 'Invalid key' }
      };

      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 401,
        json: async () => rawErrorResponse,
      });

      const result = await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(result.rawError).toBeDefined();
    });

    it('should make correct API request with minimal payload', async () => {
      // Mock success response
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
        json: async () => ({
          success: true,
          data: { text: 'hi' }
        }),
      });

      await LLMService.validateConnection(testBaseUrl, testApiKey, testModel);

      expect(mockFetch).toHaveBeenCalledTimes(1);
      // Expect call to Proxy
      expect(mockFetch).toHaveBeenCalledWith(
        expect.stringContaining('/api/llm'),
        expect.objectContaining({
          method: 'POST',
          headers: expect.objectContaining({
            'Content-Type': 'application/json',
          }),
          body: expect.stringContaining('"apiKey":"sk-test-key"')
        })
      );
    });
  });

  describe('error type detection helpers', () => {
    it('should detect quota exceeded from various error codes', async () => {
      const quotaErrorCodes = ['rate_limit_exceeded', 'quota_exceeded', 'insufficient_quota'];

      for (const code of quotaErrorCodes) {
        mockFetch.mockResolvedValueOnce({
          ok: false,
          status: 429,
          json: async () => ({ error: { code, message: 'Quota error' } }),
        });

        const result = await LLMService.validateConnection('https://api.test.com', 'key', 'model');
        expect(result.errorType).toBe(ApiErrorType.QUOTA_EXCEEDED);
      }
    });

    it('should detect model not found from various error codes', async () => {
      const modelErrorCodes = ['model_not_found', 'invalid_model', 'model_does_not_exist'];

      for (const code of modelErrorCodes) {
        mockFetch.mockResolvedValueOnce({
          ok: false,
          status: 404,
          json: async () => ({ error: { code, message: 'Model error' } }),
        });

        const result = await LLMService.validateConnection('https://api.test.com', 'key', 'model');
        expect(result.errorType).toBe(ApiErrorType.MODEL_NOT_FOUND);
      }
    });
  });
});
