import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { Audio } from 'expo-av'
import { useUserStore } from '../../shared/stores/userStore'
import type { MusicTrack } from '../../shared/services/music'
import type { MusicVibe } from '../../shared/types'

const mockTracks: MusicTrack[] = [
  { id: '1', title: 'Energy Pulse', artist: 'Neon Dreams', duration: 240, url: '', vibe: 'electronic' },
  { id: '2', title: 'Cyber Sprint', artist: 'Digital Flux', duration: 180, url: '', vibe: 'electronic' },
  { id: '3', title: 'High Voltage', artist: 'Circuit Breakers', duration: 200, url: '', vibe: 'electronic' },
]

const mockHipHopTracks: MusicTrack[] = [
  { id: '4', title: 'Street Heat', artist: 'Urban Flow', duration: 210, url: '', vibe: 'hip-hop' },
]

function getRandomTrackIndex(tracks: MusicTrack[]): number {
  if (tracks.length === 0) return -1
  return Math.floor(Math.random() * tracks.length)
}

function getNextTrackIndex(currentIndex: number, tracksLength: number): number {
  if (tracksLength <= 1) return 0
  return (currentIndex + 1) % tracksLength
}

function clampVolume(volume: number): number {
  return Math.max(0, Math.min(1, volume))
}

describe('useMusicPlayer', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    useUserStore.setState({
      settings: {
        haptics: true,
        soundEffects: true,
        voiceCoaching: true,
        musicEnabled: true,
        musicVolume: 0.5,
        reminders: false,
        reminderTime: '09:00',
      },
    })
  })

  afterEach(() => {
    vi.restoreAllMocks()
  })

  describe('audio mode configuration', () => {
    it('should configure audio with correct settings', async () => {
      const expectedConfig = {
        playsInSilentModeIOS: true,
        staysActiveInBackground: true,
        shouldDuckAndroid: true,
        interruptionModeIOS: 1,
        interruptionModeAndroid: 1,
      }

      await Audio.setAudioModeAsync(expectedConfig)

      expect(Audio.setAudioModeAsync).toHaveBeenCalledWith(expectedConfig)
    })
  })

  describe('track selection', () => {
    it('should return valid random track index', () => {
      const index = getRandomTrackIndex(mockTracks)
      expect(index).toBeGreaterThanOrEqual(0)
      expect(index).toBeLessThan(mockTracks.length)
    })

    it('should return -1 for empty track list', () => {
      const index = getRandomTrackIndex([])
      expect(index).toBe(-1)
    })

    it('should cycle to next track', () => {
      const nextIndex = getNextTrackIndex(0, 3)
      expect(nextIndex).toBe(1)
    })

    it('should wrap around to first track', () => {
      const nextIndex = getNextTrackIndex(2, 3)
      expect(nextIndex).toBe(0)
    })

    it('should return 0 for single track list', () => {
      const nextIndex = getNextTrackIndex(0, 1)
      expect(nextIndex).toBe(0)
    })
  })

  describe('volume control', () => {
    it('should clamp volume above 1 to 1', () => {
      expect(clampVolume(1.5)).toBe(1)
    })

    it('should clamp volume below 0 to 0', () => {
      expect(clampVolume(-0.5)).toBe(0)
    })

    it('should keep valid volume unchanged', () => {
      expect(clampVolume(0.7)).toBe(0.7)
    })

    it('should handle edge cases', () => {
      expect(clampVolume(0)).toBe(0)
      expect(clampVolume(1)).toBe(1)
    })
  })

  describe('music enabled state', () => {
    it('should check music enabled from store', () => {
      const musicEnabled = useUserStore.getState().settings.musicEnabled
      expect(musicEnabled).toBe(true)
    })

    it('should respect music disabled state', () => {
      useUserStore.setState({
        settings: {
          ...useUserStore.getState().settings,
          musicEnabled: false,
        },
      })

      const musicEnabled = useUserStore.getState().settings.musicEnabled
      expect(musicEnabled).toBe(false)
    })
  })

  describe('track filtering by vibe', () => {
    it('should filter tracks by vibe', () => {
      const electronicTracks = mockTracks.filter(t => t.vibe === 'electronic')
      expect(electronicTracks).toHaveLength(3)
    })

    it('should return empty array for unmatched vibe', () => {
      const rockTracks = mockTracks.filter(t => t.vibe === 'rock')
      expect(rockTracks).toHaveLength(0)
    })
  })

  describe('playback status', () => {
    it('should create sound with correct initial status', async () => {
      const mockSound = {
        playAsync: vi.fn(),
        pauseAsync: vi.fn(),
        stopAsync: vi.fn(),
        unloadAsync: vi.fn(),
        getStatusAsync: vi.fn().mockResolvedValue({ isLoaded: true, isPlaying: false }),
        setVolumeAsync: vi.fn(),
      }

      vi.mocked(Audio.Sound.createAsync).mockResolvedValueOnce({
        sound: mockSound,
        status: { isLoaded: true },
      } as any)

      const result = await Audio.Sound.createAsync({} as any, {
        shouldPlay: false,
        volume: 0.5,
        isLooping: false,
      })

      expect(result.status.isLoaded).toBe(true)
    })
  })

  describe('error handling', () => {
    it('should handle empty track list', () => {
      const tracks: MusicTrack[] = []
      expect(tracks.length).toBe(0)
    })

    it('should handle tracks without URL', () => {
      const tracksWithoutUrl = mockTracks.filter(t => !t.url)
      expect(tracksWithoutUrl).toHaveLength(3)
    })
  })

  describe('vibe type', () => {
    it('should accept valid vibe types', () => {
      const vibes: MusicVibe[] = ['electronic', 'hip-hop', 'pop', 'rock', 'chill']
      expect(vibes).toHaveLength(5)
    })
  })

  describe('sound cleanup', () => {
    it('should unload sound on cleanup', async () => {
      const mockUnload = vi.fn()
      const mockSound = {
        playAsync: vi.fn(),
        pauseAsync: vi.fn(),
        stopAsync: vi.fn(),
        unloadAsync: mockUnload,
        getStatusAsync: vi.fn().mockResolvedValue({ isLoaded: true }),
        setVolumeAsync: vi.fn(),
      }

      vi.mocked(Audio.Sound.createAsync).mockResolvedValueOnce({
        sound: mockSound,
        status: { isLoaded: true },
      } as any)

      const { sound } = await Audio.Sound.createAsync({} as any)
      await sound.unloadAsync()

      expect(mockUnload).toHaveBeenCalled()
    })
  })
})
