import { describe, it, expect, vi, beforeEach } from 'vitest'
import { Audio } from 'expo-av'
import { useUserStore } from '../../shared/stores/userStore'

vi.mock('expo-av', () => ({
  Audio: {
    Sound: {
      createAsync: vi.fn().mockResolvedValue({
        sound: {
          playAsync: vi.fn(),
          pauseAsync: vi.fn(),
          stopAsync: vi.fn(),
          unloadAsync: vi.fn(),
          setPositionAsync: vi.fn(),
          setVolumeAsync: vi.fn(),
        },
        status: { isLoaded: true },
      }),
    },
    setAudioModeAsync: vi.fn(),
  },
}))

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

  describe('audio mode configuration', () => {
    it('should configure audio with correct settings', async () => {
      const expectedConfig = {
        playsInSilentModeIOS: true,
        staysActiveInBackground: false,
        shouldDuckAndroid: true,
      }
      
      await Audio.setAudioModeAsync(expectedConfig)
      
      expect(Audio.setAudioModeAsync).toHaveBeenCalledWith(expectedConfig)
    })
  })

  describe('sound creation', () => {
    it('should create sound with createAsync', async () => {
      const mockSound = {
        playAsync: vi.fn(),
        setPositionAsync: vi.fn(),
        unloadAsync: vi.fn(),
      }
      
      vi.mocked(Audio.Sound.createAsync).mockResolvedValueOnce({
        sound: mockSound,
        status: { isLoaded: true },
      } as any)
      
      const result = await Audio.Sound.createAsync({} as any)
      
      expect(result.sound).toBeDefined()
      expect(result.status.isLoaded).toBe(true)
    })

    it('should handle sound creation failure gracefully', async () => {
      vi.mocked(Audio.Sound.createAsync).mockRejectedValueOnce(new Error('Failed to load'))
      
      await expect(Audio.Sound.createAsync({} as any)).rejects.toThrow('Failed to load')
    })
  })

  describe('sound playback', () => {
    const createSoundCallbacks = (soundEnabled: boolean) => {
      const play = async (soundKey: string) => {
        if (!soundEnabled) return
        
        try {
          const { sound } = await Audio.Sound.createAsync({} as any)
          await sound.setPositionAsync(0)
          await sound.playAsync()
        } catch (error) {
          // Handle error silently
        }
      }
      
      return {
        countdownBeep: () => play('countdown'),
        phaseStart: () => play('phaseStart'),
        workoutComplete: () => play('complete'),
      }
    }

    describe('when sound enabled', () => {
      it('should play countdown beep', async () => {
        const mockSound = {
          playAsync: vi.fn(),
          setPositionAsync: vi.fn(),
          unloadAsync: vi.fn(),
        }
        vi.mocked(Audio.Sound.createAsync).mockResolvedValueOnce({
          sound: mockSound,
          status: { isLoaded: true },
        } as any)
        
        const callbacks = createSoundCallbacks(true)
        await callbacks.countdownBeep()
        
        expect(Audio.Sound.createAsync).toHaveBeenCalled()
        expect(mockSound.setPositionAsync).toHaveBeenCalledWith(0)
        expect(mockSound.playAsync).toHaveBeenCalled()
      })

      it('should play phase start sound', async () => {
        const mockSound = {
          playAsync: vi.fn(),
          setPositionAsync: vi.fn(),
          unloadAsync: vi.fn(),
        }
        vi.mocked(Audio.Sound.createAsync).mockResolvedValueOnce({
          sound: mockSound,
          status: { isLoaded: true },
        } as any)
        
        const callbacks = createSoundCallbacks(true)
        await callbacks.phaseStart()
        
        expect(Audio.Sound.createAsync).toHaveBeenCalled()
        expect(mockSound.playAsync).toHaveBeenCalled()
      })

      it('should play workout complete sound', async () => {
        const mockSound = {
          playAsync: vi.fn(),
          setPositionAsync: vi.fn(),
          unloadAsync: vi.fn(),
        }
        vi.mocked(Audio.Sound.createAsync).mockResolvedValueOnce({
          sound: mockSound,
          status: { isLoaded: true },
        } as any)
        
        const callbacks = createSoundCallbacks(true)
        await callbacks.workoutComplete()
        
        expect(Audio.Sound.createAsync).toHaveBeenCalled()
        expect(mockSound.playAsync).toHaveBeenCalled()
      })
    })

    describe('when sound disabled', () => {
      it('should not play countdown beep', async () => {
        const callbacks = createSoundCallbacks(false)
        await callbacks.countdownBeep()
        
        expect(Audio.Sound.createAsync).not.toHaveBeenCalled()
      })

      it('should not play phase start sound', async () => {
        const callbacks = createSoundCallbacks(false)
        await callbacks.phaseStart()
        
        expect(Audio.Sound.createAsync).not.toHaveBeenCalled()
      })

      it('should not play workout complete sound', async () => {
        const callbacks = createSoundCallbacks(false)
        await callbacks.workoutComplete()
        
        expect(Audio.Sound.createAsync).not.toHaveBeenCalled()
      })
    })
  })

  describe('sound cleanup', () => {
    it('should unload sound on cleanup', async () => {
      const mockUnload = vi.fn()
      const mockSound = {
        playAsync: vi.fn(),
        setPositionAsync: vi.fn(),
        unloadAsync: mockUnload,
      }
      
      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()
    })
  })

  describe('error handling', () => {
    it('should handle playback errors gracefully', async () => {
      vi.mocked(Audio.Sound.createAsync).mockRejectedValueOnce(new Error('Playback failed'))
      
      const play = async () => {
        try {
          await Audio.Sound.createAsync({} as any)
        } catch {
          // Silently handle
        }
      }
      
      await expect(play()).resolves.not.toThrow()
    })
  })
})
