import axios from 'axios';
import apiClient from '@/lib/api/client';

// Mock axios
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('API Client', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    localStorage.clear();
  });

  describe('Request Interceptor', () => {
    it('should add Authorization header when token exists', async () => {
      const mockToken = 'test_access_token';
      localStorage.setItem('accessToken', mockToken);

      // Mock the interceptor behavior
      const config = {
        headers: {},
      };

      // Simulate request interceptor
      const interceptedConfig = {
        ...config,
        headers: {
          ...config.headers,
          Authorization: `Bearer ${mockToken}`,
        },
      };

      expect(interceptedConfig.headers.Authorization).toBe(`Bearer ${mockToken}`);
    });

    it('should not add Authorization header when token does not exist', () => {
      const config = {
        headers: {},
      };

      // No token in localStorage
      expect(localStorage.getItem('accessToken')).toBeNull();

      // Headers should remain unchanged
      expect(config.headers).toEqual({});
    });
  });

  describe('Response Interceptor - Token Refresh', () => {
    it('should refresh token on 401 error', async () => {
      const originalRequest = {
        headers: {},
        url: '/api/v1/test',
      };

      const mockRefreshToken = 'refresh_token_123';
      const mockNewAccessToken = 'new_access_token_123';

      localStorage.setItem('refreshToken', mockRefreshToken);

      // Mock the refresh token endpoint
      mockedAxios.post.mockResolvedValueOnce({
        data: {
          data: {
            accessToken: mockNewAccessToken,
          },
        },
      });

      // Simulate the retry logic
      expect(localStorage.getItem('refreshToken')).toBe(mockRefreshToken);
    });

    it('should redirect to login on refresh token failure', () => {
      localStorage.setItem('accessToken', 'expired_token');
      localStorage.setItem('refreshToken', 'invalid_refresh_token');

      // Mock failed refresh
      mockedAxios.post.mockRejectedValueOnce({
        response: {
          status: 401,
        },
      });

      // Simulate clearing tokens
      localStorage.removeItem('accessToken');
      localStorage.removeItem('refreshToken');

      expect(localStorage.getItem('accessToken')).toBeNull();
      expect(localStorage.getItem('refreshToken')).toBeNull();
    });
  });

  describe('Error Handling', () => {
    it('should handle network errors gracefully', async () => {
      const networkError = new Error('Network Error');

      mockedAxios.get.mockRejectedValueOnce(networkError);

      try {
        await mockedAxios.get('/api/v1/test');
        fail('Should have thrown an error');
      } catch (error) {
        expect(error).toBe(networkError);
      }
    });

    it('should handle 500 server errors', async () => {
      const serverError = {
        response: {
          status: 500,
          data: {
            message: 'Internal Server Error',
          },
        },
      };

      mockedAxios.get.mockRejectedValueOnce(serverError);

      try {
        await mockedAxios.get('/api/v1/test');
        fail('Should have thrown an error');
      } catch (error) {
        expect(error).toEqual(serverError);
      }
    });
  });

  describe('Base URL Configuration', () => {
    it('should use environment variable for base URL', () => {
      const expectedBaseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3020';

      // The client should be configured with this base URL
      expect(expectedBaseURL).toBeTruthy();
    });
  });

  describe('Request Configuration', () => {
    it('should set default timeout', () => {
      // Default timeout should be configured (typically 10000ms)
      const defaultTimeout = 10000;
      expect(defaultTimeout).toBe(10000);
    });

    it('should handle FormData requests', async () => {
      const formData = new FormData();
      formData.append('file', new Blob(['test']), 'test.txt');

      mockedAxios.post.mockResolvedValueOnce({
        data: { success: true },
      });

      await mockedAxios.post('/api/v1/upload', formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      });

      expect(mockedAxios.post).toHaveBeenCalledWith(
        '/api/v1/upload',
        formData,
        expect.objectContaining({
          headers: {
            'Content-Type': 'multipart/form-data',
          },
        })
      );
    });
  });
});
