"""Integration tests for NFO creation during download flow.

Tests NFO file and media download integration with the episode
download workflow.
"""
import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch

import pytest

from src.config.settings import Settings
from src.core.SeriesApp import DownloadStatusEventArgs, SeriesApp
from src.core.services.nfo_service import NFOService
from src.core.services.tmdb_client import TMDBAPIError


@pytest.fixture
def temp_anime_dir(tmp_path):
    """Create temporary anime directory."""
    anime_dir = tmp_path / "anime"
    anime_dir.mkdir()
    return str(anime_dir)


@pytest.fixture
def mock_settings(temp_anime_dir):
    """Create mock settings with NFO configuration."""
    settings = Settings()
    settings.anime_directory = temp_anime_dir
    settings.tmdb_api_key = "test_api_key_12345"
    settings.nfo_auto_create = True
    settings.nfo_download_poster = True
    settings.nfo_download_logo = True
    settings.nfo_download_fanart = True
    settings.nfo_image_size = "original"
    return settings


@pytest.fixture
def mock_nfo_service():
    """Create mock NFO service."""
    service = Mock(spec=NFOService)
    service.check_nfo_exists = AsyncMock(return_value=False)
    service.create_tvshow_nfo = AsyncMock()
    return service


@pytest.fixture
def mock_loader():
    """Create mock loader for downloads."""
    loader = Mock()
    loader.download = Mock(return_value=True)
    loader.subscribe_download_progress = Mock()
    loader.unsubscribe_download_progress = Mock()
    return loader


class TestNFODownloadIntegration:
    """Test NFO creation integrated with download flow."""

    @pytest.mark.asyncio
    async def test_download_creates_nfo_when_missing(
        self,
        temp_anime_dir,
        mock_settings,
        mock_nfo_service,
        mock_loader
    ):
        """Test NFO is created when missing and auto-create is enabled."""
        # Setup
        with patch('src.core.SeriesApp.settings', mock_settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            # Configure mock loaders
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            # Create SeriesApp
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            series_app.nfo_service = mock_nfo_service
            
            # Track download events
            events_received = []
            
            def on_download_status(args: DownloadStatusEventArgs):
                events_received.append({
                    "status": args.status,
                    "message": args.message,
                    "serie_folder": args.serie_folder
                })
            
            series_app._events.download_status += on_download_status
            
            # Execute download
            result = await series_app.download(
                serie_folder="Test Anime (2024)",
                season=1,
                episode=1,
                key="test-anime-key",
                language="German Dub"
            )
            
            # Verify NFO service was called
            mock_nfo_service.check_nfo_exists.assert_called_once_with(
                "Test Anime (2024)"
            )
            mock_nfo_service.create_tvshow_nfo.assert_called_once_with(
                serie_name="Test Anime (2024)",
                serie_folder="Test Anime (2024)",
                download_poster=True,
                download_logo=True,
                download_fanart=True
            )
            
            # Verify download events
            nfo_events = [
                e for e in events_received
                if e["status"] in ["nfo_creating", "nfo_completed"]
            ]
            assert len(nfo_events) >= 2
            assert nfo_events[0]["status"] == "nfo_creating"
            assert nfo_events[1]["status"] == "nfo_completed"
            
            # Verify download was successful
            assert result is True

    @pytest.mark.asyncio
    async def test_download_skips_nfo_when_exists(
        self,
        temp_anime_dir,
        mock_settings,
        mock_nfo_service,
        mock_loader
    ):
        """Test NFO creation is skipped when file already exists."""
        # Configure NFO service to report NFO exists
        mock_nfo_service.check_nfo_exists = AsyncMock(return_value=True)
        
        with patch('src.core.SeriesApp.settings', mock_settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            series_app.nfo_service = mock_nfo_service
            
            # Execute download
            result = await series_app.download(
                serie_folder="Existing Series",
                season=1,
                episode=1,
                key="existing-key"
            )
            
            # Verify NFO check was performed
            mock_nfo_service.check_nfo_exists.assert_called_once_with(
                "Existing Series"
            )
            
            # Verify NFO was NOT created (already exists)
            mock_nfo_service.create_tvshow_nfo.assert_not_called()
            
            # Verify download still succeeded
            assert result is True

    @pytest.mark.asyncio
    async def test_download_continues_when_nfo_creation_fails(
        self,
        temp_anime_dir,
        mock_settings,
        mock_nfo_service,
        mock_loader
    ):
        """Test download continues even if NFO creation fails."""
        # Configure NFO service to fail
        mock_nfo_service.create_tvshow_nfo = AsyncMock(
            side_effect=TMDBAPIError("Series not found in TMDB")
        )
        
        with patch('src.core.SeriesApp.settings', mock_settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            series_app.nfo_service = mock_nfo_service
            
            events_received = []
            
            def on_download_status(args: DownloadStatusEventArgs):
                events_received.append({
                    "status": args.status,
                    "message": args.message
                })
            
            series_app._events.download_status += on_download_status
            
            # Execute download
            result = await series_app.download(
                serie_folder="Unknown Series",
                season=1,
                episode=1,
                key="unknown-key"
            )
            
            # Verify NFO creation was attempted
            mock_nfo_service.create_tvshow_nfo.assert_called_once()
            
            # Verify nfo_failed event was fired
            nfo_failed_events = [
                e for e in events_received if e["status"] == "nfo_failed"
            ]
            assert len(nfo_failed_events) == 1
            assert "NFO creation failed" in nfo_failed_events[0]["message"]
            
            # Verify download still succeeded despite NFO failure
            assert result is True

    @pytest.mark.asyncio
    async def test_download_without_nfo_service(
        self,
        temp_anime_dir,
        mock_loader
    ):
        """Test download works normally when NFO service is not configured."""
        settings = Settings()
        settings.anime_directory = temp_anime_dir
        settings.tmdb_api_key = None  # No TMDB API key
        settings.nfo_auto_create = False
        
        with patch('src.core.SeriesApp.settings', settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            
            # NFO service should not be initialized
            assert series_app.nfo_service is None
            
            # Execute download
            result = await series_app.download(
                serie_folder="Regular Series",
                season=1,
                episode=1,
                key="regular-key"
            )
            
            # Download should succeed without NFO service
            assert result is True

    @pytest.mark.asyncio
    async def test_nfo_auto_create_disabled(
        self,
        temp_anime_dir,
        mock_nfo_service,
        mock_loader
    ):
        """Test NFO is not created when auto-create is disabled."""
        settings = Settings()
        settings.anime_directory = temp_anime_dir
        settings.tmdb_api_key = "test_key"
        settings.nfo_auto_create = False  # Disabled
        
        with patch('src.core.SeriesApp.settings', settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            series_app.nfo_service = mock_nfo_service
            
            # Execute download
            result = await series_app.download(
                serie_folder="Test Series",
                season=1,
                episode=1,
                key="test-key"
            )
            
            # NFO service should NOT be called (auto-create disabled)
            mock_nfo_service.check_nfo_exists.assert_not_called()
            mock_nfo_service.create_tvshow_nfo.assert_not_called()
            
            # Download should still succeed
            assert result is True

    @pytest.mark.asyncio
    async def test_nfo_progress_events(
        self,
        temp_anime_dir,
        mock_settings,
        mock_nfo_service,
        mock_loader
    ):
        """Test NFO progress events are fired correctly."""
        with patch('src.core.SeriesApp.settings', mock_settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            series_app.nfo_service = mock_nfo_service
            
            events_received = []
            
            def on_download_status(args: DownloadStatusEventArgs):
                events_received.append({
                    "status": args.status,
                    "message": args.message,
                    "serie_folder": args.serie_folder,
                    "key": args.key,
                    "season": args.season,
                    "episode": args.episode,
                    "item_id": args.item_id
                })
            
            series_app._events.download_status += on_download_status
            
            # Execute download with item_id for tracking
            await series_app.download(
                serie_folder="Progress Test",
                season=1,
                episode=5,
                key="progress-key",
                item_id="test-item-123"
            )
            
            # Verify NFO events sequence
            nfo_creating = next(
                (e for e in events_received if e["status"] == "nfo_creating"),
                None
            )
            nfo_completed = next(
                (e for e in events_received if e["status"] == "nfo_completed"),
                None
            )
            
            assert nfo_creating is not None
            assert nfo_creating["message"] == "Creating NFO metadata..."
            assert nfo_creating["serie_folder"] == "Progress Test"
            assert nfo_creating["key"] == "progress-key"
            assert nfo_creating["season"] == 1
            assert nfo_creating["episode"] == 5
            assert nfo_creating["item_id"] == "test-item-123"
            
            assert nfo_completed is not None
            assert nfo_completed["message"] == "NFO metadata created"
            assert nfo_completed["item_id"] == "test-item-123"

    @pytest.mark.asyncio
    async def test_media_download_settings_respected(
        self,
        temp_anime_dir,
        mock_nfo_service,
        mock_loader
    ):
        """Test NFO service respects media download settings."""
        settings = Settings()
        settings.anime_directory = temp_anime_dir
        settings.tmdb_api_key = "test_key"
        settings.nfo_auto_create = True
        settings.nfo_download_poster = True
        settings.nfo_download_logo = False  # Disabled
        settings.nfo_download_fanart = True
        
        with patch('src.core.SeriesApp.settings', settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            series_app.nfo_service = mock_nfo_service
            
            # Execute download
            await series_app.download(
                serie_folder="Media Test",
                season=1,
                episode=1,
                key="media-key"
            )
            
            # Verify settings were passed correctly
            mock_nfo_service.create_tvshow_nfo.assert_called_once_with(
                serie_name="Media Test",
                serie_folder="Media Test",
                download_poster=True,
                download_logo=False,  # Disabled in settings
                download_fanart=True
            )

    @pytest.mark.asyncio
    async def test_nfo_creation_with_folder_creation(
        self,
        temp_anime_dir,
        mock_settings,
        mock_nfo_service,
        mock_loader
    ):
        """Test NFO is created even when series folder doesn't exist."""
        with patch('src.core.SeriesApp.settings', mock_settings), \
             patch('src.core.SeriesApp.Loaders') as mock_loaders_class:
            
            mock_loaders = Mock()
            mock_loaders.GetLoader.return_value = mock_loader
            mock_loaders_class.return_value = mock_loaders
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            series_app.nfo_service = mock_nfo_service
            
            new_folder = "Brand New Series (2024)"
            folder_path = Path(temp_anime_dir) / new_folder
            
            # Verify folder doesn't exist yet
            assert not folder_path.exists()
            
            # Execute download
            result = await series_app.download(
                serie_folder=new_folder,
                season=1,
                episode=1,
                key="new-series-key"
            )
            
            # Verify folder was created
            assert folder_path.exists()
            
            # Verify NFO creation was attempted
            mock_nfo_service.check_nfo_exists.assert_called_once()
            mock_nfo_service.create_tvshow_nfo.assert_called_once()
            
            # Verify download succeeded
            assert result is True


class TestNFOServiceInitialization:
    """Test NFO service initialization in SeriesApp."""

    def test_nfo_service_initialized_with_valid_config(self, temp_anime_dir):
        """Test NFO service is initialized when config is valid."""
        settings = Settings()
        settings.anime_directory = temp_anime_dir
        settings.tmdb_api_key = "valid_api_key_123"
        settings.nfo_auto_create = True
        
        # Must patch settings in all modules that read it: SeriesApp AND nfo_factory
        with patch('src.core.SeriesApp.settings', settings), \
             patch('src.core.services.nfo_factory.settings', settings), \
             patch('src.core.SeriesApp.Loaders'):
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            
            # NFO service should be initialized
            assert series_app.nfo_service is not None
            assert isinstance(series_app.nfo_service, NFOService)

    def test_nfo_service_not_initialized_without_api_key(self, temp_anime_dir):
        """Test NFO service is not initialized without TMDB API key."""
        settings = Settings()
        settings.anime_directory = temp_anime_dir
        settings.tmdb_api_key = None  # No API key
        
        with patch('src.core.SeriesApp.settings', settings), \
             patch('src.core.SeriesApp.Loaders'):
            
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            
            # NFO service should NOT be initialized
            assert series_app.nfo_service is None

    def test_nfo_service_initialization_failure_handled(self, temp_anime_dir):
        """Test graceful handling when NFO service initialization fails."""
        settings = Settings()
        settings.anime_directory = temp_anime_dir
        settings.tmdb_api_key = "test_key"
        
        with patch('src.core.SeriesApp.settings', settings), \
             patch('src.core.SeriesApp.Loaders'), \
             patch('src.core.services.nfo_factory.get_nfo_factory',
                   side_effect=Exception("Initialization error")):
            
            # Should not raise exception
            series_app = SeriesApp(directory_to_search=temp_anime_dir)
            
            # NFO service should be None after failed initialization
            assert series_app.nfo_service is None
