import { describe, it, expect, vi, beforeEach } from 'vitest';
import axios from 'axios';
import { apiClient } from '@/services/api/client';
import {
  getTracks,
  getTrackById,
  getTracksByArtist,
  getTracksByAlbum,
  getTracksByGenre,
  searchTracks,
} from './trackListService';
import type { Track } from '../../player/types';

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

const mockTracks: Track[] = [
  {
    id: 1,
    title: 'Track 1',
    artist: 'Artist 1',
    album: 'Album 1',
    duration: 180,
    url: 'https://example.com/track1.mp3',
    genre: 'Rock',
  },
  {
    id: 2,
    title: 'Track 2',
    artist: 'Artist 2',
    album: 'Album 2',
    duration: 240,
    url: 'https://example.com/track2.mp3',
    genre: 'Pop',
  },
  {
    id: 3,
    title: 'Track 3',
    artist: 'Artist 1',
    album: 'Album 1',
    duration: 200,
    url: 'https://example.com/track3.mp3',
    genre: 'Rock',
  },
];

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

  describe('getTracks', () => {
    it('should fetch tracks without options', async () => {
      const mockResponse = {
        data: {
          tracks: mockTracks,
          total: mockTracks.length,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks();

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?');
      expect(result.data).toEqual(mockTracks);
      expect(result.total).toBe(mockTracks.length);
    });

    it('should fetch tracks with pagination', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: mockTracks.length,
          page: 1,
          limit: 1,
          totalPages: 3,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks({
        pagination: { page: 1, limit: 1 },
      });

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?page=1&limit=1');
      expect(result.data).toEqual([mockTracks[0]]);
      expect(result.page).toBe(1);
      expect(result.limit).toBe(1);
      expect(result.totalPages).toBe(3);
    });

    it('should fetch tracks with filters', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0], mockTracks[2]],
          total: 2,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks({
        filters: { genre: 'Rock' },
      });

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?genre=Rock');
      expect(result.data).toEqual([mockTracks[0], mockTracks[2]]);
    });

    it('should fetch tracks with multiple filters', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: 1,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks({
        filters: {
          genre: 'Rock',
          artist: 'Artist 1',
          minDuration: 150,
          maxDuration: 200,
        },
      });

      expect(apiClient.get).toHaveBeenCalledWith(
        '/tracks?genre=Rock&artist=Artist+1&min_duration=150&max_duration=200',
      );
      expect(result.data).toEqual([mockTracks[0]]);
    });

    it('should fetch tracks with sort options', async () => {
      const mockResponse = {
        data: {
          tracks: mockTracks,
          total: mockTracks.length,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks({
        sort: { field: 'title', order: 'asc' },
      });

      expect(apiClient.get).toHaveBeenCalledWith(
        '/tracks?sort_by=title&sort_order=asc',
      );
      expect(result.data).toEqual(mockTracks);
    });

    it('should fetch tracks with search query', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: 1,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks({
        search: 'Track 1',
      });

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?search=Track+1');
      expect(result.data).toEqual([mockTracks[0]]);
    });

    it('should fetch tracks with all options', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: 1,
          page: 1,
          limit: 10,
          totalPages: 1,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks({
        pagination: { page: 1, limit: 10 },
        filters: { genre: 'Rock' },
        sort: { field: 'title', order: 'asc' },
        search: 'Track',
      });

      expect(apiClient.get).toHaveBeenCalledWith(
        '/tracks?page=1&limit=10&genre=Rock&sort_by=title&sort_order=asc&search=Track',
      );
      expect(result.data).toEqual([mockTracks[0]]);
    });

    it('should handle API errors', async () => {
      const mockError = {
        response: {
          data: {
            message: 'Error fetching tracks',
          },
        },
        isAxiosError: true,
      };

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

      await expect(getTracks()).rejects.toThrow('Error fetching tracks');
    });

    it('should calculate totalPages when not provided', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: 10,
          page: 1,
          limit: 3,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracks({
        pagination: { page: 1, limit: 3 },
      });

      expect(result.totalPages).toBe(4); // Math.ceil(10 / 3) = 4
    });
  });

  describe('getTrackById', () => {
    it('should fetch a track by ID', async () => {
      const mockResponse = {
        data: mockTracks[0],
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTrackById(1);

      expect(apiClient.get).toHaveBeenCalledWith('/tracks/1');
      expect(result).toEqual(mockTracks[0]);
    });

    it('should handle API errors', async () => {
      const mockError = {
        response: {
          data: {
            message: 'Track not found',
          },
        },
        isAxiosError: true,
      };

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

      await expect(getTrackById(999)).rejects.toThrow('Track not found');
    });
  });

  describe('getTracksByArtist', () => {
    it('should fetch tracks by artist', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0], mockTracks[2]],
          total: 2,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracksByArtist('Artist 1');

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?artist=Artist+1');
      expect(result.data).toEqual([mockTracks[0], mockTracks[2]]);
    });

    it('should fetch tracks by artist with pagination', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: 2,
          page: 1,
          limit: 1,
          totalPages: 2,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracksByArtist('Artist 1', {
        pagination: { page: 1, limit: 1 },
      });

      expect(apiClient.get).toHaveBeenCalledWith(
        '/tracks?page=1&limit=1&artist=Artist+1',
      );
      expect(result.data).toEqual([mockTracks[0]]);
    });
  });

  describe('getTracksByAlbum', () => {
    it('should fetch tracks by album', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0], mockTracks[2]],
          total: 2,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracksByAlbum('Album 1');

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?album=Album+1');
      expect(result.data).toEqual([mockTracks[0], mockTracks[2]]);
    });
  });

  describe('getTracksByGenre', () => {
    it('should fetch tracks by genre', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0], mockTracks[2]],
          total: 2,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await getTracksByGenre('Rock');

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?genre=Rock');
      expect(result.data).toEqual([mockTracks[0], mockTracks[2]]);
    });
  });

  describe('searchTracks', () => {
    it('should search tracks', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: 1,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await searchTracks('Track 1');

      expect(apiClient.get).toHaveBeenCalledWith('/tracks?search=Track+1');
      expect(result.data).toEqual([mockTracks[0]]);
    });

    it('should search tracks with filters', async () => {
      const mockResponse = {
        data: {
          tracks: [mockTracks[0]],
          total: 1,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue(mockResponse);

      const result = await searchTracks('Track', {
        filters: { genre: 'Rock' },
      });

      expect(apiClient.get).toHaveBeenCalledWith(
        '/tracks?genre=Rock&search=Track',
      );
      expect(result.data).toEqual([mockTracks[0]]);
    });
  });
});
