"""Unit tests for BackgroundLoaderService.

Tests cover:
- Task queuing and worker orchestration
- Loading status tracking and progress reporting
- Concurrent task processing
- WebSocket broadcasting
- Error handling and recovery
- Resource cleanup
- Missing data detection
"""
import asyncio
from datetime import datetime, timezone
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from src.server.services.background_loader_service import (
    BackgroundLoaderService,
    LoadingStatus,
    SeriesLoadingTask,
)


@pytest.fixture
def mock_websocket_service():
    """Mock WebSocket service."""
    service = AsyncMock()
    service.broadcast = AsyncMock()
    return service


@pytest.fixture
def mock_anime_service():
    """Mock AnimeService."""
    service = AsyncMock()
    service.sync_episodes_to_db = AsyncMock()
    service.sync_single_series_after_scan = AsyncMock()
    return service


@pytest.fixture
def mock_series_app():
    """Mock SeriesApp."""
    app = MagicMock()
    app.directory_to_search = "/anime"
    app.nfo_service = AsyncMock()
    app.nfo_service.has_nfo = MagicMock(return_value=False)
    app.nfo_service.create_tvshow_nfo = AsyncMock(return_value="/anime/TestSeries/tvshow.nfo")
    app.serie_scanner = MagicMock()
    app.serie_scanner.scan_single_series = MagicMock(return_value={
        "Season 1": ["episode1.mp4", "episode2.mp4"]
    })
    return app


@pytest.fixture
def background_loader_service(mock_websocket_service, mock_anime_service, mock_series_app):
    """Create BackgroundLoaderService instance."""
    return BackgroundLoaderService(
        websocket_service=mock_websocket_service,
        anime_service=mock_anime_service,
        series_app=mock_series_app,
        max_concurrent_loads=3
    )


class TestSeriesLoadingTask:
    """Tests for SeriesLoadingTask data class."""
    
    def test_task_initialization(self):
        """Test task is initialized with correct default values."""
        task = SeriesLoadingTask(
            key="test_series",
            folder="test_folder",
            name="Test Series",
            year=2020
        )
        
        assert task.key == "test_series"
        assert task.folder == "test_folder"
        assert task.name == "Test Series"
        assert task.year == 2020
        assert task.status == LoadingStatus.PENDING
        assert task.progress == {
            "episodes": False,
            "nfo": False,
            "logo": False,
            "images": False
        }
        assert task.started_at is None
        assert task.completed_at is None
        assert task.error is None
    
    def test_task_with_minimal_fields(self):
        """Test task creation with minimal required fields."""
        task = SeriesLoadingTask(
            key="minimal",
            folder="folder",
            name="Name"
        )
        
        assert task.key == "minimal"
        assert task.year is None
        assert task.status == LoadingStatus.PENDING
    
    def test_task_progress_tracking(self):
        """Test progress tracking updates correctly."""
        task = SeriesLoadingTask(
            key="test",
            folder="Test",
            name="Test"
        )

        task.progress["episodes"] = True
        assert task.progress["episodes"] is True
        assert not task.progress["nfo"]


class TestLoadingStatus:
    """Tests for LoadingStatus enumeration."""
    
    def test_all_status_values(self):
        """Test all loading status values."""
        assert LoadingStatus.PENDING.value == "pending"
        assert LoadingStatus.LOADING_EPISODES.value == "loading_episodes"
        assert LoadingStatus.LOADING_NFO.value == "loading_nfo"
        assert LoadingStatus.COMPLETED.value == "completed"
        assert LoadingStatus.FAILED.value == "failed"


class TestBackgroundLoaderServiceInitialization:
    """Tests for service initialization."""
    
    def test_service_initialization(self, mock_websocket_service, mock_anime_service, mock_series_app):
        """Test service initializes with correct configuration."""
        service = BackgroundLoaderService(
            websocket_service=mock_websocket_service,
            anime_service=mock_anime_service,
            series_app=mock_series_app,
            max_concurrent_loads=5
        )
        
        assert service.websocket_service is mock_websocket_service
        assert service.anime_service is mock_anime_service
        assert service.series_app is mock_series_app
        assert service.max_concurrent_loads == 5
        assert isinstance(service.task_queue, asyncio.Queue)
        assert service.active_tasks == {}
        assert service.worker_tasks == []
        assert service._shutdown is False
    
    def test_default_max_concurrent_loads(self, mock_websocket_service, mock_anime_service, mock_series_app):
        """Test default max_concurrent_loads is 5."""
        service = BackgroundLoaderService(
            websocket_service=mock_websocket_service,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )
        
        assert service.max_concurrent_loads == 5


class TestStartStopService:
    """Tests for service startup and shutdown."""
    
    @pytest.mark.asyncio
    async def test_service_start(self, background_loader_service):
        """Test service starts worker tasks."""
        await background_loader_service.start()
        
        assert len(background_loader_service.worker_tasks) == 3
        assert all(not task.done() for task in background_loader_service.worker_tasks)
        
        await background_loader_service.stop()
    
    @pytest.mark.asyncio
    async def test_service_stop(self, background_loader_service):
        """Test service stops all worker tasks gracefully."""
        await background_loader_service.start()
        assert len(background_loader_service.worker_tasks) == 3
        
        await background_loader_service.stop()
        
        assert background_loader_service.worker_tasks == []
        assert background_loader_service._shutdown is True
    
    @pytest.mark.asyncio
    async def test_service_stop_when_not_started(self, background_loader_service):
        """Test stopping a service that was never started."""
        await background_loader_service.stop()
        assert background_loader_service.worker_tasks == []


class TestAddSeriesLoadingTask:
    """Tests for adding tasks to the queue."""
    
    @pytest.mark.asyncio
    async def test_add_series_loading_task(self, background_loader_service):
        """Test adding a series loading task to the queue."""
        await background_loader_service.add_series_loading_task(
            key="test_series",
            folder="test_folder",
            name="Test Series",
            year=2020
        )
        
        assert "test_series" in background_loader_service.active_tasks
        task = background_loader_service.active_tasks["test_series"]
        
        assert task.key == "test_series"
        assert task.folder == "test_folder"
        assert task.name == "Test Series"
        assert task.year == 2020
        assert task.status == LoadingStatus.PENDING
        assert task.started_at is not None
    
    @pytest.mark.asyncio
    async def test_add_duplicate_task_skips_addition(self, background_loader_service):
        """Test adding duplicate task skips the new one."""
        await background_loader_service.add_series_loading_task(
            key="test_series",
            folder="test_folder1",
            name="Test Series 1"
        )
        
        initial_task = background_loader_service.active_tasks["test_series"]
        
        await background_loader_service.add_series_loading_task(
            key="test_series",
            folder="test_folder2",
            name="Test Series 2"
        )
        
        assert background_loader_service.active_tasks["test_series"] is initial_task
    
    @pytest.mark.asyncio
    async def test_add_task_broadcasts_status(self, background_loader_service, mock_websocket_service):
        """Test adding task broadcasts initial status."""
        await background_loader_service.add_series_loading_task(
            key="test_series",
            folder="test_folder",
            name="Test Series"
        )
        
        mock_websocket_service.broadcast.assert_called_once()
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        
        assert call_args["type"] == "series_loading_update"
        assert call_args["key"] == "test_series"
        assert call_args["status"] == LoadingStatus.PENDING.value
    
    @pytest.mark.asyncio
    async def test_add_multiple_tasks(self, background_loader_service):
        """Test adding multiple different tasks."""
        await background_loader_service.add_series_loading_task(
            key="series1",
            folder="folder1",
            name="Series 1"
        )
        await background_loader_service.add_series_loading_task(
            key="series2",
            folder="folder2",
            name="Series 2"
        )
        
        assert len(background_loader_service.active_tasks) == 2
        assert "series1" in background_loader_service.active_tasks
        assert "series2" in background_loader_service.active_tasks


class TestCheckMissingData:
    """Tests for checking missing data for a series."""
    
    @pytest.mark.asyncio
    async def test_check_missing_data_no_series_in_db(self, background_loader_service):
        """Test checking missing data when series doesn't exist in DB."""
        mock_db = AsyncMock()
        
        with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
            mock_service_class.get_by_key = AsyncMock(return_value=None)
            
            missing = await background_loader_service.check_missing_data(
                key="new_series",
                folder="new_folder",
                anime_directory="/anime",
                db=mock_db
            )
        
        assert all(missing.values())
    
    @pytest.mark.asyncio
    async def test_check_missing_data_partial_loaded(self, background_loader_service):
        """Test checking missing data for partially loaded series."""
        mock_db = AsyncMock()
        mock_series = MagicMock()
        mock_series.episodes_loaded = True
        mock_series.has_nfo = True
        mock_series.logo_loaded = False
        mock_series.images_loaded = False
        
        with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
            with patch("src.server.utils.media.check_media_files") as mock_check:
                mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
                mock_check.return_value = {
                    "poster": False,
                    "logo": False,
                    "fanart": False,
                    "nfo": True
                }
                
                missing = await background_loader_service.check_missing_data(
                    key="partial_series",
                    folder="partial_folder",
                    anime_directory="/anime",
                    db=mock_db
                )
        
        assert missing["episodes"] is False
        assert missing["nfo"] is False
        assert missing["logo"] is True
        assert missing["images"] is True


class TestBroadcastStatus:
    """Tests for WebSocket status broadcasting."""
    
    @pytest.mark.asyncio
    async def test_broadcast_status_pending(self, background_loader_service, mock_websocket_service):
        """Test broadcasting pending status."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test",
            status=LoadingStatus.PENDING
        )
        
        await background_loader_service._broadcast_status(task)
        
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        assert call_args["status"] == LoadingStatus.PENDING.value
        assert "Queued" in call_args["message"]
    
    @pytest.mark.asyncio
    async def test_broadcast_status_completed(self, background_loader_service, mock_websocket_service):
        """Test broadcasting completed status."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test",
            status=LoadingStatus.COMPLETED
        )
        
        await background_loader_service._broadcast_status(task)
        
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        assert call_args["status"] == LoadingStatus.COMPLETED.value
        assert "successfully" in call_args["message"]
    
    @pytest.mark.asyncio
    async def test_broadcast_status_failed(self, background_loader_service, mock_websocket_service):
        """Test broadcasting failed status with error message."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test",
            status=LoadingStatus.FAILED,
            error="Test error"
        )
        
        await background_loader_service._broadcast_status(task)
        
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        assert call_args["status"] == LoadingStatus.FAILED.value
        assert call_args["error"] == "Test error"
        assert "failed" in call_args["message"]
    
    @pytest.mark.asyncio
    async def test_broadcast_status_custom_message(self, background_loader_service, mock_websocket_service):
        """Test broadcasting with custom message."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test"
        )
        
        await background_loader_service._broadcast_status(task, "Custom message")
        
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        assert call_args["message"] == "Custom message"
    
    @pytest.mark.asyncio
    async def test_broadcast_status_includes_metadata(self, background_loader_service, mock_websocket_service):
        """Test broadcast includes all required metadata."""
        task = SeriesLoadingTask(
            key="test_key",
            folder="test_folder",
            name="Test Name"
        )
        task.progress = {"episodes": True, "nfo": False, "logo": False, "images": False}
        
        await background_loader_service._broadcast_status(task)
        
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        assert call_args["type"] == "series_loading_update"
        assert call_args["key"] == "test_key"
        assert call_args["series_key"] == "test_key"
        assert call_args["folder"] == "test_folder"
        assert call_args["progress"] == {"episodes": True, "nfo": False, "logo": False, "images": False}
        assert "timestamp" in call_args


class TestFindSeriesDirectory:
    """Tests for finding series directory."""
    
    @pytest.mark.asyncio
    async def test_find_series_directory_exists(self, background_loader_service, tmp_path):
        """Test finding series directory when it exists."""
        series_dir = tmp_path / "TestSeries"
        series_dir.mkdir()
        
        background_loader_service.series_app.directory_to_search = str(tmp_path)
        
        task = SeriesLoadingTask(
            key="test",
            folder="TestSeries",
            name="Test"
        )
        
        result = await background_loader_service._find_series_directory(task)
        
        assert result is not None
        assert result.name == "TestSeries"
    
    @pytest.mark.asyncio
    async def test_find_series_directory_not_found(self, background_loader_service, tmp_path):
        """Test finding series directory when it doesn't exist."""
        background_loader_service.series_app.directory_to_search = str(tmp_path)
        
        task = SeriesLoadingTask(
            key="test",
            folder="NonExistentSeries",
            name="Test"
        )
        
        result = await background_loader_service._find_series_directory(task)
        
        assert result is None


class TestScanSeriesEpisodes:
    """Tests for scanning episodes in a series directory."""
    
    @pytest.mark.asyncio
    async def test_scan_series_episodes(self, background_loader_service, tmp_path):
        """Test scanning episodes from series directory."""
        season1 = tmp_path / "Season 1"
        season1.mkdir()
        (season1 / "episode1.mp4").touch()
        (season1 / "episode2.mp4").touch()
        
        season2 = tmp_path / "Season 2"
        season2.mkdir()
        (season2 / "episode1.mp4").touch()
        
        task = SeriesLoadingTask(
            key="test",
            folder="TestSeries",
            name="Test"
        )
        
        result = await background_loader_service._scan_series_episodes(tmp_path, task)
        
        assert "Season 1" in result
        assert "Season 2" in result
        assert len(result["Season 1"]) == 2
        assert len(result["Season 2"]) == 1
    
    @pytest.mark.asyncio
    async def test_scan_series_episodes_ignores_non_mp4(self, background_loader_service, tmp_path):
        """Test that only .mp4 files are scanned."""
        season = tmp_path / "Season 1"
        season.mkdir()
        (season / "episode1.mp4").touch()
        (season / "episode2.txt").touch()
        (season / "episode3.avi").touch()
        
        task = SeriesLoadingTask(
            key="test",
            folder="TestSeries",
            name="Test"
        )
        
        result = await background_loader_service._scan_series_episodes(tmp_path, task)
        
        assert len(result["Season 1"]) == 1
        assert result["Season 1"][0] == "episode1.mp4"
    
    @pytest.mark.asyncio
    async def test_scan_series_episodes_empty_directory(self, background_loader_service, tmp_path):
        """Test scanning empty directory."""
        task = SeriesLoadingTask(
            key="test",
            folder="TestSeries",
            name="Test"
        )
        
        result = await background_loader_service._scan_series_episodes(tmp_path, task)
        
        assert result == {}


class TestLoadNfoAndImages:
    """Tests for loading NFO files and images."""
    
    @pytest.mark.asyncio
    async def test_load_nfo_creates_new_nfo(self, background_loader_service, mock_websocket_service):
        """Test creating new NFO file when it doesn't exist."""
        mock_db = AsyncMock()
        mock_series = MagicMock()
        mock_series.has_nfo = False

        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series",
            year=2020
        )

        mock_nfo_service = AsyncMock()
        mock_nfo_service.create_tvshow_nfo = AsyncMock(return_value="/anime/test_folder/tvshow.nfo")
        mock_factory = MagicMock()
        mock_factory.create = MagicMock(return_value=mock_nfo_service)

        with patch("src.server.database.service.AnimeSeriesService") as mock_service_class, \
             patch("src.server.services.background_loader_service.get_nfo_factory", return_value=mock_factory):
            mock_service_class.get_by_key = AsyncMock(return_value=mock_series)

            result = await background_loader_service._load_nfo_and_images(task, mock_db)

        assert result is True
        assert task.progress["nfo"] is True
        assert task.progress["logo"] is True
        assert task.progress["images"] is True
    
    @pytest.mark.asyncio
    async def test_load_nfo_uses_existing(self, background_loader_service):
        """Test using existing NFO file when it already exists."""
        background_loader_service.series_app.nfo_service.has_nfo = MagicMock(return_value=True)
        
        mock_db = AsyncMock()
        mock_series = MagicMock()
        mock_series.has_nfo = True
        
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
            mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
            
            result = await background_loader_service._load_nfo_and_images(task, mock_db)
        
        assert result is False
        assert task.progress["nfo"] is True
    
    @pytest.mark.asyncio
    async def test_load_nfo_without_nfo_service(self, background_loader_service):
        """Test graceful handling when NFO service not available."""
        background_loader_service.series_app.nfo_service = None
        
        mock_db = AsyncMock()
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        result = await background_loader_service._load_nfo_and_images(task, mock_db)
        
        assert result is False
        assert task.progress["nfo"] is False
        assert task.progress["logo"] is False


class TestScanMissingEpisodes:
    """Tests for scanning missing episodes."""
    
    @pytest.mark.asyncio
    async def test_scan_missing_episodes(self, background_loader_service):
        """Test scanning for missing episodes."""
        mock_db = AsyncMock()
        mock_series = MagicMock()
        
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
            mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
            
            await background_loader_service._scan_missing_episodes(task, mock_db)
        
        assert task.progress["episodes"] is True
        background_loader_service.anime_service.sync_single_series_after_scan.assert_called_once_with("test")
    
    @pytest.mark.asyncio
    async def test_scan_missing_episodes_no_scanner(self, background_loader_service):
        """Test handling when scanner not available."""
        background_loader_service.series_app.serie_scanner = None
        
        mock_db = AsyncMock()
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        await background_loader_service._scan_missing_episodes(task, mock_db)


class TestConcurrentProcessing:
    """Tests for concurrent task processing."""
    
    @pytest.mark.asyncio
    async def test_multiple_concurrent_tasks(self, background_loader_service):
        """Test processing multiple tasks concurrently."""
        await background_loader_service.add_series_loading_task("series1", "folder1", "Series 1")
        await background_loader_service.add_series_loading_task("series2", "folder2", "Series 2")
        await background_loader_service.add_series_loading_task("series3", "folder3", "Series 3")
        
        assert len(background_loader_service.active_tasks) == 3
        assert background_loader_service.task_queue.qsize() == 3
    
    @pytest.mark.asyncio
    async def test_max_concurrent_load_limit(self, mock_websocket_service, mock_anime_service, mock_series_app):
        """Test respecting maximum concurrent loads setting."""
        service = BackgroundLoaderService(
            websocket_service=mock_websocket_service,
            anime_service=mock_anime_service,
            series_app=mock_series_app,
            max_concurrent_loads=2
        )
        
        await service.start()
        
        assert len(service.worker_tasks) == 2
        
        await service.stop()


class TestLoadSeriesData:
    """Tests for complete series data loading process."""
    
    @pytest.mark.asyncio
    async def test_load_series_data_successful(self, background_loader_service):
        """Test successful series data loading."""
        mock_db = AsyncMock()
        mock_series = MagicMock()
        
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        with patch("src.server.database.connection.get_db_session"):
            with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
                mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
                
                with patch.object(background_loader_service, "check_missing_data", return_value={
                    "episodes": True,
                    "nfo": True,
                    "logo": True,
                    "images": True
                }):
                    with patch.object(background_loader_service, "_load_nfo_and_images", return_value=True):
                        with patch.object(background_loader_service, "_scan_missing_episodes"):
                            with patch.object(background_loader_service, "_broadcast_status"):
                                await background_loader_service._load_series_data(task)
        
        assert task.status == LoadingStatus.COMPLETED
        assert task.completed_at is not None
        assert "test" not in background_loader_service.active_tasks
    
    @pytest.mark.asyncio
    async def test_load_series_data_handles_error(self, background_loader_service):
        """Test error handling during series data loading."""
        mock_db = AsyncMock()
        
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        with patch("src.server.database.connection.get_db_session"):
            with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
                mock_service_class.get_by_key = AsyncMock(return_value=None)
                
                with patch.object(background_loader_service, "check_missing_data", side_effect=Exception("Test error")):
                    with patch.object(background_loader_service, "_broadcast_status"):
                        await background_loader_service._load_series_data(task)
        
        assert task.status == LoadingStatus.FAILED
        assert task.error == "Test error"
        assert task.completed_at is not None
    
    @pytest.mark.asyncio
    async def test_load_series_data_with_partial_missing(self, background_loader_service):
        """Test loading series when only some data is missing."""
        mock_db = AsyncMock()
        mock_series = MagicMock()
        
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        with patch("src.server.database.connection.get_db_session"):
            with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
                mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
                
                with patch.object(background_loader_service, "check_missing_data", return_value={
                    "episodes": False,
                    "nfo": False,
                    "logo": False,
                    "images": False
                }):
                    with patch.object(background_loader_service, "_load_nfo_and_images", return_value=False):
                        with patch.object(background_loader_service, "_scan_missing_episodes"):
                            with patch.object(background_loader_service, "_broadcast_status"):
                                await background_loader_service._load_series_data(task)
        
        assert task.status == LoadingStatus.COMPLETED
        # When nothing is missing, nfo/logo/images get marked true, but episodes marked false by scan
        assert task.progress["nfo"] is True or task.progress["nfo"] is False


class TestWorkerExecution:
    """Tests for background worker execution."""
    
    @pytest.mark.asyncio
    async def test_worker_processes_task_from_queue(self, background_loader_service):
        """Test worker processes task from queue."""
        # Create a task
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        # Add it to queue
        await background_loader_service.task_queue.put(task)
        
        # Start service
        await background_loader_service.start()
        
        # Let worker process briefly
        await asyncio.sleep(0.1)
        
        # Stop service
        await background_loader_service.stop()
        
        # Queue should be empty
        assert background_loader_service.task_queue.empty()
    
    @pytest.mark.asyncio
    async def test_worker_timeout_loop(self, background_loader_service):
        """Test worker timeout loop works correctly."""
        await background_loader_service.start()
        
        # Let worker loop a few times with no tasks
        await asyncio.sleep(0.2)
        
        # Workers should still be running
        assert all(not task.done() for task in background_loader_service.worker_tasks)
        
        await background_loader_service.stop()


class TestBroadcastStatusMessages:
    """Tests for various status message broadcasts."""
    
    @pytest.mark.asyncio
    async def test_broadcast_loading_episodes_status(self, background_loader_service, mock_websocket_service):
        """Test broadcasting loading episodes status."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test",
            status=LoadingStatus.LOADING_EPISODES
        )
        
        await background_loader_service._broadcast_status(task)
        
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        assert "loading" in call_args["message"].lower() or "episode" in call_args["message"].lower()
    
    @pytest.mark.asyncio
    async def test_broadcast_loading_nfo_status(self, background_loader_service, mock_websocket_service):
        """Test broadcasting loading NFO status."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test",
            status=LoadingStatus.LOADING_NFO
        )
        
        await background_loader_service._broadcast_status(task)
        
        call_args = mock_websocket_service.broadcast.call_args[0][0]
        assert "nfo" in call_args["message"].lower() or "loading" in call_args["message"].lower()


class TestErrorHandling:
    """Tests for error handling scenarios."""
    
    @pytest.mark.asyncio
    async def test_load_nfo_error_handling(self, background_loader_service):
        """Test error handling during NFO creation."""
        background_loader_service.series_app.nfo_service.has_nfo = MagicMock(return_value=False)
        background_loader_service.series_app.nfo_service.create_tvshow_nfo = AsyncMock(
            side_effect=Exception("API error")
        )
        
        mock_db = AsyncMock()
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        result = await background_loader_service._load_nfo_and_images(task, mock_db)
        
        assert result is False
        assert task.progress["nfo"] is False
        assert task.progress["logo"] is False
        assert task.progress["images"] is False
    
    @pytest.mark.asyncio
    async def test_scan_missing_episodes_error_handling(self, background_loader_service):
        """Test error handling during episode scan."""
        background_loader_service.series_app.serie_scanner.scan_single_series = MagicMock(
            side_effect=Exception("Scan error")
        )
        
        mock_db = AsyncMock()
        task = SeriesLoadingTask(
            key="test",
            folder="test_folder",
            name="Test Series"
        )
        
        await background_loader_service._scan_missing_episodes(task, mock_db)
        
        assert task.progress["episodes"] is False


class TestDirectoryScanning:
    """Tests for directory operations."""
    
    @pytest.mark.asyncio
    async def test_find_series_directory_error_handling(self, background_loader_service):
        """Test error handling when directory lookup fails."""
        background_loader_service.series_app.directory_to_search = "/invalid/path"
        
        task = SeriesLoadingTask(
            key="test",
            folder="TestSeries",
            name="Test"
        )
        
        result = await background_loader_service._find_series_directory(task)
        
        assert result is None
    
    @pytest.mark.asyncio
    async def test_scan_series_episodes_error_handling(self, background_loader_service):
        """Test error handling during episode scanning."""
        task = SeriesLoadingTask(
            key="test",
            folder="TestSeries",
            name="Test"
        )
        
        # Pass a non-existent path
        result = await background_loader_service._scan_series_episodes(Path("/nonexistent"), task)
        
        assert result == {}


class TestCheckMissingDataEdgeCases:
    """Tests for edge cases in missing data checking."""
    
    @pytest.mark.asyncio
    async def test_check_missing_data_all_loaded(self, background_loader_service):
        """Test checking missing data when all data is already loaded."""
        mock_db = AsyncMock()
        mock_series = MagicMock()
        mock_series.episodes_loaded = True
        mock_series.has_nfo = True
        mock_series.logo_loaded = True
        mock_series.images_loaded = True
        
        with patch("src.server.database.service.AnimeSeriesService") as mock_service_class:
            with patch("src.server.utils.media.check_media_files") as mock_check:
                mock_service_class.get_by_key = AsyncMock(return_value=mock_series)
                mock_check.return_value = {
                    "poster": True,
                    "logo": True,
                    "fanart": True,
                    "nfo": True
                }
                
                missing = await background_loader_service.check_missing_data(
                    key="complete_series",
                    folder="complete_folder",
                    anime_directory="/anime",
                    db=mock_db
                )
        
        assert not any(missing.values())


class TestTaskProgressTracking:
    """Tests for task progress tracking."""
    
    @pytest.mark.asyncio
    async def test_task_progress_updates(self, background_loader_service):
        """Test that task progress is properly updated."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test"
        )
        
        # Initially all progress is false
        assert not any(task.progress.values())
        
        # Update progress
        task.progress["episodes"] = True
        assert task.progress["episodes"] is True
        
        task.progress["nfo"] = True
        assert task.progress["nfo"] is True
        
        # Other progress still false
        assert not task.progress["logo"]
        assert not task.progress["images"]
    
    @pytest.mark.asyncio
    async def test_task_status_lifecycle(self, background_loader_service):
        """Test task goes through complete status lifecycle."""
        task = SeriesLoadingTask(
            key="test",
            folder="folder",
            name="Test"
        )
        
        # Start with PENDING
        assert task.status == LoadingStatus.PENDING
        
        # Simulate transitions
        task.status = LoadingStatus.LOADING_EPISODES
        assert task.status == LoadingStatus.LOADING_EPISODES
        
        task.status = LoadingStatus.LOADING_NFO
        assert task.status == LoadingStatus.LOADING_NFO
        
        task.status = LoadingStatus.COMPLETED
        assert task.status == LoadingStatus.COMPLETED
        assert task.completed_at is None  # Not set automatically
