import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { useTrackList } from './useTrackList';
import { getTracks } from '../services/trackListService';
import type { Track } from '../../player/types';

// Mock getTracks service
vi.mock('../services/trackListService', () => ({
  getTracks: vi.fn(),
}));

// Mock useSearchParams from react-router-dom
const mockSetSearchParams = vi.fn();
const mockSearchParams = new URLSearchParams();

vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual('react-router-dom');
  return {
    ...actual,
    useSearchParams: () => [mockSearchParams, mockSetSearchParams],
  };
});

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

describe('useTrackList', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    localStorage.clear();
    mockSearchParams.forEach((_, key) => {
      mockSearchParams.delete(key);
    });
  });

  afterEach(() => {
    localStorage.clear();
  });

  describe('Client-side mode (useService = false)', () => {
    it('should initialize with default values', () => {
      const { result } = renderHook(() =>
        useTrackList({ useService: false, autoLoad: false }),
      );

      expect(result.current.tracks).toEqual([]);
      expect(result.current.displayMode).toBe('list');
      expect(result.current.sortOptions.field).toBe('title');
      expect(result.current.sortOptions.order).toBe('asc');
      expect(result.current.filterOptions).toEqual({});
      expect(result.current.isLoading).toBe(false);
      expect(result.current.error).toBeNull();
      expect(result.current.pagination).toEqual({ page: 1, limit: 20 });
    });

    it('should initialize with provided values', () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          initialTracks: mockTracks,
          initialDisplayMode: 'grid',
          initialSortOptions: { field: 'duration', order: 'desc' },
          initialFilterOptions: { genre: 'Rock' },
          initialPagination: { page: 1, limit: 10 },
        }),
      );

      expect(result.current.tracks.length).toBeGreaterThan(0);
      expect(result.current.displayMode).toBe('grid');
      expect(result.current.sortOptions.field).toBe('duration');
      expect(result.current.sortOptions.order).toBe('desc');
      expect(result.current.filterOptions.genre).toBe('Rock');
    });

    it('should filter tracks by genre', () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          initialTracks: mockTracks,
        }),
      );

      act(() => {
        result.current.setFilterOptions({ genre: 'Rock' });
      });

      expect(result.current.filteredTracks).toHaveLength(2);
      expect(
        result.current.filteredTracks.every((t) => t.genre === 'Rock'),
      ).toBe(true);
    });

    it('should sort tracks by title', () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          initialTracks: mockTracks,
        }),
      );

      act(() => {
        result.current.setSortField('title');
        result.current.setSortOrder('asc');
      });

      expect(result.current.filteredTracks[0].title).toBe('Track A');
    });

    it('should paginate tracks', () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          initialTracks: mockTracks,
          initialPagination: { page: 1, limit: 2 },
        }),
      );

      expect(result.current.filteredTracks).toHaveLength(2);
      expect(result.current.total).toBe(3);
      expect(result.current.totalPages).toBe(2);
    });
  });

  describe('Service mode (useService = true)', () => {
    it('should load tracks from service on mount', async () => {
      const mockResponse = {
        data: mockTracks,
        total: mockTracks.length,
        page: 1,
        limit: 20,
        totalPages: 1,
      };

      vi.mocked(getTracks).mockResolvedValue(mockResponse);

      const { result } = renderHook(() =>
        useTrackList({ useService: true, autoLoad: true }),
      );

      expect(result.current.isLoading).toBe(true);

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      expect(getTracks).toHaveBeenCalled();
      expect(result.current.tracks).toEqual(mockTracks);
      expect(result.current.total).toBe(mockTracks.length);
    });

    it('should not load tracks if autoLoad is false', () => {
      renderHook(() => useTrackList({ useService: true, autoLoad: false }));

      expect(getTracks).not.toHaveBeenCalled();
    });

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

      const mockResponse2 = {
        data: [mockTracks[1]],
        total: mockTracks.length,
        page: 2,
        limit: 1,
        totalPages: 3,
      };

      vi.mocked(getTracks)
        .mockResolvedValueOnce(mockResponse1)
        .mockResolvedValueOnce(mockResponse2);

      const { result } = renderHook(() =>
        useTrackList({
          useService: true,
          autoLoad: true,
          initialPagination: { page: 1, limit: 1 },
        }),
      );

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      act(() => {
        result.current.setPage(2);
      });

      await waitFor(() => {
        expect(getTracks).toHaveBeenCalledTimes(2);
      });
    });

    it('should reload tracks when filters change', async () => {
      const mockResponse = {
        data: [mockTracks[0], mockTracks[2]],
        total: 2,
        page: 1,
        limit: 20,
        totalPages: 1,
      };

      vi.mocked(getTracks).mockResolvedValue(mockResponse);

      const { result } = renderHook(() =>
        useTrackList({ useService: true, autoLoad: true }),
      );

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      act(() => {
        result.current.setFilterOptions({ genre: 'Rock' });
      });

      await waitFor(() => {
        expect(getTracks).toHaveBeenCalledWith(
          expect.objectContaining({
            filters: { genre: 'Rock' },
          }),
        );
      });
    });

    it('should reload tracks when sort changes', async () => {
      const mockResponse = {
        data: mockTracks,
        total: mockTracks.length,
        page: 1,
        limit: 20,
        totalPages: 1,
      };

      vi.mocked(getTracks).mockResolvedValue(mockResponse);

      const { result } = renderHook(() =>
        useTrackList({ useService: true, autoLoad: true }),
      );

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      act(() => {
        result.current.setSortField('duration');
      });

      await waitFor(() => {
        expect(getTracks).toHaveBeenCalledWith(
          expect.objectContaining({
            sort: { field: 'duration', order: 'asc' },
          }),
        );
      });
    });

    it('should handle loading state', async () => {
      let resolvePromise: (value: any) => void;
      const promise = new Promise((resolve) => {
        resolvePromise = resolve;
      });

      vi.mocked(getTracks).mockReturnValue(promise as any);

      const { result } = renderHook(() =>
        useTrackList({ useService: true, autoLoad: true }),
      );

      expect(result.current.isLoading).toBe(true);

      act(() => {
        resolvePromise!({
          data: mockTracks,
          total: mockTracks.length,
          page: 1,
          limit: 20,
          totalPages: 1,
        });
      });

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });
    });

    it('should handle errors', async () => {
      const error = new Error('Failed to load tracks');
      vi.mocked(getTracks).mockRejectedValue(error);

      const { result } = renderHook(() =>
        useTrackList({ useService: true, autoLoad: true }),
      );

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      expect(result.current.error).toEqual(error);
      expect(result.current.tracks).toEqual([]);
    });

    it('should manually load tracks', async () => {
      const mockResponse = {
        data: mockTracks,
        total: mockTracks.length,
        page: 1,
        limit: 20,
        totalPages: 1,
      };

      vi.mocked(getTracks).mockResolvedValue(mockResponse);

      const { result } = renderHook(() =>
        useTrackList({ useService: true, autoLoad: false }),
      );

      await act(async () => {
        await result.current.loadTracks();
      });

      expect(getTracks).toHaveBeenCalled();
      expect(result.current.tracks).toEqual(mockTracks);
    });

    it('should refresh tracks', async () => {
      const mockResponse = {
        data: mockTracks,
        total: mockTracks.length,
        page: 1,
        limit: 20,
        totalPages: 1,
      };

      vi.mocked(getTracks).mockResolvedValue(mockResponse);

      const { result } = renderHook(() =>
        useTrackList({ useService: true, autoLoad: true }),
      );

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      const callCount = vi.mocked(getTracks).mock.calls.length;

      await act(async () => {
        await result.current.refreshTracks();
      });

      expect(getTracks).toHaveBeenCalledTimes(callCount + 1);
    });

    it('should reset to page 1 when filters change', async () => {
      const mockResponse = {
        data: mockTracks,
        total: mockTracks.length,
        page: 1,
        limit: 20,
        totalPages: 1,
      };

      vi.mocked(getTracks).mockResolvedValue(mockResponse);

      const { result } = renderHook(() =>
        useTrackList({
          useService: true,
          autoLoad: true,
          initialPagination: { page: 2, limit: 20 },
        }),
      );

      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      act(() => {
        result.current.setFilterOptions({ genre: 'Rock' });
      });

      await waitFor(() => {
        expect(result.current.pagination.page).toBe(1);
      });
    });
  });

  describe('CRUD operations', () => {
    it('should add track', () => {
      const { result } = renderHook(() =>
        useTrackList({ useService: false, autoLoad: false }),
      );

      act(() => {
        result.current.addTrack(mockTracks[0]);
      });

      expect(result.current.tracks).toContain(mockTracks[0]);
    });

    it('should remove track', () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          initialTracks: mockTracks,
        }),
      );

      act(() => {
        result.current.removeTrack(2);
      });

      expect(result.current.tracks).toHaveLength(2);
      expect(result.current.tracks.find((t) => t.id === 2)).toBeUndefined();
    });

    it('should update track', () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          initialTracks: mockTracks,
        }),
      );

      const updatedTrack = { ...mockTracks[0], title: 'Updated Title' };

      act(() => {
        result.current.updateTrack(updatedTrack);
      });

      const updated = result.current.tracks.find(
        (t) => t.id === updatedTrack.id,
      );
      expect(updated?.title).toBe('Updated Title');
    });
  });

  describe('Pagination controls', () => {
    it('should set page', () => {
      const { result } = renderHook(() =>
        useTrackList({ useService: false, autoLoad: false }),
      );

      act(() => {
        result.current.setPage(3);
      });

      expect(result.current.pagination.page).toBe(3);
    });

    it('should set limit', () => {
      const { result } = renderHook(() =>
        useTrackList({ useService: false, autoLoad: false }),
      );

      act(() => {
        result.current.setLimit(50);
      });

      expect(result.current.pagination.limit).toBe(50);
      expect(result.current.pagination.page).toBe(1); // Should reset to page 1
    });

    it('should set pagination', () => {
      const { result } = renderHook(() =>
        useTrackList({ useService: false, autoLoad: false }),
      );

      act(() => {
        result.current.setPagination({ page: 2, limit: 10 });
      });

      expect(result.current.pagination).toEqual({ page: 2, limit: 10 });
    });
  });

  describe('Search', () => {
    it('should set search query', () => {
      const { result } = renderHook(() =>
        useTrackList({ useService: false, autoLoad: false }),
      );

      act(() => {
        result.current.setSearchQuery('test query');
      });

      // In client mode, search is handled in filteredTracks
      expect(result.current.filteredTracks).toBeDefined();
    });

    it('should filter tracks by search query in client mode', () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          initialTracks: mockTracks,
        }),
      );

      act(() => {
        result.current.setSearchQuery('Track A');
      });

      expect(result.current.filteredTracks).toHaveLength(1);
      expect(result.current.filteredTracks[0].title).toBe('Track A');
    });
  });

  describe('Persistence', () => {
    it('should persist filters to localStorage when persistFilters is enabled', async () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          persistFilters: true,
        }),
      );

      act(() => {
        result.current.setFilterOptions({ genre: 'Rock', artist: 'Artist 1' });
      });

      await waitFor(
        () => {
          const stored = localStorage.getItem('trackList_filters');
          expect(stored).toBeTruthy();
          if (stored) {
            const parsed = JSON.parse(stored);
            expect(parsed.genre).toBe('Rock');
            expect(parsed.artist).toBe('Artist 1');
          }
        },
        { timeout: 2000 },
      );
    });

    it('should persist sort options to localStorage when persistSort is enabled', async () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          persistSort: true,
        }),
      );

      act(() => {
        result.current.setSortField('artist');
        result.current.setSortOrder('desc');
      });

      await waitFor(
        () => {
          const stored = localStorage.getItem('trackList_sort');
          expect(stored).toBeTruthy();
          if (stored) {
            const parsed = JSON.parse(stored);
            expect(parsed.field).toBe('artist');
            expect(parsed.order).toBe('desc');
          }
        },
        { timeout: 2000 },
      );
    });

    it('should restore filters from localStorage on mount when persistFilters is enabled', () => {
      localStorage.setItem(
        'trackList_filters',
        JSON.stringify({ genre: 'Pop', artist: 'Artist 2' }),
      );

      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          persistFilters: true,
        }),
      );

      expect(result.current.filterOptions.genre).toBe('Pop');
      expect(result.current.filterOptions.artist).toBe('Artist 2');
    });

    it('should restore sort options from localStorage on mount when persistSort is enabled', () => {
      localStorage.setItem(
        'trackList_sort',
        JSON.stringify({ field: 'duration', order: 'desc' }),
      );

      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          persistSort: true,
        }),
      );

      expect(result.current.sortOptions.field).toBe('duration');
      expect(result.current.sortOptions.order).toBe('desc');
    });

    it('should use custom storage key prefix', async () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          persistFilters: true,
          storageKeyPrefix: 'customPrefix',
        }),
      );

      act(() => {
        result.current.setFilterOptions({ genre: 'Jazz' });
      });

      await waitFor(
        () => {
          const stored = localStorage.getItem('customPrefix_filters');
          expect(stored).toBeTruthy();
          if (stored) {
            const parsed = JSON.parse(stored);
            expect(parsed.genre).toBe('Jazz');
          }
        },
        { timeout: 2000 },
      );
    });

    it('should sync filters with URL params when syncUrlParams is enabled', async () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          syncUrlParams: true,
        }),
      );

      act(() => {
        result.current.setFilterOptions({ genre: 'Rock', artist: 'Artist 1' });
      });

      await waitFor(
        () => {
          expect(mockSetSearchParams).toHaveBeenCalled();
        },
        { timeout: 2000 },
      );
    });

    it('should sync sort options with URL params when syncUrlParams is enabled', async () => {
      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          syncUrlParams: true,
        }),
      );

      act(() => {
        result.current.setSortField('artist');
        result.current.setSortOrder('desc');
      });

      await waitFor(
        () => {
          expect(mockSetSearchParams).toHaveBeenCalled();
        },
        { timeout: 2000 },
      );
    });

    it('should load filters from URL params when syncUrlParams is enabled', () => {
      mockSearchParams.set('genre', 'Pop');
      mockSearchParams.set('artist', 'Artist 2');
      mockSearchParams.set('year', '2020');

      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          syncUrlParams: true,
        }),
      );

      expect(result.current.filterOptions.genre).toBe('Pop');
      expect(result.current.filterOptions.artist).toBe('Artist 2');
      expect(result.current.filterOptions.year).toBe(2020);
    });

    it('should load sort options from URL params when syncUrlParams is enabled', () => {
      mockSearchParams.set('sortField', 'duration');
      mockSearchParams.set('sortOrder', 'desc');

      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          syncUrlParams: true,
        }),
      );

      expect(result.current.sortOptions.field).toBe('duration');
      expect(result.current.sortOptions.order).toBe('desc');
    });

    it('should prioritize URL params over localStorage when both are enabled', () => {
      localStorage.setItem(
        'trackList_filters',
        JSON.stringify({ genre: 'Rock' }),
      );
      mockSearchParams.set('genre', 'Pop');

      const { result } = renderHook(() =>
        useTrackList({
          useService: false,
          autoLoad: false,
          persistFilters: true,
          syncUrlParams: true,
        }),
      );

      // URL params should take priority
      expect(result.current.filterOptions.genre).toBe('Pop');
    });
  });
});
