"""Unit tests for initialization service.

This module tests application startup orchestration, configuration loading,
and initialization sequences to ensure proper system setup.

Coverage Target: 85%+
"""

from unittest.mock import AsyncMock, MagicMock, call, patch

import pytest

from src.server.services.folder_scan_service import perform_nfo_repair_scan
from src.server.services.initialization_service import (
    _check_initial_scan_status,
    _check_media_scan_status,
    _check_nfo_scan_status,
    _check_scan_status,
    _execute_media_scan,
    _execute_nfo_scan,
    _is_nfo_scan_configured,
    _load_series_into_memory,
    _mark_initial_scan_completed,
    _mark_media_scan_completed,
    _mark_nfo_scan_completed,
    _mark_scan_completed,
    _sync_anime_folders,
    _validate_anime_directory,
    perform_initial_setup,
    perform_media_scan_if_needed,
    perform_nfo_scan_if_needed,
)


class TestCheckScanStatus:
    """Test _check_scan_status generic function."""
    
    @pytest.mark.asyncio
    async def test_check_scan_status_completed(self):
        """Test checking scan status when completed."""
        mock_check_method = AsyncMock(return_value=True)
        
        with patch('src.server.database.connection.get_db_session') as mock_get_db:
            mock_db = AsyncMock()
            mock_get_db.return_value.__aenter__.return_value = mock_db
            
            result = await _check_scan_status(
                check_method=mock_check_method,
                scan_type="test",
                log_completed_msg="Completed",
                log_not_completed_msg="Not completed"
            )
        
        assert result is True
        mock_check_method.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_check_scan_status_not_completed(self):
        """Test checking scan status when not completed."""
        mock_check_method = AsyncMock(return_value=False)
        
        with patch('src.server.database.connection.get_db_session') as mock_get_db:
            mock_db = AsyncMock()
            mock_get_db.return_value.__aenter__.return_value = mock_db
            
            result = await _check_scan_status(
                check_method=mock_check_method,
                scan_type="test"
            )
        
        assert result is False
    
    @pytest.mark.asyncio
    async def test_check_scan_status_exception(self):
        """Test checking scan status with exception returns False."""
        mock_check_method = AsyncMock(side_effect=Exception("Database error"))
        
        with patch('src.server.database.connection.get_db_session') as mock_get_db:
            mock_db = AsyncMock()
            mock_get_db.return_value.__aenter__.return_value = mock_db
            
            result = await _check_scan_status(
                check_method=mock_check_method,
                scan_type="test"
            )
        
        assert result is False


class TestMarkScanCompleted:
    """Test _mark_scan_completed generic function."""
    
    @pytest.mark.asyncio
    async def test_mark_scan_completed_success(self):
        """Test marking scan as completed successfully."""
        mock_mark_method = AsyncMock()
        
        with patch('src.server.database.connection.get_db_session') as mock_get_db:
            mock_db = AsyncMock()
            mock_get_db.return_value.__aenter__.return_value = mock_db
            
            await _mark_scan_completed(
                mark_method=mock_mark_method,
                scan_type="test"
            )
        
        mock_mark_method.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_mark_scan_completed_exception(self):
        """Test marking scan as completed with exception logs warning."""
        mock_mark_method = AsyncMock(side_effect=Exception("Database error"))
        
        with patch('src.server.database.connection.get_db_session') as mock_get_db:
            mock_db = AsyncMock()
            mock_get_db.return_value.__aenter__.return_value = mock_db
            
            # Should not raise exception
            await _mark_scan_completed(
                mark_method=mock_mark_method,
                scan_type="test"
            )


class TestInitialScanFunctions:
    """Test initial scan status check and marking functions."""
    
    @pytest.mark.asyncio
    async def test_check_initial_scan_status_completed(self):
        """Test checking initial scan when completed."""
        with patch('src.server.services.initialization_service._check_scan_status', 
                   new_callable=AsyncMock, return_value=True) as mock_check:
            result = await _check_initial_scan_status()
        
        assert result is True
        mock_check.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_check_initial_scan_status_not_completed(self):
        """Test checking initial scan when not completed."""
        with patch('src.server.services.initialization_service._check_scan_status', 
                   new_callable=AsyncMock, return_value=False) as mock_check:
            result = await _check_initial_scan_status()
        
        assert result is False
    
    @pytest.mark.asyncio
    async def test_mark_initial_scan_completed(self):
        """Test marking initial scan as completed."""
        with patch('src.server.services.initialization_service._mark_scan_completed', 
                   new_callable=AsyncMock) as mock_mark:
            await _mark_initial_scan_completed()
        
        mock_mark.assert_called_once()


class TestSyncAnimeFolders:
    """Test anime folder scanning and syncing."""
    
    @pytest.mark.asyncio
    async def test_sync_anime_folders_without_progress(self):
        """Test syncing anime folders without progress service."""
        with patch('src.server.services.initialization_service.sync_legacy_series_to_db', 
                   new_callable=AsyncMock, return_value=42) as mock_sync:
            result = await _sync_anime_folders()
        
        assert result == 42
        mock_sync.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_sync_anime_folders_with_progress(self):
        """Test syncing anime folders with progress updates."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service.sync_legacy_series_to_db', 
                   new_callable=AsyncMock, return_value=10) as mock_sync:
            result = await _sync_anime_folders(progress_service=mock_progress)
        
        assert result == 10
        # Verify progress updates were called
        assert mock_progress.update_progress.call_count == 2
        mock_progress.update_progress.assert_any_call(
            progress_id="series_sync",
            current=25,
            message="Scanning anime folders...",
            metadata={"step_id": "series_sync"}
        )
        mock_progress.update_progress.assert_any_call(
            progress_id="series_sync",
            current=75,
            message="Synced 10 series from data files",
            metadata={"step_id": "series_sync"}
        )


class TestLoadSeriesIntoMemory:
    """Test series loading into memory."""
    
    @pytest.mark.asyncio
    async def test_load_series_without_progress(self):
        """Test loading series without progress service."""
        mock_anime_service = MagicMock()
        mock_anime_service._load_series_from_db = AsyncMock()
        
        with patch('src.server.utils.dependencies.get_anime_service', 
                   return_value=mock_anime_service):
            await _load_series_into_memory()
        
        mock_anime_service._load_series_from_db.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_load_series_with_progress(self):
        """Test loading series with progress service."""
        mock_anime_service = MagicMock()
        mock_anime_service._load_series_from_db = AsyncMock()
        mock_progress = AsyncMock()
        
        with patch('src.server.utils.dependencies.get_anime_service', 
                   return_value=mock_anime_service):
            await _load_series_into_memory(progress_service=mock_progress)
        
        mock_anime_service._load_series_from_db.assert_called_once()
        mock_progress.complete_progress.assert_called_once_with(
            progress_id="series_sync",
            message="Series loaded into memory",
            metadata={"step_id": "series_sync"}
        )


class TestValidateAnimeDirectory:
    """Test anime directory validation."""
    
    @pytest.mark.asyncio
    async def test_validate_directory_configured(self):
        """Test validation when directory is configured."""
        with patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.anime_directory = "/path/to/anime"
            
            result = await _validate_anime_directory()
        
        assert result is True
    
    @pytest.mark.asyncio
    async def test_validate_directory_not_configured(self):
        """Test validation when directory is not configured."""
        with patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.anime_directory = None
            
            result = await _validate_anime_directory()
        
        assert result is False
    
    @pytest.mark.asyncio
    async def test_validate_directory_not_configured_with_progress(self):
        """Test validation when directory not configured with progress service."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.anime_directory = ""
            
            result = await _validate_anime_directory(progress_service=mock_progress)
        
        assert result is False
        mock_progress.complete_progress.assert_called_once_with(
            progress_id="series_sync",
            message="No anime directory configured",
            metadata={"step_id": "series_sync"}
        )


class TestPerformInitialSetup:
    """Test complete initial setup orchestration."""
    
    @pytest.mark.asyncio
    async def test_initial_setup_already_completed(self):
        """Test setup skips when already completed."""
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=True):
            result = await perform_initial_setup()
        
        assert result is False
    
    @pytest.mark.asyncio
    async def test_initial_setup_already_completed_with_progress(self):
        """Test setup skips when already completed with progress updates."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=True):
            result = await perform_initial_setup(progress_service=mock_progress)
        
        assert result is False
        mock_progress.start_progress.assert_called_once()
        mock_progress.complete_progress.assert_called_once_with(
            progress_id="series_sync",
            message="Already completed",
            metadata={"step_id": "series_sync"}
        )
    
    @pytest.mark.asyncio
    async def test_initial_setup_directory_not_configured(self):
        """Test setup fails when directory not configured."""
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=False):
            result = await perform_initial_setup()
        
        assert result is False
    
    @pytest.mark.asyncio
    async def test_initial_setup_success(self):
        """Test successful initial setup."""
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, return_value=5), \
             patch('src.server.services.initialization_service._mark_initial_scan_completed', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._load_series_into_memory', 
                   new_callable=AsyncMock):
            result = await perform_initial_setup()
        
        assert result is True
    
    @pytest.mark.asyncio
    async def test_initial_setup_with_progress_service(self):
        """Test successful initial setup with progress updates."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, return_value=5), \
             patch('src.server.services.initialization_service._mark_initial_scan_completed', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._load_series_into_memory', 
                   new_callable=AsyncMock):
            result = await perform_initial_setup(progress_service=mock_progress)
        
        assert result is True
        mock_progress.start_progress.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_initial_setup_os_error(self):
        """Test setup handles OSError gracefully."""
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, side_effect=OSError("Disk error")):
            result = await perform_initial_setup()
        
        assert result is False
    
    @pytest.mark.asyncio
    async def test_initial_setup_runtime_error(self):
        """Test setup handles RuntimeError gracefully."""
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, side_effect=RuntimeError("Runtime error")):
            result = await perform_initial_setup()
        
        assert result is False
    
    @pytest.mark.asyncio
    async def test_initial_setup_value_error(self):
        """Test setup handles ValueError gracefully."""
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, side_effect=ValueError("Invalid value")):
            result = await perform_initial_setup()
        
        assert result is False


class TestNFOScanFunctions:
    """Test NFO scan status and configuration checks."""
    
    @pytest.mark.asyncio
    async def test_check_nfo_scan_status(self):
        """Test checking NFO scan status."""
        with patch('src.server.services.initialization_service._check_scan_status', 
                   new_callable=AsyncMock, return_value=True) as mock_check:
            result = await _check_nfo_scan_status()
        
        assert result is True
        mock_check.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_mark_nfo_scan_completed(self):
        """Test marking NFO scan as completed."""
        with patch('src.server.services.initialization_service._mark_scan_completed', 
                   new_callable=AsyncMock) as mock_mark:
            await _mark_nfo_scan_completed()
        
        mock_mark.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_is_nfo_scan_configured_with_api_key_and_auto_create(self):
        """Test NFO scan configured with API key and auto create."""
        with patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.tmdb_api_key = "test_api_key"
            mock_settings.nfo_auto_create = True
            mock_settings.nfo_update_on_scan = False
            
            result = await _is_nfo_scan_configured()
        
        assert result is True
    
    @pytest.mark.asyncio
    async def test_is_nfo_scan_configured_with_api_key_and_update_on_scan(self):
        """Test NFO scan configured with API key and update on scan."""
        with patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.tmdb_api_key = "test_api_key"
            mock_settings.nfo_auto_create = False
            mock_settings.nfo_update_on_scan = True
            
            result = await _is_nfo_scan_configured()
        
        assert result is True
    
    @pytest.mark.asyncio
    async def test_is_nfo_scan_not_configured_no_api_key(self):
        """Test NFO scan not configured without API key."""
        with patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.tmdb_api_key = None
            mock_settings.nfo_auto_create = True
            mock_settings.nfo_update_on_scan = True
            
            result = await _is_nfo_scan_configured()
        
        # Result should be falsy (None or False) when API key is None
        assert not result
    
    @pytest.mark.asyncio
    async def test_is_nfo_scan_not_configured_features_disabled(self):
        """Test NFO scan not configured when features disabled."""
        with patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.tmdb_api_key = "test_api_key"
            mock_settings.nfo_auto_create = False
            mock_settings.nfo_update_on_scan = False
            
            result = await _is_nfo_scan_configured()
        
        assert result is False


class TestExecuteNFOScan:
    """Test NFO scan execution."""
    
    @pytest.mark.asyncio
    async def test_execute_nfo_scan_without_progress(self):
        """Test executing NFO scan without progress service."""
        mock_manager = MagicMock()
        mock_manager.scan_and_process_nfo = AsyncMock()
        mock_manager.close = AsyncMock()
        
        with patch('src.core.services.series_manager_service.SeriesManagerService') as mock_sms:
            mock_sms.from_settings.return_value = mock_manager
            
            await _execute_nfo_scan()
        
        mock_manager.scan_and_process_nfo.assert_called_once()
        mock_manager.close.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_execute_nfo_scan_with_progress(self):
        """Test executing NFO scan with progress updates."""
        mock_manager = MagicMock()
        mock_manager.scan_and_process_nfo = AsyncMock()
        mock_manager.close = AsyncMock()
        mock_progress = AsyncMock()
        
        with patch('src.core.services.series_manager_service.SeriesManagerService') as mock_sms:
            mock_sms.from_settings.return_value = mock_manager
            
            await _execute_nfo_scan(progress_service=mock_progress)
        
        mock_manager.scan_and_process_nfo.assert_called_once()
        mock_manager.close.assert_called_once()
        assert mock_progress.update_progress.call_count == 2
        mock_progress.complete_progress.assert_called_once()


class TestPerformNFOScan:
    """Test complete NFO scan orchestration."""
    
    @pytest.mark.asyncio
    async def test_nfo_scan_already_completed(self):
        """Test NFO scan skips when already completed."""
        with patch('src.server.services.initialization_service._check_nfo_scan_status', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._is_nfo_scan_configured', 
                   new_callable=AsyncMock, return_value=True):
            await perform_nfo_scan_if_needed()
    
    @pytest.mark.asyncio
    async def test_nfo_scan_not_configured_no_api_key(self):
        """Test NFO scan skips when API key not configured."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service._check_nfo_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._is_nfo_scan_configured', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.tmdb_api_key = None
            
            await perform_nfo_scan_if_needed(progress_service=mock_progress)
        
        mock_progress.start_progress.assert_called_once()
        mock_progress.complete_progress.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_nfo_scan_not_configured_features_disabled(self):
        """Test NFO scan skips when features disabled."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service._check_nfo_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._is_nfo_scan_configured', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service.settings') as mock_settings:
            mock_settings.tmdb_api_key = "test_key"
            
            await perform_nfo_scan_if_needed(progress_service=mock_progress)
        
        mock_progress.complete_progress.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_nfo_scan_success(self):
        """Test successful NFO scan execution."""
        with patch('src.server.services.initialization_service._check_nfo_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._is_nfo_scan_configured', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._execute_nfo_scan', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._mark_nfo_scan_completed', 
                   new_callable=AsyncMock) as mock_mark:
            await perform_nfo_scan_if_needed()
        
        mock_mark.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_nfo_scan_with_progress_service(self):
        """Test NFO scan with progress service updates."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service._check_nfo_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._is_nfo_scan_configured', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._execute_nfo_scan', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._mark_nfo_scan_completed', 
                   new_callable=AsyncMock):
            await perform_nfo_scan_if_needed(progress_service=mock_progress)
        
        mock_progress.start_progress.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_nfo_scan_exception_handling(self):
        """Test NFO scan handles exceptions and updates progress."""
        mock_progress = AsyncMock()
        
        with patch('src.server.services.initialization_service._check_nfo_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._is_nfo_scan_configured', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._execute_nfo_scan', 
                   new_callable=AsyncMock, side_effect=Exception("TMDB API error")):
            await perform_nfo_scan_if_needed(progress_service=mock_progress)
        
        mock_progress.fail_progress.assert_called_once()


class TestMediaScanFunctions:
    """Test media scan status and execution."""
    
    @pytest.mark.asyncio
    async def test_check_media_scan_status(self):
        """Test checking media scan status."""
        with patch('src.server.services.initialization_service._check_scan_status', 
                   new_callable=AsyncMock, return_value=True) as mock_check:
            result = await _check_media_scan_status()
        
        assert result is True
        mock_check.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_mark_media_scan_completed(self):
        """Test marking media scan as completed."""
        with patch('src.server.services.initialization_service._mark_scan_completed', 
                   new_callable=AsyncMock) as mock_mark:
            await _mark_media_scan_completed()
        
        mock_mark.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_execute_media_scan(self):
        """Test executing media scan."""
        mock_loader = MagicMock()
        
        with patch('src.server.fastapi_app._check_incomplete_series_on_startup', 
                   new_callable=AsyncMock) as mock_check:
            await _execute_media_scan(mock_loader)
        
        mock_check.assert_called_once_with(mock_loader)


class TestPerformMediaScan:
    """Test complete media scan orchestration."""
    
    @pytest.mark.asyncio
    async def test_media_scan_already_completed(self):
        """Test media scan skips when already completed."""
        mock_loader = MagicMock()
        
        with patch('src.server.services.initialization_service._check_media_scan_status', 
                   new_callable=AsyncMock, return_value=True):
            await perform_media_scan_if_needed(mock_loader)
    
    @pytest.mark.asyncio
    async def test_media_scan_success(self):
        """Test successful media scan execution."""
        mock_loader = MagicMock()
        
        with patch('src.server.services.initialization_service._check_media_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._execute_media_scan', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._mark_media_scan_completed', 
                   new_callable=AsyncMock) as mock_mark:
            await perform_media_scan_if_needed(mock_loader)
        
        mock_mark.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_media_scan_exception_handling(self):
        """Test media scan handles exceptions gracefully."""
        mock_loader = MagicMock()
        
        with patch('src.server.services.initialization_service._check_media_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._execute_media_scan', 
                   new_callable=AsyncMock, side_effect=Exception("Media scan error")):
            # Should not raise exception
            await perform_media_scan_if_needed(mock_loader)


class TestInitializationIntegration:
    """Test integration scenarios for initialization service."""
    
    @pytest.mark.asyncio
    async def test_full_initialization_sequence(self):
        """Test complete initialization sequence."""
        mock_progress = AsyncMock()
        mock_loader = MagicMock()
        
        # Initial setup
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, return_value=10), \
             patch('src.server.services.initialization_service._mark_initial_scan_completed', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._load_series_into_memory', 
                   new_callable=AsyncMock):
            result = await perform_initial_setup(progress_service=mock_progress)
        
        assert result is True
        
        # NFO scan
        with patch('src.server.services.initialization_service._check_nfo_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._is_nfo_scan_configured', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._execute_nfo_scan', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._mark_nfo_scan_completed', 
                   new_callable=AsyncMock):
            await perform_nfo_scan_if_needed(progress_service=mock_progress)
        
        # Media scan
        with patch('src.server.services.initialization_service._check_media_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._execute_media_scan', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._mark_media_scan_completed', 
                   new_callable=AsyncMock):
            await perform_media_scan_if_needed(mock_loader)
    
    @pytest.mark.asyncio
    async def test_partial_initialization_recovery(self):
        """Test recovery from partial initialization."""
        # Simulate initial scan failed
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, side_effect=OSError("Disk full")):
            result = await perform_initial_setup()
        
        assert result is False
        
        # Retry should work
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, return_value=5), \
             patch('src.server.services.initialization_service._mark_initial_scan_completed', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._load_series_into_memory', 
                   new_callable=AsyncMock):
            result = await perform_initial_setup()
        
        assert result is True
    
    @pytest.mark.asyncio
    async def test_idempotent_initialization(self):
        """Test initialization is idempotent."""
        # First run
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=False), \
             patch('src.server.services.initialization_service._validate_anime_directory', 
                   new_callable=AsyncMock, return_value=True), \
             patch('src.server.services.initialization_service._sync_anime_folders', 
                   new_callable=AsyncMock, return_value=5), \
             patch('src.server.services.initialization_service._mark_initial_scan_completed', 
                   new_callable=AsyncMock), \
             patch('src.server.services.initialization_service._load_series_into_memory', 
                   new_callable=AsyncMock):
            result1 = await perform_initial_setup()
        
        assert result1 is True
        
        # Second run should skip
        with patch('src.server.services.initialization_service._check_initial_scan_status', 
                   new_callable=AsyncMock, return_value=True):
            result2 = await perform_initial_setup()
        
        assert result2 is False


class TestPerformNfoRepairScan:
    """Tests for the perform_nfo_repair_scan startup hook."""

    @pytest.mark.asyncio
    async def test_skips_without_tmdb_api_key(self, tmp_path):
        """Should return immediately when no TMDB API key is configured."""
        mock_settings = MagicMock()
        mock_settings.tmdb_api_key = ""
        mock_settings.anime_directory = str(tmp_path)

        with patch(
            "src.server.services.folder_scan_service._settings", mock_settings
        ):
            await perform_nfo_repair_scan()

        # No exception means guard worked — nothing was iterated

    @pytest.mark.asyncio
    async def test_skips_without_anime_directory(self, tmp_path):
        """Should return immediately when no anime directory is configured."""
        mock_settings = MagicMock()
        mock_settings.tmdb_api_key = "some-key"
        mock_settings.anime_directory = ""

        with patch(
            "src.server.services.folder_scan_service._settings", mock_settings
        ):
            await perform_nfo_repair_scan()

    @pytest.mark.asyncio
    async def test_queues_deficient_series_as_asyncio_task(self, tmp_path):
        """Series with incomplete NFO should be scheduled via asyncio.create_task."""
        series_dir = tmp_path / "MyAnime"
        series_dir.mkdir()
        nfo_file = series_dir / "tvshow.nfo"
        nfo_file.write_text("<tvshow><title>MyAnime</title></tvshow>")

        mock_settings = MagicMock()
        mock_settings.tmdb_api_key = "test-key"
        mock_settings.anime_directory = str(tmp_path)

        mock_repair_service = AsyncMock()
        mock_repair_service.repair_series = AsyncMock(return_value=True)

        with patch(
            "src.server.services.folder_scan_service._settings", mock_settings
        ), patch(
            "src.core.services.nfo_repair_service.nfo_needs_repair",
            return_value=True,
        ), patch(
            "src.core.services.nfo_factory.NFOServiceFactory"
        ) as mock_factory_cls, patch(
            "src.core.services.nfo_repair_service.NfoRepairService",
            return_value=mock_repair_service,
        ), patch(
            "asyncio.create_task"
        ) as mock_create_task:
            mock_factory_cls.return_value.create.return_value = MagicMock()
            await perform_nfo_repair_scan(background_loader=AsyncMock())

        mock_create_task.assert_called_once()

    @pytest.mark.asyncio
    async def test_skips_complete_series(self, tmp_path):
        """Series with complete NFO should not be scheduled for repair."""
        series_dir = tmp_path / "CompleteAnime"
        series_dir.mkdir()
        nfo_file = series_dir / "tvshow.nfo"
        nfo_file.write_text("<tvshow><title>CompleteAnime</title></tvshow>")

        mock_settings = MagicMock()
        mock_settings.tmdb_api_key = "test-key"
        mock_settings.anime_directory = str(tmp_path)

        with patch(
            "src.server.services.folder_scan_service._settings", mock_settings
        ), patch(
            "src.core.services.nfo_repair_service.nfo_needs_repair",
            return_value=False,
        ), patch(
            "src.core.services.nfo_factory.NFOServiceFactory"
        ) as mock_factory_cls, patch(
            "asyncio.create_task"
        ) as mock_create_task:
            mock_factory_cls.return_value.create.return_value = MagicMock()
            await perform_nfo_repair_scan(background_loader=AsyncMock())

        mock_create_task.assert_not_called()

    @pytest.mark.asyncio
    async def test_repairs_via_asyncio_task_without_background_loader(self, tmp_path):
        """When no background_loader provided, repair is still scheduled via asyncio.create_task."""
        series_dir = tmp_path / "NeedsRepair"
        series_dir.mkdir()
        nfo_file = series_dir / "tvshow.nfo"
        nfo_file.write_text("<tvshow><title>NeedsRepair</title></tvshow>")

        mock_settings = MagicMock()
        mock_settings.tmdb_api_key = "test-key"
        mock_settings.anime_directory = str(tmp_path)

        mock_repair_service = AsyncMock()
        mock_repair_service.repair_series = AsyncMock(return_value=True)

        with patch(
            "src.server.services.folder_scan_service._settings", mock_settings
        ), patch(
            "src.core.services.nfo_repair_service.nfo_needs_repair",
            return_value=True,
        ), patch(
            "src.core.services.nfo_factory.NFOServiceFactory"
        ) as mock_factory_cls, patch(
            "src.core.services.nfo_repair_service.NfoRepairService",
            return_value=mock_repair_service,
        ), patch(
            "asyncio.create_task"
        ) as mock_create_task:
            mock_factory_cls.return_value.create.return_value = MagicMock()
            await perform_nfo_repair_scan(background_loader=None)

        mock_create_task.assert_called_once()
