"""
Unit tests for Anime-Sama fallback mechanism
Tests player priority, caching, and URL validation
"""
import pytest
from unittest.mock import Mock, AsyncMock, patch, MagicMock
from httpx import TimeoutException, ConnectError

from app.downloaders.anime_sites.animesama import AnimeSamaDownloader


class TestAnimeSamaFallback:
    """Tests for Anime-Sama fallback mechanism"""

    @pytest.fixture
    def downloader(self):
        """Create AnimeSamaDownloader instance"""
        return AnimeSamaDownloader()

    @pytest.mark.asyncio
    async def test_fallback_tries_players_in_priority_order(self, downloader):
        """
        Test that fallback tries players in priority order:
        VidMoly -> SendVid -> Sibnet -> Lpayer
        """
        # Mock each player extraction method
        with patch.object(downloader, '_extract_from_vidmoly') as mock_vidmoly, \
             patch.object(downloader, '_extract_from_sendvid') as mock_sendvid, \
             patch.object(downloader, '_extract_from_sibnet') as mock_sibnet, \
             patch.object(downloader, '_extract_from_lpayer') as mock_lpayer, \
             patch.object(downloader, '_test_video_url', new_callable=AsyncMock) as mock_test_url:
            
            # Make vidmoly and sendvid fail, sibnet succeed
            mock_vidmoly.side_effect = Exception("VidMoly failed")
            mock_sendvid.side_effect = Exception("SendVid failed")
            mock_sibnet.return_value = ("http://sibnet.com/video.mp4", "video.mp4")
            mock_lpayer.return_value = ("http://lpayer.com/video.mp4", "video.mp4")
            
            # Make validation pass for sibnet
            mock_test_url.return_value = True
            
            result = await downloader.get_download_link_with_fallback(
                "http://vidmoly.to/test",
                anime_page_url="https://anime-sama.si/catalogue/test/vostfr/"
            )
            
            # Verify player order was correct
            assert mock_vidmoly.called, "VidMoly should be tried first"
            assert mock_sendvid.called, "SendVid should be tried second"
            assert mock_sibnet.called, "Sibnet should be tried third"
            assert not mock_lpayer.called, "Lpayer should not be called since Sibnet succeeded"
            
            assert result == ("http://sibnet.com/video.mp4", "video.mp4")

    @pytest.mark.asyncio
    async def test_caching_mechanism_stores_working_player(self, downloader):
        """
        Test that caching mechanism stores working player for same anime URL.
        After first successful player, subsequent requests should use cached player first.
        """
        # Setup: First request - vidmoly fails, sendvid succeeds
        with patch.object(downloader, '_extract_from_vidmoly') as mock_vidmoly, \
             patch.object(downloader, '_extract_from_sendvid') as mock_sendvid, \
             patch.object(downloader, '_extract_from_sibnet') as mock_sibnet, \
             patch.object(downloader, '_extract_from_lpayer') as mock_lpayer, \
             patch.object(downloader, '_test_video_url', new_callable=AsyncMock) as mock_test_url:
            
            # First request: vidmoly fails, sendvid succeeds
            mock_vidmoly.side_effect = Exception("VidMoly failed")
            mock_sendvid.return_value = ("http://sendvid.com/video.mp4", "video.mp4")
            mock_test_url.return_value = True
            
            anime_url = "https://anime-sama.si/catalogue/test/vostfr/"
            
            result1 = await downloader.get_download_link_with_fallback(
                "http://vidmoly.to/test",
                anime_page_url=anime_url
            )
            
            # Verify caching worked
            assert anime_url in downloader._working_players
            assert downloader._working_players[anime_url] == "sendvid"
            
            # Reset mocks for second request
            mock_vidmoly.reset_mock()
            mock_sendvid.reset_mock()
            mock_sibnet.reset_mock()
            mock_lpayer.reset_mock()
            
            # Second request: Should try sendvid first (cached)
            mock_sendvid.return_value = ("http://sendvid.com/video2.mp4", "video2.mp4")
            mock_test_url.return_value = True
            
            result2 = await downloader.get_download_link_with_fallback(
                "http://vidmoly.to/test",
                anime_page_url=anime_url
            )
            
            # Verify sendvid was tried first (due to cache)
            assert mock_sendvid.call_count == 1, "Cached player (sendvid) should be tried first"


    @pytest.mark.asyncio
    async def test_all_players_failing_raises_exception(self, downloader):
        """
        Test that when all players fail, an exception is raised with proper error message.
        """
        with patch.object(downloader, '_extract_from_vidmoly') as mock_vidmoly, \
             patch.object(downloader, '_extract_from_sendvid') as mock_sendvid, \
             patch.object(downloader, '_extract_from_sibnet') as mock_sibnet, \
             patch.object(downloader, '_extract_from_lpayer_api') as mock_lpayer_api, \
             patch.object(downloader, '_extract_from_smoothpre') as mock_smoothpre:
            
            # All players fail
            mock_vidmoly.side_effect = Exception("VidMoly error")
            mock_sendvid.side_effect = Exception("SendVid error")
            mock_sibnet.side_effect = Exception("Sibnet error")
            mock_lpayer_api.side_effect = Exception("Lpayer error")
            mock_smoothpre.side_effect = Exception("Smoothpre error")
            
            anime_url = "https://anime-sama.si/catalogue/test/vostfr/"
            
            with pytest.raises(Exception) as exc_info:
                await downloader.get_download_link_with_fallback(
                    "http://vidmoly.to/test",
                    anime_page_url=anime_url
                )
            
            # Verify error message mentions all players failed
            assert "All players failed" in str(exc_info.value)
            
            # Verify all players were tried
            assert mock_vidmoly.called
            assert mock_sendvid.called
            assert mock_sibnet.called
            assert mock_lpayer_api.called

    @pytest.mark.asyncio
    async def test_test_video_url_returns_true_for_valid_url(self, downloader):
        """
        Test that _test_video_url returns True for valid video URL (HTTP 200 with content).
        """
        # Mock the client to return valid response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.content = b"video content data"
        
        with patch.object(downloader.client, 'get', new_callable=AsyncMock) as mock_get:
            mock_get.return_value = mock_response
            
            result = await downloader._test_video_url("http://example.com/video.mp4")
            
            assert result is True
            mock_get.assert_called_once()
            # Verify Range header was included
            call_args = mock_get.call_args
            assert "Range" in call_args.kwargs.get("headers", {})

    @pytest.mark.asyncio
    async def test_test_video_url_returns_false_for_invalid_url(self, downloader):
        """
        Test that _test_video_url returns False for invalid/non-working URL.
        """
        # Test case 1: HTTP error status
        mock_response = Mock()
        mock_response.status_code = 404
        
        with patch.object(downloader.client, 'get', new_callable=AsyncMock) as mock_get:
            mock_get.return_value = mock_response
            
            result = await downloader._test_video_url("http://example.com/notfound.mp4")
            
            assert result is False

    @pytest.mark.asyncio
    async def test_test_video_url_returns_false_for_empty_response(self, downloader):
        """
        Test that _test_video_url returns False for empty response content.
        """
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.content = b""  # Empty content
        
        with patch.object(downloader.client, 'get', new_callable=AsyncMock) as mock_get:
            mock_get.return_value = mock_response
            
            result = await downloader._test_video_url("http://example.com/empty.mp4")
            
            assert result is False

    @pytest.mark.asyncio
    async def test_test_video_url_returns_false_for_timeout(self, downloader):
        """
        Test that _test_video_url returns False for timeout.
        """
        with patch.object(downloader.client, 'get', new_callable=AsyncMock) as mock_get:
            mock_get.side_effect = TimeoutException("Request timeout")
            
            result = await downloader._test_video_url("http://example.com/slow.mp4")
            
            assert result is False

    @pytest.mark.asyncio
    async def test_test_video_url_returns_false_for_connection_error(self, downloader):
        """
        Test that _test_video_url returns False for connection error.
        """
        with patch.object(downloader.client, 'get', new_callable=AsyncMock) as mock_get:
            mock_get.side_effect = ConnectError("Connection failed")
            
            result = await downloader._test_video_url("http://example.com/badhost.mp4")
            
            assert result is False

    @pytest.mark.asyncio
    async def test_fallback_skips_invalid_player_url(self, downloader):
        """
        Test that fallback skips players that return invalid URLs (validation fails).
        """
        with patch.object(downloader, '_extract_from_vidmoly') as mock_vidmoly, \
             patch.object(downloader, '_extract_from_sendvid') as mock_sendvid, \
             patch.object(downloader, '_extract_from_sibnet') as mock_sibnet, \
             patch.object(downloader, '_test_video_url', new_callable=AsyncMock) as mock_test_url:
            
            # Vidmoly returns URL but validation fails
            mock_vidmoly.return_value = ("http://vidmoly.com/video.mp4", "video.mp4")
            
            # SendVid returns URL and validation passes
            mock_sendvid.return_value = ("http://sendvid.com/video.mp4", "video.mp4")
            mock_sibnet.return_value = ("http://sibnet.com/video.mp4", "video.mp4")
            
            # First call (vidmoly): validation fails
            # Second call (sendvid): validation passes
            # Third call (sibnet): not called because sendvid succeeded
            mock_test_url.side_effect = [False, True]
            
            result = await downloader.get_download_link_with_fallback(
                "http://vidmoly.to/test",
                anime_page_url="https://anime-sama.si/catalogue/test/vostfr/"
            )
            
            # Verify validation was called for vidmoly
            assert mock_test_url.call_count >= 1
            # Verify sendvid was also tried after vidmoly failed validation
            assert mock_sendvid.called

    @pytest.mark.asyncio
    async def test_cache_not_used_without_anime_page_url(self, downloader):
        """
        Test that caching is not used when anime_page_url is not provided.
        """
        with patch.object(downloader, '_extract_from_vidmoly') as mock_vidmoly, \
             patch.object(downloader, '_extract_from_sendvid') as mock_sendvid, \
             patch.object(downloader, '_test_video_url', new_callable=AsyncMock) as mock_test_url:
            
            # First request: no anime_page_url, vidmoly succeeds
            mock_vidmoly.return_value = ("http://vidmoly.com/video.mp4", "video.mp4")
            mock_test_url.return_value = True
            
            result1 = await downloader.get_download_link_with_fallback(
                "http://vidmoly.to/test"
            )
            
            # Cache should be empty (no anime_page_url provided)
            assert len(downloader._working_players) == 0
            
            # Second request: still no anime_page_url, should not use cache
            mock_vidmoly.reset_mock()
            
            result2 = await downloader.get_download_link_with_fallback(
                "http://vidmoly.to/test"
            )
            
            # Vidmoly should still be called (no cache used)
            assert mock_vidmoly.call_count == 1
