import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TokenStorage } from './tokenStorage';

// Mock dependencies
vi.mock('./tokenStorage');

const mockTokenStorage = vi.mocked(TokenStorage);

// Mock axios
const mockPost = vi.fn();
const mockAxiosInstance = {
  post: mockPost,
  get: vi.fn(),
  put: vi.fn(),
  delete: vi.fn(),
  patch: vi.fn(),
  interceptors: {
    request: { use: vi.fn(), eject: vi.fn() },
    response: { use: vi.fn(), eject: vi.fn() },
  },
};

vi.mock('axios', () => ({
  default: {
    create: vi.fn(() => mockAxiosInstance),
  },
}));

// Import after mocks are set up
import { refreshToken } from './tokenRefresh';

describe('tokenRefresh', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockPost.mockReset();
  });

  describe('refreshToken', () => {
    it('should refresh token successfully', async () => {
      const mockRefreshToken = 'test-refresh-token';
      const mockAccessToken = 'new-access-token';
      const mockNewRefreshToken = 'new-refresh-token';

      // Mock TokenStorage.getRefreshToken
      mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);

      // Mock axios post response
      mockPost.mockResolvedValue({
        data: {
          access_token: mockAccessToken,
          refresh_token: mockNewRefreshToken,
          expires_in: 900,
        },
      });

      await refreshToken();

      // Verify refresh token was retrieved
      expect(mockTokenStorage.getRefreshToken).toHaveBeenCalledTimes(1);

      // Verify API call was made with correct parameters
      expect(mockPost).toHaveBeenCalledWith('/auth/refresh', {
        refresh_token: mockRefreshToken,
      });

      // Verify tokens were updated
      expect(mockTokenStorage.setTokens).toHaveBeenCalledWith(
        mockAccessToken,
        mockNewRefreshToken,
      );

      // Verify clearTokens was not called on success
      expect(mockTokenStorage.clearTokens).not.toHaveBeenCalled();
    });

    it('should throw error if no refresh token available', async () => {
      // Mock TokenStorage.getRefreshToken to return null
      mockTokenStorage.getRefreshToken.mockReturnValue(null);

      await expect(refreshToken()).rejects.toThrow(
        'No refresh token available',
      );

      // Verify API was not called
      expect(mockPost).not.toHaveBeenCalled();

      // Verify tokens were not updated
      expect(mockTokenStorage.setTokens).not.toHaveBeenCalled();
    });

    it('should clear tokens and throw error on API failure', async () => {
      const mockRefreshToken = 'test-refresh-token';
      const apiError = new Error('API Error');

      // Mock TokenStorage.getRefreshToken
      mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);

      mockPost.mockRejectedValue(apiError);

      await expect(refreshToken()).rejects.toThrow('API Error');

      // Verify API call was made
      expect(mockPost).toHaveBeenCalledWith('/auth/refresh', {
        refresh_token: mockRefreshToken,
      });

      // Verify tokens were cleared on error
      expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);

      // Verify tokens were not updated
      expect(mockTokenStorage.setTokens).not.toHaveBeenCalled();
    });

    it('should clear tokens on 401 Unauthorized error', async () => {
      const mockRefreshToken = 'test-refresh-token';
      const axiosError = {
        response: {
          status: 401,
          data: { error: 'Invalid refresh token' },
        },
        isAxiosError: true,
      };

      // Mock TokenStorage.getRefreshToken
      mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);

      mockPost.mockRejectedValue(axiosError);

      await expect(refreshToken()).rejects.toEqual(axiosError);

      // Verify tokens were cleared on 401 error
      expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
    });

    it('should clear tokens on 400 Bad Request error', async () => {
      const mockRefreshToken = 'test-refresh-token';
      const axiosError = {
        response: {
          status: 400,
          data: { error: 'Invalid request' },
        },
        isAxiosError: true,
      };

      // Mock TokenStorage.getRefreshToken
      mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);

      mockPost.mockRejectedValue(axiosError);

      await expect(refreshToken()).rejects.toEqual(axiosError);

      // Verify tokens were cleared on error
      expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
    });

    it('should clear tokens on network error', async () => {
      const mockRefreshToken = 'test-refresh-token';
      const networkError = new Error('Network Error');

      // Mock TokenStorage.getRefreshToken
      mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);

      mockPost.mockRejectedValue(networkError);

      await expect(refreshToken()).rejects.toThrow('Network Error');

      // Verify tokens were cleared on error
      expect(mockTokenStorage.clearTokens).toHaveBeenCalledTimes(1);
    });

    it('should update tokens with new values from API response', async () => {
      const mockRefreshToken = 'old-refresh-token';
      const newAccessToken = 'updated-access-token';
      const newRefreshToken = 'updated-refresh-token';

      // Mock TokenStorage.getRefreshToken
      mockTokenStorage.getRefreshToken.mockReturnValue(mockRefreshToken);

      mockPost.mockResolvedValue({
        data: {
          access_token: newAccessToken,
          refresh_token: newRefreshToken,
          expires_in: 900,
        },
      });

      await refreshToken();

      // Verify tokens were updated with new values
      expect(mockTokenStorage.setTokens).toHaveBeenCalledWith(
        newAccessToken,
        newRefreshToken,
      );
    });

    it('should handle empty refresh token string', async () => {
      // Mock TokenStorage.getRefreshToken to return empty string
      mockTokenStorage.getRefreshToken.mockReturnValue('');

      await expect(refreshToken()).rejects.toThrow(
        'No refresh token available',
      );

      // Verify API was not called
      expect(mockPost).not.toHaveBeenCalled();
    });
  });
});
