"""Integration tests for NFO creation and media download workflows."""

import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, patch

import pytest

from src.core.services.nfo_service import NFOService
from src.core.services.tmdb_client import TMDBAPIError


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


@pytest.fixture
def nfo_service(anime_dir):
    """Create NFO service with temp directory."""
    return NFOService(
        tmdb_api_key="test_api_key",
        anime_directory=str(anime_dir),
        image_size="w500",
        auto_create=True
    )


@pytest.fixture
def mock_tmdb_complete():
    """Complete TMDB data with all fields."""
    return {
        "id": 1429,
        "name": "Attack on Titan",
        "original_name": "進撃の巨人",
        "first_air_date": "2013-04-07",
        "overview": "Humans fight against giant humanoid Titans.",
        "vote_average": 8.6,
        "vote_count": 5000,
        "status": "Ended",
        "episode_run_time": [24],
        "genres": [{"id": 16, "name": "Animation"}],
        "networks": [{"id": 1, "name": "MBS"}],
        "production_countries": [{"name": "Japan"}],
        "poster_path": "/poster.jpg",
        "backdrop_path": "/backdrop.jpg",
        "external_ids": {
            "imdb_id": "tt2560140",
            "tvdb_id": 267440
        },
        "credits": {
            "cast": [
                {"id": 1, "name": "Yuki Kaji", "character": "Eren", "profile_path": "/actor.jpg"}
            ]
        },
        "images": {
            "logos": [{"file_path": "/logo.png"}]
        }
    }


@pytest.fixture
def mock_content_ratings():
    """Mock content ratings with German FSK."""
    return {
        "results": [
            {"iso_3166_1": "DE", "rating": "16"},
            {"iso_3166_1": "US", "rating": "TV-MA"}
        ]
    }


class TestNFOCreationFlow:
    """Test complete NFO creation workflow."""
    
    @pytest.mark.asyncio
    async def test_complete_nfo_creation_workflow(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test complete NFO creation with all media files."""
        series_name = "Attack on Titan"
        series_folder = anime_dir / series_name
        series_folder.mkdir()
        
        with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            
            mock_search.return_value = {
                "results": [{"id": 1429, "name": series_name, "first_air_date": "2013-04-07"}]
            }
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            mock_download.return_value = {
                "poster": True,
                "logo": True,
                "fanart": True
            }
            
            # Create NFO
            nfo_path = await nfo_service.create_tvshow_nfo(
                series_name,
                series_name,
                year=2013,
                download_poster=True,
                download_logo=True,
                download_fanart=True
            )
            
            # Verify NFO file exists
            assert nfo_path.exists()
            assert nfo_path.name == "tvshow.nfo"
            assert nfo_path.parent == series_folder
            
            # Verify NFO content
            nfo_content = nfo_path.read_text(encoding="utf-8")
            assert "<title>Attack on Titan</title>" in nfo_content
            assert "<mpaa>FSK 16</mpaa>" in nfo_content
            assert "<tmdbid>1429</tmdbid>" in nfo_content
            
            # Verify media download was called
            mock_download.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_nfo_creation_without_media(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test NFO creation without downloading media files."""
        series_name = "Test Series"
        series_folder = anime_dir / series_name
        series_folder.mkdir()
        
        with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            
            mock_search.return_value = {
                "results": [{"id": 1, "name": series_name, "first_air_date": "2020-01-01"}]
            }
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            mock_download.return_value = {}
            
            # Create NFO without media
            nfo_path = await nfo_service.create_tvshow_nfo(
                series_name,
                series_name,
                download_poster=False,
                download_logo=False,
                download_fanart=False
            )
            
            # NFO should exist
            assert nfo_path.exists()
            
            # Verify no media URLs were passed
            call_args = mock_download.call_args
            assert call_args.kwargs['poster_url'] is None
            assert call_args.kwargs['logo_url'] is None
            assert call_args.kwargs['fanart_url'] is None
    
    @pytest.mark.asyncio
    async def test_nfo_folder_structure(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test that NFO and media files are in correct folder structure."""
        series_name = "Test Series"
        series_folder = anime_dir / series_name
        series_folder.mkdir()
        
        with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            
            mock_search.return_value = {
                "results": [{"id": 1, "name": series_name, "first_air_date": "2020-01-01"}]
            }
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            mock_download.return_value = {"poster": True}
            
            nfo_path = await nfo_service.create_tvshow_nfo(
                series_name,
                series_name,
                download_poster=True,
                download_logo=False,
                download_fanart=False
            )
            
            # Verify folder structure
            assert nfo_path.parent.name == series_name
            assert nfo_path.parent.parent == anime_dir
            
            # Verify download was called with correct folder
            call_args = mock_download.call_args
            assert call_args.args[0] == series_folder


class TestNFOUpdateFlow:
    """Test NFO update workflow."""
    
    @pytest.mark.asyncio
    async def test_nfo_update_refreshes_content(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test that NFO update refreshes content from TMDB."""
        series_name = "Test Series"
        series_folder = anime_dir / series_name
        series_folder.mkdir()
        
        # Create initial NFO
        nfo_path = series_folder / "tvshow.nfo"
        nfo_path.write_text("""<?xml version="1.0" encoding="UTF-8"?>
<tvshow>
    <title>Old Title</title>
    <plot>Old plot</plot>
    <tmdbid>1429</tmdbid>
</tvshow>
""", encoding="utf-8")
        
        with patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            mock_download.return_value = {}
            
            # Update NFO
            updated_path = await nfo_service.update_tvshow_nfo(
                series_name,
                download_media=False
            )
            
            # Verify content was updated
            updated_content = updated_path.read_text(encoding="utf-8")
            assert "<title>Attack on Titan</title>" in updated_content
            assert "Old Title" not in updated_content
            assert "進撃の巨人" in updated_content
    
    @pytest.mark.asyncio
    async def test_nfo_update_with_media_redownload(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test NFO update re-downloads media files."""
        series_name = "Test Series"
        series_folder = anime_dir / series_name
        series_folder.mkdir()
        
        nfo_path = series_folder / "tvshow.nfo"
        nfo_path.write_text("""<?xml version="1.0" encoding="UTF-8"?>
<tvshow>
    <title>Test</title>
    <tmdbid>1429</tmdbid>
</tvshow>
""", encoding="utf-8")
        
        with patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            mock_download.return_value = {"poster": True, "logo": True, "fanart": True}
            
            # Update with media
            await nfo_service.update_tvshow_nfo(
                series_name,
                download_media=True
            )
            
            # Verify media download was called
            mock_download.assert_called_once()
            call_args = mock_download.call_args
            assert call_args.kwargs['poster_url'] is not None
            assert call_args.kwargs['logo_url'] is not None
            assert call_args.kwargs['fanart_url'] is not None


class TestNFOErrorHandling:
    """Test NFO service error handling."""
    
    @pytest.mark.asyncio
    async def test_nfo_creation_continues_despite_media_failure(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test that NFO is created even if media download fails."""
        series_name = "Test Series"
        series_folder = anime_dir / series_name
        series_folder.mkdir()
        
        with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            
            mock_search.return_value = {
                "results": [{"id": 1, "name": series_name, "first_air_date": "2020-01-01"}]
            }
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            # Simulate media download failure
            mock_download.return_value = {"poster": False, "logo": False, "fanart": False}
            
            # NFO creation should succeed
            nfo_path = await nfo_service.create_tvshow_nfo(
                series_name,
                series_name,
                download_poster=True,
                download_logo=True,
                download_fanart=True
            )
            
            # NFO should exist despite media failure
            assert nfo_path.exists()
            nfo_content = nfo_path.read_text(encoding="utf-8")
            assert "<tvshow>" in nfo_content
    
    @pytest.mark.asyncio
    async def test_nfo_creation_fails_with_invalid_folder(
        self,
        nfo_service,
        anime_dir
    ):
        """Test NFO creation fails gracefully with invalid search results."""
        with patch.object(
            nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock,
            return_value={"results": []}
        ):
            with pytest.raises(TMDBAPIError, match="No results found"):
                await nfo_service.create_tvshow_nfo(
                    "Nonexistent",
                    "nonexistent_folder",
                    download_poster=False,
                    download_logo=False,
                    download_fanart=False
                )


class TestConcurrentNFOOperations:
    """Test concurrent NFO operations."""
    
    @pytest.mark.asyncio
    async def test_concurrent_nfo_creation(
        self,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test creating NFOs for multiple series concurrently."""
        nfo_service = NFOService(
            tmdb_api_key="test_key",
            anime_directory=str(anime_dir),
            image_size="w500"
        )
        
        # Create multiple series folders
        series_list = ["Series1", "Series2", "Series3"]
        for series in series_list:
            (anime_dir / series).mkdir()
        
        with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            
            # Mock responses for all series
            mock_search.return_value = {
                "results": [{"id": 1, "name": "Test", "first_air_date": "2020-01-01"}]
            }
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            mock_download.return_value = {"poster": True}
            
            # Create NFOs concurrently
            tasks = [
                nfo_service.create_tvshow_nfo(
                    series,
                    series,
                    download_poster=True,
                    download_logo=False,
                    download_fanart=False
                )
                for series in series_list
            ]
            
            nfo_paths = await asyncio.gather(*tasks)
            
            # Verify all NFOs were created
            assert len(nfo_paths) == 3
            for nfo_path in nfo_paths:
                assert nfo_path.exists()
                assert nfo_path.name == "tvshow.nfo"
    
    @pytest.mark.asyncio
    async def test_concurrent_media_downloads(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete
    ):
        """Test concurrent media downloads for same series."""
        series_folder = anime_dir / "Test"
        series_folder.mkdir()
        
        with patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock) as mock_download:
            mock_download.return_value = {"poster": True, "logo": True, "fanart": True}
            
            # Attempt concurrent downloads (simulating multiple calls)
            tasks = [
                nfo_service._download_media_files(
                    mock_tmdb_complete,
                    series_folder,
                    download_poster=True,
                    download_logo=True,
                    download_fanart=True
                )
                for _ in range(3)
            ]
            
            results = await asyncio.gather(*tasks)
            
            # All should succeed
            assert len(results) == 3
            for result in results:
                assert result["poster"] is True


class TestNFODataIntegrity:
    """Test NFO data integrity throughout workflow."""
    
    @pytest.mark.asyncio
    async def test_nfo_preserves_all_metadata(
        self,
        nfo_service,
        anime_dir,
        mock_tmdb_complete,
        mock_content_ratings
    ):
        """Test that all TMDB metadata is preserved in NFO."""
        series_name = "Complete Test"
        series_folder = anime_dir / series_name
        series_folder.mkdir()
        
        with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
             patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
             patch.object(nfo_service.image_downloader, 'download_all_media', new_callable=AsyncMock):
            
            mock_search.return_value = {
                "results": [{"id": 1429, "name": series_name, "first_air_date": "2013-04-07"}]
            }
            mock_details.return_value = mock_tmdb_complete
            mock_ratings.return_value = mock_content_ratings
            
            nfo_path = await nfo_service.create_tvshow_nfo(
                series_name,
                series_name,
                year=2013,
                download_poster=False,
                download_logo=False,
                download_fanart=False
            )
            
            # Verify all key metadata is in NFO
            nfo_content = nfo_path.read_text(encoding="utf-8")
            assert "<title>Attack on Titan</title>" in nfo_content
            assert "<originaltitle>進撃の巨人</originaltitle>" in nfo_content
            assert "<year>2013</year>" in nfo_content
            assert "<plot>Humans fight against giant humanoid Titans.</plot>" in nfo_content
            assert "<status>Ended</status>" in nfo_content
            assert "<genre>Animation</genre>" in nfo_content
            assert "<studio>MBS</studio>" in nfo_content
            assert "<country>Japan</country>" in nfo_content
            assert "<mpaa>FSK 16</mpaa>" in nfo_content
            assert "<tmdbid>1429</tmdbid>" in nfo_content
            assert "<imdbid>tt2560140</imdbid>" in nfo_content
            assert "<tvdbid>267440</tvdbid>" in nfo_content
            assert "<name>Yuki Kaji</name>" in nfo_content
            assert "<role>Eren</role>" in nfo_content
