import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { usePlayer } from './usePlayer';
import { usePlayerStore } from '../store/playerStore';
import { audioPlayerService } from '../services/playerService';
import type { Track } from '../types';

// Mock dependencies
vi.mock('../store/playerStore', () => ({
  usePlayerStore: vi.fn(),
}));

vi.mock('../services/playerService', () => ({
  audioPlayerService: {
    initialize: vi.fn(),
    cleanup: vi.fn(),
    loadTrack: vi.fn(),
    play: vi.fn(),
    pause: vi.fn(),
    stop: vi.fn(),
    seek: vi.fn(),
    setVolume: vi.fn(),
    setMuted: vi.fn(),
    onTimeUpdate: vi.fn(),
    onDurationChange: vi.fn(),
    onEnded: vi.fn(),
    onError: vi.fn(),
    onPlay: vi.fn(),
    onPause: vi.fn(),
  },
}));

describe('usePlayer', () => {
  const mockTrack: Track = {
    id: 1,
    title: 'Test Track',
    duration: 180,
    url: 'https://example.com/track.mp3',
  };

  const mockStore = {
    currentTrack: null,
    isPlaying: false,
    currentTime: 0,
    duration: 0,
    volume: 100,
    muted: false,
    queue: [],
    currentIndex: -1,
    repeat: 'off' as const,
    shuffle: false,
    play: vi.fn(),
    pause: vi.fn(),
    resume: vi.fn(),
    stop: vi.fn(),
    next: vi.fn(),
    previous: vi.fn(),
    seek: vi.fn(),
    setCurrentTime: vi.fn(),
    setDuration: vi.fn(),
    setVolume: vi.fn(),
    toggleMute: vi.fn(),
    toggleShuffle: vi.fn(),
    setRepeat: vi.fn(),
    addToQueue: vi.fn(),
    clearQueue: vi.fn(),
  };

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(usePlayerStore).mockReturnValue(mockStore);
    vi.mocked(audioPlayerService.play).mockResolvedValue(undefined);
    vi.mocked(audioPlayerService.loadTrack).mockResolvedValue(undefined);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it('should return player state', () => {
    const { result } = renderHook(() => usePlayer());

    expect(result.current.currentTrack).toBeNull();
    expect(result.current.isPlaying).toBe(false);
    expect(result.current.currentTime).toBe(0);
    expect(result.current.duration).toBe(0);
    expect(result.current.volume).toBe(100);
    expect(result.current.muted).toBe(false);
    expect(result.current.queue).toEqual([]);
    expect(result.current.currentIndex).toBe(-1);
    expect(result.current.repeat).toBe('off');
    expect(result.current.shuffle).toBe(false);
  });

  it('should return player controls', () => {
    const { result } = renderHook(() => usePlayer());

    expect(typeof result.current.play).toBe('function');
    expect(typeof result.current.pause).toBe('function');
    expect(typeof result.current.resume).toBe('function');
    expect(typeof result.current.stop).toBe('function');
    expect(typeof result.current.next).toBe('function');
    expect(typeof result.current.previous).toBe('function');
    expect(typeof result.current.seek).toBe('function');
    expect(typeof result.current.setVolume).toBe('function');
    expect(typeof result.current.toggleMute).toBe('function');
    expect(typeof result.current.toggleShuffle).toBe('function');
    expect(typeof result.current.setRepeat).toBe('function');
    expect(typeof result.current.addToQueue).toBe('function');
    expect(typeof result.current.clearQueue).toBe('function');
  });

  it('should initialize audio service when audio element is provided', () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    renderHook(() => usePlayer(audioRef));

    expect(audioPlayerService.initialize).toHaveBeenCalledWith(audioElement);
  });

  it('should cleanup audio service on unmount', () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    const { unmount } = renderHook(() => usePlayer(audioRef));

    unmount();

    expect(audioPlayerService.cleanup).toHaveBeenCalled();
  });

  it('should load track when currentTrack changes', async () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    vi.mocked(usePlayerStore).mockReturnValue({
      ...mockStore,
      currentTrack: mockTrack,
    });

    renderHook(() => usePlayer(audioRef));

    await waitFor(() => {
      expect(audioPlayerService.loadTrack).toHaveBeenCalledWith(mockTrack);
    });
  });

  it('should sync playback state with audio element', async () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    vi.mocked(usePlayerStore).mockReturnValue({
      ...mockStore,
      isPlaying: true,
    });

    renderHook(() => usePlayer(audioRef));

    await waitFor(() => {
      expect(audioPlayerService.play).toHaveBeenCalled();
    });
  });

  it('should sync volume and mute with audio element', () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    vi.mocked(usePlayerStore).mockReturnValue({
      ...mockStore,
      volume: 50,
      muted: false,
    });

    renderHook(() => usePlayer(audioRef));

    expect(audioPlayerService.setVolume).toHaveBeenCalledWith(0.5);
    expect(audioPlayerService.setMuted).toHaveBeenCalledWith(false);
  });

  it('should handle play with track', async () => {
    const { result } = renderHook(() => usePlayer());

    await result.current.play(mockTrack);

    expect(mockStore.play).toHaveBeenCalledWith(mockTrack);
  });

  it('should handle pause', () => {
    const { result } = renderHook(() => usePlayer());

    result.current.pause();

    expect(mockStore.pause).toHaveBeenCalled();
  });

  it('should handle resume', async () => {
    const { result } = renderHook(() => usePlayer());

    await result.current.resume();

    expect(mockStore.resume).toHaveBeenCalled();
  });

  it('should handle stop', () => {
    const { result } = renderHook(() => usePlayer());

    result.current.stop();

    expect(mockStore.stop).toHaveBeenCalled();
  });

  it('should handle seek', () => {
    const { result } = renderHook(() => usePlayer());

    result.current.seek(50);

    expect(mockStore.seek).toHaveBeenCalledWith(50);
  });

  it('should handle setVolume', () => {
    const { result } = renderHook(() => usePlayer());

    result.current.setVolume(75);

    expect(mockStore.setVolume).toHaveBeenCalledWith(75);
  });

  it('should handle toggleMute', () => {
    const { result } = renderHook(() => usePlayer());

    result.current.toggleMute();

    expect(mockStore.toggleMute).toHaveBeenCalled();
  });

  it('should setup event callbacks', () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    renderHook(() => usePlayer(audioRef));

    expect(audioPlayerService.onTimeUpdate).toHaveBeenCalled();
    expect(audioPlayerService.onDurationChange).toHaveBeenCalled();
    expect(audioPlayerService.onEnded).toHaveBeenCalled();
    expect(audioPlayerService.onError).toHaveBeenCalled();
    expect(audioPlayerService.onPlay).toHaveBeenCalled();
    expect(audioPlayerService.onPause).toHaveBeenCalled();
  });

  it('should handle track end with repeat track mode', () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    vi.mocked(usePlayerStore).mockReturnValue({
      ...mockStore,
      repeat: 'track' as const,
    });

    renderHook(() => usePlayer(audioRef));

    // Get the ended callback
    const endedCallback = vi.mocked(audioPlayerService.onEnded).mock
      .calls[0][0];
    expect(endedCallback).toBeDefined();

    // Simulate track end
    if (endedCallback) {
      endedCallback();
    }

    // Should seek to start and play again (handled by the callback)
    expect(audioPlayerService.seek).toHaveBeenCalledWith(0);
  });

  it('should handle track end with repeat off', () => {
    const audioElement = document.createElement('audio');
    const audioRef = { current: audioElement };

    vi.mocked(usePlayerStore).mockReturnValue({
      ...mockStore,
      repeat: 'off' as const,
    });

    renderHook(() => usePlayer(audioRef));

    // Get the ended callback
    const endedCallback = vi.mocked(audioPlayerService.onEnded).mock
      .calls[0][0];
    expect(endedCallback).toBeDefined();

    // Simulate track end
    if (endedCallback) {
      endedCallback();
    }

    // Should call next
    expect(mockStore.next).toHaveBeenCalled();
  });
});
