import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AxiosError } from 'axios';
import { uploadAvatar, deleteAvatar, AvatarUploadError } from './avatarService';
import { apiClient } from '@/services/api/client';

// Mock apiClient
vi.mock('@/services/api/client', () => ({
  apiClient: {
    post: vi.fn(),
    delete: vi.fn(),
  },
}));

const mockedApiClient = apiClient as {
  post: ReturnType<typeof vi.fn>;
  delete: ReturnType<typeof vi.fn>;
};

describe('avatarService', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('uploadAvatar', () => {
    it('should upload avatar successfully', async () => {
      const mockFile = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' });
      const mockResponse = {
        avatar_url: 'https://example.com/avatar.jpg',
      };

      mockedApiClient.post.mockResolvedValue({
        data: mockResponse,
      });

      const result = await uploadAvatar('user-1', mockFile);

      expect(result).toEqual(mockResponse);
      expect(mockedApiClient.post).toHaveBeenCalledWith(
        '/users/user-1/avatar',
        expect.any(FormData),
        expect.objectContaining({
          headers: {
            'Content-Type': 'multipart/form-data',
          },
        }),
      );
    });

    it('should call onProgress callback during upload', async () => {
      const mockFile = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' });
      const mockResponse = {
        avatar_url: 'https://example.com/avatar.jpg',
      };
      const onProgress = vi.fn();

      mockedApiClient.post.mockImplementation((url, data, config) => {
        // Simulate progress
        if (config?.onUploadProgress) {
          config.onUploadProgress({
            loaded: 50,
            total: 100,
          } as any);
        }
        return Promise.resolve({ data: mockResponse });
      });

      await uploadAvatar('user-1', mockFile, onProgress);

      expect(onProgress).toHaveBeenCalledWith(50);
    });

    it('should throw AvatarUploadError with VALIDATION code on 400 error', async () => {
      const mockFile = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' });
      const mockError = new AxiosError('Validation failed');
      mockError.response = {
        status: 400,
        data: { error: 'Invalid file format' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(uploadAvatar('user-1', mockFile)).rejects.toThrow(
        AvatarUploadError,
      );
      await expect(uploadAvatar('user-1', mockFile)).rejects.toMatchObject({
        code: 'VALIDATION',
      });
    });

    it('should throw AvatarUploadError with VALIDATION code on 413 error', async () => {
      const mockFile = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' });
      const mockError = new AxiosError('File too large');
      mockError.response = {
        status: 413,
        data: {},
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(uploadAvatar('user-1', mockFile)).rejects.toThrow(
        AvatarUploadError,
      );
      await expect(uploadAvatar('user-1', mockFile)).rejects.toMatchObject({
        code: 'VALIDATION',
        message: 'Fichier trop volumineux (max 5MB)',
      });
    });

    it('should throw AvatarUploadError with SERVER code on 500+ error', async () => {
      const mockFile = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' });
      const mockError = new AxiosError('Server error');
      mockError.response = {
        status: 500,
        data: { error: 'Internal server error' },
      } as any;

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(uploadAvatar('user-1', mockFile)).rejects.toThrow(
        AvatarUploadError,
      );
      await expect(uploadAvatar('user-1', mockFile)).rejects.toMatchObject({
        code: 'SERVER',
      });
    });

    it('should throw AvatarUploadError with NETWORK code on network error', async () => {
      const mockFile = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' });
      const mockError = new AxiosError('Network error');
      mockError.request = {};

      mockedApiClient.post.mockRejectedValue(mockError);

      await expect(uploadAvatar('user-1', mockFile)).rejects.toThrow(
        AvatarUploadError,
      );
      await expect(uploadAvatar('user-1', mockFile)).rejects.toMatchObject({
        code: 'NETWORK',
      });
    });

    it('should throw AvatarUploadError with UNKNOWN code on unknown error', async () => {
      const mockFile = new File(['test'], 'avatar.jpg', { type: 'image/jpeg' });

      mockedApiClient.post.mockRejectedValue(new Error('Unknown error'));

      await expect(uploadAvatar('user-1', mockFile)).rejects.toThrow(
        AvatarUploadError,
      );
      await expect(uploadAvatar('user-1', mockFile)).rejects.toMatchObject({
        code: 'UNKNOWN',
      });
    });
  });

  describe('deleteAvatar', () => {
    it('should delete avatar successfully', async () => {
      mockedApiClient.delete.mockResolvedValue({ data: {} });

      await deleteAvatar('user-1');

      expect(mockedApiClient.delete).toHaveBeenCalledWith(
        '/users/user-1/avatar',
      );
    });

    it('should throw error on delete failure', async () => {
      const mockError = new AxiosError('Delete failed');
      mockError.response = {
        status: 404,
        data: { error: 'Avatar not found' },
      } as any;

      mockedApiClient.delete.mockRejectedValue(mockError);

      await expect(deleteAvatar('user-1')).rejects.toThrow();
    });
  });
});
