"""
Integration tests for asynchronous series data loading.
Tests the complete flow from API to database with WebSocket notifications.

Note: These tests focus on the integration between components and proper async behavior.
Mocking is used to avoid dependencies on external services (TMDB, Aniworld, etc.).
"""
import asyncio
from unittest.mock import AsyncMock, Mock

import pytest
from httpx import ASGITransport, AsyncClient

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


@pytest.fixture
async def client():
    """Create an async test client."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        yield ac


class TestBackgroundLoaderIntegration:
    """Integration tests for BackgroundLoaderService."""

    @pytest.mark.asyncio
    async def test_loader_initialization(self):
        """Test that background loader initializes correctly."""
        mock_websocket = AsyncMock()
        mock_anime_service = AsyncMock()
        mock_series_app = Mock()

        loader = BackgroundLoaderService(
            websocket_service=mock_websocket,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )

        assert loader.websocket_service == mock_websocket
        assert loader.anime_service == mock_anime_service
        assert loader.series_app == mock_series_app
        assert loader.task_queue is not None
        assert loader.active_tasks == {}

    @pytest.mark.asyncio
    async def test_loader_start_stop(self):
        """Test starting and stopping the background loader."""
        mock_websocket = AsyncMock()
        mock_anime_service = AsyncMock()
        mock_series_app = Mock()

        loader = BackgroundLoaderService(
            websocket_service=mock_websocket,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )

        # Start loader
        await loader.start()
        assert len(loader.worker_tasks) > 0
        assert not loader.worker_tasks[0].done()

        # Stop loader
        await loader.stop()
        assert all(task.done() for task in loader.worker_tasks)

    @pytest.mark.asyncio
    async def test_add_series_loading_task(self):
        """Test adding a series loading task to the queue."""
        mock_websocket = AsyncMock()
        mock_anime_service = AsyncMock()
        mock_series_app = Mock()

        loader = BackgroundLoaderService(
            websocket_service=mock_websocket,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )

        # Mock _load_series_data to prevent DB access and keep task in active_tasks
        async def slow_load(task):
            await asyncio.sleep(100)
        loader._load_series_data = slow_load

        await loader.start()

        try:
            # Add a task
            await loader.add_series_loading_task(
                key="test-series",
                folder="test_folder",
                name="Test Series"
            )

            # Wait a moment for task to be picked up
            await asyncio.sleep(0.2)

            # Verify task was added
            assert "test-series" in loader.active_tasks
            task = loader.active_tasks["test-series"]
            assert task.key == "test-series"
            assert task.name == "Test Series"
            assert task.folder == "test_folder"
        finally:
            await loader.stop()

    @pytest.mark.asyncio
    async def test_multiple_tasks_concurrent(self):
        """Test loader handles multiple concurrent tasks."""
        mock_websocket = AsyncMock()
        mock_anime_service = AsyncMock()
        mock_series_app = Mock()

        # Setup mocks to simulate async work
        mock_anime_service.scan_series_episodes = AsyncMock()
        mock_anime_service.update_series = AsyncMock()
        mock_series_app.nfo_service = Mock()
        mock_series_app.nfo_service.create_nfo_async = AsyncMock()

        loader = BackgroundLoaderService(
            websocket_service=mock_websocket,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )

        # Mock _load_series_data to prevent DB access and keep tasks in active_tasks
        async def slow_load(task):
            await asyncio.sleep(100)
        loader._load_series_data = slow_load

        await loader.start()

        try:
            # Add multiple tasks quickly
            series_count = 3
            for i in range(series_count):
                await loader.add_series_loading_task(
                    key=f"series-{i}",
                    folder=f"folder_{i}",
                    name=f"Series {i}"
                )

            # Wait for processing
            await asyncio.sleep(0.5)

            # Verify all tasks were registered
            assert len(loader.active_tasks) >= series_count
        finally:
            await loader.stop()

    @pytest.mark.asyncio
    async def test_graceful_shutdown(self):
        """Test graceful shutdown with pending tasks."""
        mock_websocket = AsyncMock()
        mock_anime_service = AsyncMock()
        mock_series_app = Mock()

        loader = BackgroundLoaderService(
            websocket_service=mock_websocket,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )

        await loader.start()

        # Add tasks
        for i in range(5):
            await loader.add_series_loading_task(
                key=f"series-{i}",
                folder=f"folder_{i}",
                name=f"Series {i}"
            )

        # Stop should complete without exceptions
        try:
            await loader.stop()
            shutdown_success = True
        except Exception:
            shutdown_success = False

        assert shutdown_success
        # Check all worker tasks are done
        assert all(task.done() for task in loader.worker_tasks)

    @pytest.mark.asyncio
    async def test_no_duplicate_tasks(self):
        """Test that duplicate tasks for same series are handled."""
        mock_websocket = AsyncMock()
        mock_anime_service = AsyncMock()
        mock_series_app = Mock()

        loader = BackgroundLoaderService(
            websocket_service=mock_websocket,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )

        # Mock _load_series_data to prevent DB access and keep tasks in active_tasks
        async def slow_load(task):
            await asyncio.sleep(100)
        loader._load_series_data = slow_load

        await loader.start()

        try:
            # Try to add same series multiple times
            for _ in range(3):
                await loader.add_series_loading_task(
                    key="test-series",
                    folder="test_folder",
                    name="Test Series"
                )

            await asyncio.sleep(0.2)

            # Should only have one task for this series
            series_tasks = [k for k in loader.active_tasks if k == "test-series"]
            assert len(series_tasks) == 1
        finally:
            await loader.stop()


class TestLoadingStatusEnum:
    """Tests for LoadingStatus enum."""

    def test_loading_status_values(self):
        """Test LoadingStatus enum has expected values."""
        assert LoadingStatus.PENDING.value == "pending"
        assert LoadingStatus.LOADING_EPISODES.value == "loading_episodes"
        assert LoadingStatus.LOADING_NFO.value == "loading_nfo"
        assert LoadingStatus.LOADING_LOGO.value == "loading_logo"
        assert LoadingStatus.LOADING_IMAGES.value == "loading_images"
        assert LoadingStatus.COMPLETED.value == "completed"
        assert LoadingStatus.FAILED.value == "failed"

    def test_loading_status_string_repr(self):
        """Test LoadingStatus can be used as strings."""
        status = LoadingStatus.LOADING_EPISODES
        # The enum string representation includes the class name
        assert status.value == "loading_episodes"
        assert status == LoadingStatus.LOADING_EPISODES


class TestAsyncBehavior:
    """Tests to verify async and non-blocking behavior."""

    @pytest.mark.asyncio
    async def test_adding_tasks_is_fast(self):
        """Test that adding tasks doesn't block."""
        mock_websocket = AsyncMock()
        mock_anime_service = AsyncMock()
        mock_series_app = Mock()

        # Make operations slow
        async def slow_operation(*args, **kwargs):
            await asyncio.sleep(0.5)

        mock_anime_service.scan_series_episodes = AsyncMock(side_effect=slow_operation)
        mock_anime_service.update_series = AsyncMock()
        mock_series_app.nfo_service = Mock()
        mock_series_app.nfo_service.create_nfo_async = AsyncMock(side_effect=slow_operation)

        loader = BackgroundLoaderService(
            websocket_service=mock_websocket,
            anime_service=mock_anime_service,
            series_app=mock_series_app
        )

        # Mock _load_series_data to prevent DB access and keep tasks in active_tasks
        async def slow_load(task):
            await asyncio.sleep(100)
        loader._load_series_data = slow_load

        await loader.start()

        try:
            # Add multiple series quickly
            start_time = asyncio.get_event_loop().time()
            
            for i in range(5):
                await loader.add_series_loading_task(
                    key=f"series-{i}",
                    folder=f"folder_{i}",
                    name=f"Series {i}"
                )
            
            add_time = asyncio.get_event_loop().time() - start_time

            # Adding tasks should be fast (< 0.1s for 5 series)
            # even though processing is slow
            assert add_time < 0.1

            # Verify all were queued
            await asyncio.sleep(0.1)
            assert len(loader.active_tasks) == 5
        finally:
            await loader.stop()
