import asyncio
import json
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from streamrip.media.playlist import Playlist, PendingPlaylistTrack
from streamrip.media.album import Album, PendingTrack
from streamrip.exceptions import NonStreamableError


class TestErrorHandling:
    """Test error handling in playlist and album downloads."""

    @pytest.mark.asyncio
    async def test_playlist_handles_failed_track(self):
        """Test that a playlist download continues even if one track fails."""
        mock_config = MagicMock()
        mock_client = MagicMock()
        mock_db = MagicMock()
        
        mock_track_success = MagicMock()
        mock_track_success.resolve = AsyncMock(return_value=MagicMock())
        mock_track_success.resolve.return_value.rip = AsyncMock()
        
        mock_track_failure = MagicMock()
        mock_track_failure.resolve = AsyncMock(side_effect=json.JSONDecodeError("Expecting value", "", 0))
        
        playlist = Playlist(
            name="Test Playlist",
            config=mock_config,
            client=mock_client,
            tracks=[mock_track_success, mock_track_failure]
        )
        
        await playlist.download()
        
        mock_track_success.resolve.assert_called_once()
        mock_track_success.resolve.return_value.rip.assert_called_once()
        mock_track_failure.resolve.assert_called_once()

    @pytest.mark.asyncio
    async def test_album_handles_failed_track(self):
        """Test that an album download continues even if one track fails."""
        mock_config = MagicMock()
        mock_client = MagicMock()
        mock_db = MagicMock()
        mock_meta = MagicMock()
        
        # Create a list of mock tracks - one will succeed, one will fail
        mock_track_success = MagicMock()
        mock_track_success.resolve = AsyncMock(return_value=MagicMock())
        mock_track_success.resolve.return_value.rip = AsyncMock()
        
        # This track will raise a JSONDecodeError when resolved
        mock_track_failure = MagicMock()
        mock_track_failure.resolve = AsyncMock(side_effect=json.JSONDecodeError("Expecting value", "", 0))
        
        album = Album(
            meta=mock_meta,
            config=mock_config,
            tracks=[mock_track_success, mock_track_failure],
            folder="/test/folder",
            db=mock_db
        )
        
        await album.download()
        
        mock_track_success.resolve.assert_called_once()
        mock_track_success.resolve.return_value.rip.assert_called_once()
        mock_track_failure.resolve.assert_called_once()

    @pytest.mark.asyncio
    async def test_main_rip_handles_failed_media(self):
        """Test that the Main.rip method handles failed media items."""
        from streamrip.rip.main import Main
        
        mock_config = MagicMock()
        
        mock_config.session.downloads.requests_per_minute = 0
        mock_config.session.database.downloads_enabled = False
        mock_config.session.database.failed_downloads_enabled = False
        
        with patch('streamrip.rip.main.QobuzClient'), \
             patch('streamrip.rip.main.TidalClient'), \
             patch('streamrip.rip.main.DeezerClient'), \
             patch('streamrip.rip.main.SoundcloudClient'):
            
            main = Main(mock_config)
            
            mock_media_success = MagicMock()
            mock_media_success.rip = AsyncMock()
            
            mock_media_failure = MagicMock()
            mock_media_failure.rip = AsyncMock(side_effect=Exception("Media download failed"))
            
            main.media = [mock_media_success, mock_media_failure]
            
            await main.rip()
            
            mock_media_success.rip.assert_called_once()
            mock_media_failure.rip.assert_called_once()