"""Integration tests for episode download sync with data file updates.

Tests verify that when episodes are downloaded successfully:
- In-memory Serie.episodeDict is updated
- Deprecated data file is updated (if it exists)
- Missing episode list reflects the change immediately
"""
import asyncio
import json
import os
import tempfile
from pathlib import Path
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.models.download import DownloadItem, DownloadPriority, DownloadStatus
from src.server.services.download_service import DownloadService


class TestEpisodeRemovedFromMissingListAfterDownload:
    """Verify episode no longer appears in missing list after download completes."""

    @pytest.fixture
    def temp_dir(self):
        """Create temp directory for test data files."""
        with tempfile.TemporaryDirectory() as tmp:
            yield Path(tmp)

    @pytest.fixture
    def mock_anime_service(self, temp_dir):
        """Create mock anime service with app."""
        anime_service = MagicMock()
        anime_service._directory = str(temp_dir)
        
        # Create mock app withSerie with missing episodes
        serie = Serie(
            key="test-series",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1, 2, 3]},
        )
        mock_app = MagicMock()
        mock_app.list.keyDict = {"test-series": serie}
        mock_app.list.GetMissingEpisode.return_value = [serie]
        mock_app.series_list = [serie]
        anime_service._app = mock_app
        anime_service._cached_list_missing = MagicMock()
        anime_service._broadcast_series_updated = AsyncMock()
        
        return anime_service

    @pytest.fixture
    def mock_download_service(self, mock_anime_service):
        """Create download service with mocked dependencies."""
        with tempfile.TemporaryDirectory() as tmp:
            service = DownloadService(
                anime_service=mock_anime_service,
                queue_repository=MagicMock(),
                max_retries=3,
            )
            service._directory = tmp
            yield service

    @pytest.mark.asyncio
    async def test_episode_removed_from_missing_list_after_download(
        self, mock_download_service, mock_anime_service
    ):
        """Verify episode no longer appears in missing list after download completes."""
        serie = mock_anime_service._app.list.keyDict["test-series"]
        
        # Verify episode starts in missing list
        assert 2 in serie.episodeDict[1], "Episode should start in missing list"
        
        # Simulate download completion by calling _remove_episode_from_memory
        mock_download_service._remove_episode_from_memory("test-series", 1, 2)
        
        # Episode should be removed from episodeDict
        assert 2 not in serie.episodeDict[1], "Episode should be removed from missing list"
        assert serie.episodeDict[1] == [1, 3]
        
        # series_list should be refreshed
        mock_anime_service._app.list.GetMissingEpisode.assert_called()


class TestDownloadUpdatesInMemoryCache:
    """Verify in-memory Serie.episodeDict is updated after download."""

    @pytest.fixture
    def mock_anime_service(self):
        """Create mock anime service with app."""
        anime_service = MagicMock()
        anime_service._directory = "/tmp/test"
        
        # Create mock app with series having multiple seasons and episodes
        serie = Serie(
            key="multi-season-series",
            name="Multi Season Series",
            site="https://example.com",
            folder="Multi Season Series",
            episodeDict={
                1: [1, 2, 3, 4, 5],
                2: [1, 2, 3],
            },
        )
        mock_app = MagicMock()
        mock_app.list.keyDict = {"multi-season-series": serie}
        mock_app.list.GetMissingEpisode.return_value = [serie]
        mock_app.series_list = [serie]
        anime_service._app = mock_app
        anime_service._cached_list_missing = MagicMock()
        anime_service._broadcast_series_updated = AsyncMock()
        
        return anime_service

    @pytest.fixture
    def mock_download_service(self, mock_anime_service):
        """Create download service with mocked dependencies."""
        with tempfile.TemporaryDirectory() as tmp:
            service = DownloadService(
                anime_service=mock_anime_service,
                queue_repository=MagicMock(),
                max_retries=3,
            )
            service._directory = tmp
            yield service

    @pytest.mark.asyncio
    async def test_download_updates_in_memory_cache(
        self, mock_download_service, mock_anime_service
    ):
        """Verify in-memory Serie.episodeDict is updated after download."""
        # First reset to known state (remove the defaults first call might have set)
        serie = mock_anime_service._app.list.keyDict["multi-season-series"]
        
        # Put back episodes after the fixture setup
        serie.episodeDict = {1: [1, 2, 3, 4, 5], 2: [1, 2, 3]}
        
        # Verify preconditions
        assert 1 in serie.episodeDict[1]
        assert 3 in serie.episodeDict[2]
        
        # Simulate downloading multiple episodes
        mock_download_service._remove_episode_from_memory("multi-season-series", 1, 1)
        mock_download_service._remove_episode_from_memory("multi-season-series", 1, 3)
        mock_download_service._remove_episode_from_memory("multi-season-series", 2, 2)
        
        # Verify episodes removed
        assert 1 not in serie.episodeDict[1], "Episode 1 of season 1 should be removed"
        assert 3 not in serie.episodeDict[1], "Episode 3 of season 1 should be removed"
        assert 2 in serie.episodeDict[1], "Episode 2 of season 1 should remain"
        assert 3 in serie.episodeDict[2], "Episode 3 of season 2 should remain"
        assert 2 not in serie.episodeDict[2], "Episode 2 of season 2 should be removed"
        
        # Verify seasons with no episodes are cleaned up
        assert 2 in serie.episodeDict, "Season 2 should still exist (has episode 1, 3)"

    @pytest.mark.asyncio
    async def test_last_episode_removes_season(
        self, mock_download_service, mock_anime_service
    ):
        """Verify that removing last episode in a season removes the season key."""
        # Modify the series so season 1 only has episode 2 left
        serie = mock_anime_service._app.list.keyDict["multi-season-series"]
        # Reset and set to proper test state
        serie.episodeDict = {1: [2], 2: [1, 2, 3]}  # Season 1 only has episode 2
        
        # Verify initial state
        assert 2 in serie.episodeDict[1]
        assert 2 in serie.episodeDict[2]
        
        # Remove last episode of season 1 (episode 2)
        mock_download_service._remove_episode_from_memory("multi-season-series", 1, 2)
        
        # Season 1 should be completely removed
        assert 1 not in serie.episodeDict, "Season 1 should be removed"
        # Season 2 should still exist
        assert 2 in serie.episodeDict, "Season 2 should still exist"


class TestDataFileUpdatedAfterDownload:
    """Verify data file is updated after download (when it exists)."""

    @pytest.fixture
    def temp_dir(self):
        """Create temp directory for test data files."""
        with tempfile.TemporaryDirectory() as tmp:
            yield Path(tmp)

    @pytest.fixture
    def mock_anime_service(self, temp_dir):
        """Create mock anime service with app."""
        anime_service = MagicMock()
        anime_service._directory = str(temp_dir)
        
        # Create series folder with data file
        series_folder = temp_dir / "Test Series"
        series_folder.mkdir()
        data_path = series_folder / "data"
        
        serie = Serie(
            key="test-series-with-data",
            name="Test Series",
            site="https://example.com",
            folder="Test Series",
            episodeDict={1: [1, 2, 3]},
        )
        
        # Save data file to disk
        import warnings
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", DeprecationWarning)
            serie.save_to_file(str(data_path))
        
        # Update episodeDict to simulate in-progress download state
        # (episodeDict still has all episodes; will be updated after download)
        mock_app = MagicMock()
        mock_app.list.keyDict = {"test-series-with-data": serie}
        mock_app.list.GetMissingEpisode.return_value = [serie]
        mock_app.series_list = [serie]
        anime_service._app = mock_app
        anime_service._cached_list_missing = MagicMock()
        anime_service._broadcast_series_updated = AsyncMock()
        
        return anime_service

    @pytest.fixture
    def mock_download_service(self, mock_anime_service):
        """Create download service with mocked dependencies."""
        service = DownloadService(
            anime_service=mock_anime_service,
            queue_repository=MagicMock(),
            max_retries=3,
        )
        service._directory = str(mock_anime_service._directory)
        yield service

    @pytest.mark.asyncio
    async def test_data_file_updated_after_download(
        self, mock_download_service, mock_anime_service, temp_dir
    ):
        """Verify data file is updated after download when data file exists."""
        serie = mock_anime_service._app.list.keyDict["test-series-with-data"]
        data_path = temp_dir / "Test Series" / "data"
        
        # Verify data file exists before test
        assert data_path.exists(), "Data file should exist before test"
        
        # Read original data file
        with open(data_path) as f:
            original_data = json.load(f)
        assert 2 in original_data["episodeDict"]["1"], "Episode should be in original data"
        
        # Simulate download completion
        mock_download_service._remove_episode_from_memory("test-series-with-data", 1, 2)
        
        # Read updated data file
        with open(data_path) as f:
            updated_data = json.load(f)
        
        # Verify episode 2 was removed from data file
        assert 2 not in updated_data["episodeDict"]["1"], "Episode should be removed from data file"
        assert updated_data["episodeDict"]["1"] == [1, 3]


class TestDataFileNotRequiredForDownload:
    """Verify downloads work even when data file doesn't exist."""

    @pytest.fixture
    def temp_dir(self):
        """Create temp directory without data files."""
        with tempfile.TemporaryDirectory() as tmp:
            yield Path(tmp)

    @pytest.fixture
    def mock_anime_service(self, temp_dir):
        """Create mock anime service with app but no data file."""
        anime_service = MagicMock()
        anime_service._directory = str(temp_dir)
        
        # Create series with NO data file on disk (only in memory)
        serie = Serie(
            key="memory-only-series",
            name="Memory Only Series",
            site="https://example.com",
            folder="Memory Only Series",
            episodeDict={1: [1, 2, 3]},
        )
        
        mock_app = MagicMock()
        mock_app.list.keyDict = {"memory-only-series": serie}
        mock_app.list.GetMissingEpisode.return_value = [serie]
        mock_app.series_list = [serie]
        anime_service._app = mock_app
        anime_service._cached_list_missing = MagicMock()
        anime_service._broadcast_series_updated = AsyncMock()
        
        return anime_service

    @pytest.fixture
    def mock_download_service(self, mock_anime_service):
        """Create download service with mocked dependencies."""
        service = DownloadService(
            anime_service=mock_anime_service,
            queue_repository=MagicMock(),
            max_retries=3,
        )
        service._directory = str(mock_anime_service._directory)
        yield service

    @pytest.mark.asyncio
    async def test_download_works_without_data_file(
        self, mock_download_service, mock_anime_service
    ):
        """Verify downloads work even when no data file exists on disk."""
        serie = mock_anime_service._app.list.keyDict["memory-only-series"]
        data_path = Path(mock_anime_service._directory) / "Memory Only Series" / "data"
        
        # Verify no data file exists
        assert not data_path.exists(), "No data file should exist"
        
        # Simulate download completion
        # This should NOT raise even without data file
        mock_download_service._remove_episode_from_memory("memory-only-series", 1, 2)
        
        # Episode should be removed from in-memory state
        assert 2 not in serie.episodeDict[1], "Episode should be removed from memory"
        
        # Data file should still not exist (no file created)
        assert not data_path.exists(), "No data file should be created"
