/**
 * Integration Test: ApiClient
 * 
 * Tests the ApiClient infrastructure for making HTTP requests
 * - Validates request/response handling
 * - Tests error handling and timeouts
 * - Verifies health check functionality
 * 
 * Focus: Infrastructure testing, NOT business logic
 */

import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import { ApiClient } from './api-client';

describe('ApiClient - Infrastructure Tests', () => {
  let apiClient: ApiClient;
  let mockServer: { close: () => void; port: number };

  beforeAll(async () => {
    // Create a mock HTTP server for testing
    const http = require('http');
    const server = http.createServer((req: any, res: any) => {
      if (req.url === '/health') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ status: 'ok' }));
      } else if (req.url === '/api/data') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ message: 'success', data: { id: 1, name: 'test' } }));
      } else if (req.url === '/api/error') {
        res.writeHead(500, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Internal Server Error' }));
      } else if (req.url === '/api/slow') {
        // Simulate slow response
        setTimeout(() => {
          res.writeHead(200, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ message: 'slow response' }));
        }, 2000);
      } else {
        res.writeHead(404);
        res.end('Not Found');
      }
    });

    await new Promise<void>((resolve) => {
      server.listen(0, () => {
        const port = (server.address() as any).port;
        mockServer = { close: () => server.close(), port };
        apiClient = new ApiClient({ baseUrl: `http://localhost:${port}`, timeout: 5000 });
        resolve();
      });
    });
  });

  afterAll(() => {
    if (mockServer) {
      mockServer.close();
    }
  });

  describe('GET Requests', () => {
    it('should successfully make a GET request', async () => {
      // Given: An API client configured with a mock server
      // When: Making a GET request to /api/data
      const result = await apiClient.get<{ message: string; data: { id: number; name: string } }>('/api/data');

      // Then: The response should contain the expected data
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
      expect(result.data.id).toBe(1);
      expect(result.data.name).toBe('test');
    });

    it('should handle GET request with custom headers', async () => {
      // Given: An API client configured with a mock server
      // When: Making a GET request with custom headers
      const result = await apiClient.get<{ message: string }>('/api/data', {
        'X-Custom-Header': 'test-value',
        'Authorization': 'Bearer token123',
      });

      // Then: The request should succeed
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });
  });

  describe('POST Requests', () => {
    it('should successfully make a POST request with body', async () => {
      // Given: An API client configured with a mock server
      const requestBody = { name: 'test', value: 123 };

      // When: Making a POST request to /api/data
      const result = await apiClient.post<{ message: string; data: any }>('/api/data', requestBody);

      // Then: The response should contain the expected data
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });

    it('should handle POST request with custom headers', async () => {
      // Given: An API client configured with a mock server
      const requestBody = { test: 'data' };

      // When: Making a POST request with custom headers
      const result = await apiClient.post<{ message: string }>('/api/data', requestBody, {
        'X-Request-ID': 'test-123',
      });

      // Then: The request should succeed
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });
  });

  describe('PUT Requests', () => {
    it('should successfully make a PUT request with body', async () => {
      // Given: An API client configured with a mock server
      const requestBody = { id: 1, name: 'updated' };

      // When: Making a PUT request to /api/data
      const result = await apiClient.put<{ message: string }>('/api/data', requestBody);

      // Then: The response should contain the expected data
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });
  });

  describe('PATCH Requests', () => {
    it('should successfully make a PATCH request with body', async () => {
      // Given: An API client configured with a mock server
      const requestBody = { name: 'patched' };

      // When: Making a PATCH request to /api/data
      const result = await apiClient.patch<{ message: string }>('/api/data', requestBody);

      // Then: The response should contain the expected data
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });
  });

  describe('DELETE Requests', () => {
    it('should successfully make a DELETE request', async () => {
      // Given: An API client configured with a mock server
      // When: Making a DELETE request to /api/data
      const result = await apiClient.delete<{ message: string }>('/api/data');

      // Then: The response should contain the expected data
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });
  });

  describe('Error Handling', () => {
    it('should handle HTTP errors gracefully', async () => {
      // Given: An API client configured with a mock server
      // When: Making a request to an endpoint that returns an error
      // Then: Should throw an error with status code
      await expect(apiClient.get('/api/error')).rejects.toThrow('API Error 500');
    });

    it('should handle 404 errors', async () => {
      // Given: An API client configured with a mock server
      // When: Making a request to a non-existent endpoint
      // Then: Should throw an error
      await expect(apiClient.get('/non-existent')).rejects.toThrow();
    });

    it('should handle timeout errors', async () => {
      // Given: An API client with a short timeout
      const shortTimeoutClient = new ApiClient({
        baseUrl: `http://localhost:${mockServer.port}`,
        timeout: 100, // 100ms timeout
      });

      // When: Making a request to a slow endpoint
      // Then: Should throw a timeout error
      await expect(shortTimeoutClient.get('/api/slow')).rejects.toThrow('Request timeout after 100ms');
    });
  });

  describe('Health Check', () => {
    it('should successfully check health endpoint', async () => {
      // Given: An API client configured with a mock server
      // When: Checking health
      const isHealthy = await apiClient.health();

      // Then: Should return true if healthy
      expect(isHealthy).toBe(true);
    });

    it('should return false when health check fails', async () => {
      // Given: An API client configured with a non-existent server
      const unhealthyClient = new ApiClient({
        baseUrl: 'http://localhost:9999', // Non-existent server
        timeout: 100,
      });

      // When: Checking health
      const isHealthy = await unhealthyClient.health();

      // Then: Should return false
      expect(isHealthy).toBe(false);
    });
  });

  describe('Wait For Ready', () => {
    it('should wait for API to be ready', async () => {
      // Given: An API client configured with a mock server
      // When: Waiting for the API to be ready
      await apiClient.waitForReady(5000);

      // Then: Should complete without throwing
      // (This test passes if waitForReady completes successfully)
      expect(true).toBe(true);
    });

    it('should timeout if API never becomes ready', async () => {
      // Given: An API client configured with a non-existent server
      const unhealthyClient = new ApiClient({
        baseUrl: 'http://localhost:9999',
        timeout: 100,
      });

      // When: Waiting for the API to be ready with a short timeout
      // Then: Should throw a timeout error
      await expect(unhealthyClient.waitForReady(500)).rejects.toThrow('API failed to become ready within 500ms');
    });
  });

  describe('Request Configuration', () => {
    it('should use custom timeout', async () => {
      // Given: An API client with a custom timeout
      const customTimeoutClient = new ApiClient({
        baseUrl: `http://localhost:${mockServer.port}`,
        timeout: 10000, // 10 seconds
      });

      // When: Making a request
      const result = await customTimeoutClient.get<{ message: string }>('/api/data');

      // Then: The request should succeed
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });

    it('should handle trailing slash in base URL', async () => {
      // Given: An API client with a base URL that has a trailing slash
      const clientWithTrailingSlash = new ApiClient({
        baseUrl: `http://localhost:${mockServer.port}/`,
        timeout: 5000,
      });

      // When: Making a request
      const result = await clientWithTrailingSlash.get<{ message: string }>('/api/data');

      // Then: The request should succeed
      expect(result).toBeDefined();
      expect(result.message).toBe('success');
    });
  });
});
