import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
  getTrackStats,
  TrackStatsError,
  TrackStats,
} from './trackStatsService';
import { apiClient } from '@/services/api/client';
import { AxiosError } from 'axios';

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

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

  describe('getTrackStats', () => {
    it('should fetch track stats successfully', async () => {
      const mockStats: TrackStats = {
        views: 100,
        likes: 25,
        comments: 10,
        total_play_time: 3600,
        downloads: 5,
      };

      vi.mocked(apiClient.get).mockResolvedValue({
        data: { stats: mockStats },
      } as any);

      const result = await getTrackStats(123);

      expect(result).toEqual(mockStats);
      expect(apiClient.get).toHaveBeenCalledWith('/tracks/123/stats');
    });

    it('should handle 404 error (track not found)', async () => {
      const error = new AxiosError('Not Found');
      error.response = {
        status: 404,
        data: { error: 'track not found' },
      } as any;

      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getTrackStats(999)).rejects.toThrow(TrackStatsError);
      await expect(getTrackStats(999)).rejects.toThrow('Track introuvable');
    });

    it('should handle 400 error (invalid track id)', async () => {
      const error = new AxiosError('Bad Request');
      error.response = {
        status: 400,
        data: { error: 'invalid track id' },
      } as any;

      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getTrackStats(0)).rejects.toThrow(TrackStatsError);
      await expect(getTrackStats(0)).rejects.toThrow('Requête invalide');
    });

    it('should handle 500 error (server error)', async () => {
      const error = new AxiosError('Internal Server Error');
      error.response = {
        status: 500,
        data: { error: 'internal server error' },
      } as any;

      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getTrackStats(123)).rejects.toThrow(TrackStatsError);
      await expect(getTrackStats(123)).rejects.toThrow('Erreur serveur');
    });

    it('should handle network errors', async () => {
      const error = new AxiosError('Network Error');
      error.code = 'ECONNABORTED';

      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getTrackStats(123)).rejects.toThrow(TrackStatsError);
      await expect(getTrackStats(123)).rejects.toThrow('Erreur réseau');
    });

    it('should handle timeout errors', async () => {
      const error = new AxiosError('Timeout');
      error.code = 'ETIMEDOUT';

      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getTrackStats(123)).rejects.toThrow(TrackStatsError);
      await expect(getTrackStats(123)).rejects.toThrow('Erreur réseau');
    });

    it('should handle unknown errors', async () => {
      const error = new Error('Unknown error');

      vi.mocked(apiClient.get).mockRejectedValue(error);

      await expect(getTrackStats(123)).rejects.toThrow(TrackStatsError);
      await expect(getTrackStats(123)).rejects.toThrow('Erreur inconnue');
    });

    it('should handle empty stats', async () => {
      const mockStats: TrackStats = {
        views: 0,
        likes: 0,
        comments: 0,
        total_play_time: 0,
        downloads: 0,
      };

      vi.mocked(apiClient.get).mockResolvedValue({
        data: { stats: mockStats },
      } as any);

      const result = await getTrackStats(123);

      expect(result).toEqual(mockStats);
      expect(result.views).toBe(0);
      expect(result.likes).toBe(0);
      expect(result.comments).toBe(0);
      expect(result.total_play_time).toBe(0);
      expect(result.downloads).toBe(0);
    });

    it('should preserve error retryable flag for server errors', async () => {
      const error = new AxiosError('Internal Server Error');
      error.response = {
        status: 500,
        data: { error: 'internal server error' },
      } as any;

      vi.mocked(apiClient.get).mockRejectedValue(error);

      try {
        await getTrackStats(123);
        expect.fail('Should have thrown an error');
      } catch (err) {
        expect(err).toBeInstanceOf(TrackStatsError);
        if (err instanceof TrackStatsError) {
          expect(err.retryable).toBe(true);
        }
      }
    });

    it('should preserve error retryable flag for network errors', async () => {
      const error = new AxiosError('Network Error');
      error.code = 'ECONNABORTED';

      vi.mocked(apiClient.get).mockRejectedValue(error);

      try {
        await getTrackStats(123);
        expect.fail('Should have thrown an error');
      } catch (err) {
        expect(err).toBeInstanceOf(TrackStatsError);
        if (err instanceof TrackStatsError) {
          expect(err.retryable).toBe(true);
        }
      }
    });

    it('should not retry on 404 errors', async () => {
      const error = new AxiosError('Not Found');
      error.response = {
        status: 404,
        data: { error: 'track not found' },
      } as any;

      vi.mocked(apiClient.get).mockRejectedValue(error);

      try {
        await getTrackStats(999);
        expect.fail('Should have thrown an error');
      } catch (err) {
        expect(err).toBeInstanceOf(TrackStatsError);
        if (err instanceof TrackStatsError) {
          expect(err.retryable).toBe(false);
        }
      }
    });
  });
});
