"""Unit tests for anime list loading from database.

Tests the fix for the issue where /api/anime returned empty array
because series weren't loaded from database into SeriesApp memory.
"""
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from src.core.entities.series import Serie
from src.core.SeriesApp import SeriesApp
from src.server.database.models import AnimeSeries, Episode
from src.server.services.anime_service import AnimeService


class TestAnimeListLoading:
    """Test suite for anime list loading functionality."""

    @pytest.fixture
    def mock_series_app(self):
        """Create a mock SeriesApp instance."""
        app = MagicMock(spec=SeriesApp)
        app.directory_to_search = "/test/anime"
        app.list = MagicMock()
        app.list.keyDict = {}
        app.list.GetList = MagicMock(return_value=[])
        app.load_series_from_list = MagicMock()
        return app

    @pytest.fixture
    def sample_db_series(self):
        """Create sample database series for testing."""
        series1 = AnimeSeries(
            id=1,
            key="test-anime-1",
            name="Test Anime 1",
            site="aniworld.to",
            folder="Test Anime 1 (2023)",
            year=2023,
            episodes=[]
        )
        
        series2 = AnimeSeries(
            id=2,
            key="test-anime-2",
            name="Test Anime 2",
            site="aniworld.to",
            folder="Test Anime 2 (2024)",
            year=2024,
            episodes=[
                Episode(
                    id=1,
                    series_id=2,
                    season=1,
                    episode_number=1
                ),
                Episode(
                    id=2,
                    series_id=2,
                    season=1,
                    episode_number=2
                )
            ]
        )
        
        return [series1, series2]

    @pytest.mark.asyncio
    async def test_load_series_from_db_populates_series_app(
        self, mock_series_app, sample_db_series
    ):
        """Test that _load_series_from_db loads series into SeriesApp memory.
        
        This is the core fix for the empty anime list issue:
        - Database has series data
        - _load_series_from_db() should load them into SeriesApp.list
        - series_app.list.GetList() should then return the series
        """
        # Create AnimeService with mock SeriesApp
        anime_service = AnimeService(mock_series_app)
        
        # Mock database session and service
        with patch('src.server.database.connection.get_db_session') as mock_get_session, \
             patch('src.server.database.service.AnimeSeriesService') as mock_db_service:
            
            # Setup mock database to return sample series
            mock_db = AsyncMock()
            mock_get_session.return_value.__aenter__.return_value = mock_db
            mock_db_service.get_all = AsyncMock(return_value=sample_db_series)
            
            # Call the method that loads series from DB
            await anime_service._load_series_from_db()
            
            # Verify load_series_from_list was called
            mock_series_app.load_series_from_list.assert_called_once()
            
            # Verify it was called with Serie objects
            called_series = mock_series_app.load_series_from_list.call_args[0][0]
            assert len(called_series) == 2
            
            # Verify Serie objects have correct attributes
            assert all(isinstance(s, Serie) for s in called_series)
            assert called_series[0].key == "test-anime-1"
            assert called_series[0].name == "Test Anime 1"
            assert called_series[0].folder == "Test Anime 1 (2023)"
            assert called_series[0].episodeDict == {}
            
            assert called_series[1].key == "test-anime-2"
            assert called_series[1].name == "Test Anime 2"
            assert called_series[1].episodeDict == {1: [1, 2]}

    @pytest.mark.asyncio
    async def test_series_app_list_empty_before_loading(self, tmpdir):
        """Test that SeriesApp.list is empty when initialized with skip_load=True.
        
        This demonstrates the original issue:
        - SeriesApp is initialized with skip_load=True
        - GetList() returns empty list until series are loaded from DB
        """
        # Create a real SeriesApp with skip_load=True
        anime_dir = str(tmpdir.mkdir("anime"))
        series_app = SeriesApp(anime_dir)
        
        # Verify list is empty (this was the bug)
        assert len(series_app.list.GetList()) == 0
        assert len(series_app.list.keyDict) == 0

    def test_load_series_from_list_populates_keydict(self, tmpdir):
        """Test that load_series_from_list correctly populates the keyDict.
        
        This is the method that fixes the empty list issue by loading
        series from database into SeriesApp memory.
        """
        # Create a real SeriesApp
        anime_dir = str(tmpdir.mkdir("anime"))
        series_app = SeriesApp(anime_dir)
        
        # Verify list starts empty
        assert len(series_app.list.GetList()) == 0
        
        # Create test series
        test_series = [
            Serie(
                key="test-1",
                name="Test Series 1",
                site="aniworld.to",
                folder="Test Series 1 (2023)",
                episodeDict={1: [1, 2, 3]}
            ),
            Serie(
                key="test-2",
                name="Test Series 2",
                site="aniworld.to",
                folder="Test Series 2 (2024)",
                episodeDict={}
            )
        ]
        
        # Load series into SeriesApp (this is what the fix does)
        series_app.load_series_from_list(test_series)
        
        # Verify list is now populated
        loaded_series = series_app.list.GetList()
        assert len(loaded_series) == 2
        assert loaded_series[0].key == "test-1"
        assert loaded_series[1].key == "test-2"
        
        # Verify keyDict is populated correctly
        assert "test-1" in series_app.list.keyDict
        assert "test-2" in series_app.list.keyDict
        assert series_app.list.keyDict["test-1"].name == "Test Series 1"

    @pytest.mark.asyncio
    async def test_anime_endpoint_returns_series_after_loading(
        self, tmpdir
    ):
        """Integration test: Verify /api/anime endpoint returns series after loading.
        
        This tests the complete flow:
        1. Series exist in database
        2. _load_series_from_db() loads them into memory
        3. /api/anime endpoint returns them
        """
        from unittest.mock import AsyncMock, MagicMock

        from httpx import ASGITransport, AsyncClient

        from src.server.fastapi_app import app as fastapi_app
        from src.server.utils.dependencies import (
            get_anime_service,
            get_series_app,
            require_auth,
        )

        # Create a mock AnimeService that returns the test data
        mock_anime_svc = MagicMock()
        mock_anime_svc.list_series_with_filters = AsyncMock(return_value=[
            {
                "key": "attack-on-titan",
                "name": "Attack on Titan",
                "site": "aniworld.to",
                "folder": "Attack on Titan (2013)",
                "episodeDict": {1: [1, 2]},
                "has_nfo": False,
            },
            {
                "key": "one-piece",
                "name": "One Piece",
                "site": "aniworld.to",
                "folder": "One Piece (1999)",
                "episodeDict": {},
                "has_nfo": False,
            },
        ])
        
        # Override dependencies
        fastapi_app.dependency_overrides[get_anime_service] = lambda: mock_anime_svc
        fastapi_app.dependency_overrides[require_auth] = lambda: {"user": "test"}
        
        try:
            transport = ASGITransport(app=fastapi_app)
            async with AsyncClient(
                transport=transport,
                base_url="http://test"
            ) as client:
                # Call the endpoint (no auth needed due to override)
                response = await client.get("/api/anime")
                
                # Verify response
                assert response.status_code == 200
                data = response.json()
                
                # This was the bug: empty array
                # After fix: returns series
                assert len(data) == 2
                assert data[0]["key"] == "attack-on-titan"
                assert data[0]["name"] == "Attack on Titan"
                assert data[0]["has_missing"] is True
                assert data[1]["key"] == "one-piece"
                assert data[1]["has_missing"] is False
        finally:
            # Clean up override
            fastapi_app.dependency_overrides.clear()

    @pytest.mark.asyncio
    async def test_empty_database_returns_empty_list(self, tmpdir):
        """Test that empty database correctly returns empty list.
        
        Edge case: No series in database should return empty array,
        not cause an error.
        """
        from httpx import ASGITransport, AsyncClient

        from src.server.fastapi_app import app as fastapi_app
        from src.server.utils.dependencies import get_series_app, require_auth

        # Create SeriesApp with no series
        anime_dir = str(tmpdir.mkdir("anime"))
        series_app = SeriesApp(anime_dir)
        series_app.load_series_from_list([])  # Empty list
        
        # Override dependencies
        fastapi_app.dependency_overrides[get_series_app] = lambda: series_app
        fastapi_app.dependency_overrides[require_auth] = lambda: {"user": "test"}
        
        try:
            transport = ASGITransport(app=fastapi_app)
            async with AsyncClient(
                transport=transport,
                base_url="http://test"
            ) as client:
                # Call the endpoint (no auth needed due to override)
                response = await client.get("/api/anime")
                
                # Should return 200 with empty array
                assert response.status_code == 200
                data = response.json()
                assert data == []
        finally:
            # Clean up override
            fastapi_app.dependency_overrides.clear()

    def test_series_app_skip_load_behavior(self, tmpdir):
        """Test that skip_load=True prevents automatic filesystem loading.
        
        This documents why the bug occurred:
        - SeriesApp initializes with skip_load=True
        - This prevents automatic loading from filesystem
        - Series must be explicitly loaded via load_series_from_list()
        """
        # Test with skip_load=True (default in production)
        anime_dir = str(tmpdir.mkdir("anime"))
        series_app = SeriesApp(anime_dir)
        assert len(series_app.list.GetList()) == 0, \
            "With skip_load=True, list should be empty initially"
        
        # Test that manual loading works
        test_serie = Serie(
            key="test",
            name="Test",
            site="aniworld.to",
            folder="Test (2023)",
            episodeDict={}
        )
        series_app.load_series_from_list([test_serie])
        
        assert len(series_app.list.GetList()) == 1, \
            "After load_series_from_list, series should be available"

    @pytest.mark.asyncio
    async def test_load_series_with_missing_episodes(self, sample_db_series):
        """Test that missing episodes are correctly converted from DB format.
        
        Verifies that Episode records in database are correctly converted
        to episodeDict format in Serie objects.
        """
        from src.server.services.anime_service import AnimeService

        # Create mock SeriesApp
        series_app = MagicMock(spec=SeriesApp)
        series_app.directory_to_search = "/test/anime"
        series_app.load_series_from_list = MagicMock()
        
        anime_service = AnimeService(series_app)
        
        # Mock database
        with patch('src.server.database.connection.get_db_session') as mock_get_session, \
             patch('src.server.database.service.AnimeSeriesService') as mock_db_service:
            
            mock_db = AsyncMock()
            mock_get_session.return_value.__aenter__.return_value = mock_db
            mock_db_service.get_all = AsyncMock(return_value=sample_db_series)
            
            # Load from DB
            await anime_service._load_series_from_db()
            
            # Get the loaded series
            called_series = series_app.load_series_from_list.call_args[0][0]
            
            # Verify episode dict conversion
            series_with_episodes = [s for s in called_series if s.episodeDict]
            assert len(series_with_episodes) == 1
            assert series_with_episodes[0].key == "test-anime-2"
            assert series_with_episodes[0].episodeDict == {1: [1, 2]}


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
