import pytest
import asyncio
import os
from unittest.mock import Mock, patch, AsyncMock
import httpx
from fastapi.testclient import TestClient
from fastapi import FastAPI
from api.v1.ppt.endpoints.images import IMAGES_ROUTER
from models.image_prompt import ImagePrompt
from services.image_generation_service import ImageGenerationService
from models.sql.image_asset import ImageAsset


class TestImageGenerationService:
    """
    Testing the image Generation Service
    """
    
    @pytest.fixture
    def mock_images_directory(self, tmp_path):
        """
        Creates new images directory for every test case we run
        """
        images_dir = tmp_path / "images"
        images_dir.mkdir()
        return str(images_dir)
    
    @pytest.fixture
    def sample_image_prompt(self):
        """
        Creates a sample ImagePrompt for testing
        """
        return ImagePrompt(prompt="A beautiful sunset over mountains")
    
    def test_image_generation_service_initialization(self, mock_images_directory):
        """
        Test initialization of ImageGenerationService with output directory
        - Checks if the output directory is set correctly
        - Checks if the image generation function is set based on environment variable
        """
        with patch.dict(os.environ, {"IMAGE_PROVIDER": "pexels"}):
            service = ImageGenerationService(mock_images_directory)
            assert service.output_directory == mock_images_directory
            assert service.image_gen_func is not None or service.image_gen_func is None
    
    def test_get_image_gen_func_pixabay_selected(self, mock_images_directory):
        """
            Testing the function selection when Pixabay is selected
            - Checks if the correct function is selected based on environment variable
            - Ensures that the function is set to get_image_from_pixabay when Pixabay is selected
        """
        with patch('services.image_generation_service.is_pixabay_selected', return_value=True):
            with patch('services.image_generation_service.is_pixels_selected', return_value=False):
                with patch('services.image_generation_service.is_gemini_flash_selected', return_value=False):
                    with patch('services.image_generation_service.is_dalle3_selected', return_value=False):
                        with patch.dict(os.environ, {"IMAGE_PROVIDER": "pixabay"}):
                            service = ImageGenerationService(mock_images_directory)
                            assert service.image_gen_func == service.get_image_from_pixabay
    
    def test_get_image_gen_func_pexels_selected(self, mock_images_directory):
        """
        Test function selection when Pexels is selected
        - Checks if the correct function is selected based on environment variable
        - Ensures that the function is set to get_image_from_pexels when Pexels is selected
        """
        with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
            with patch('services.image_generation_service.is_pixels_selected', return_value=True):
                with patch('services.image_generation_service.is_gemini_flash_selected', return_value=False):
                    with patch('services.image_generation_service.is_dalle3_selected', return_value=False):
                        with patch.dict(os.environ, {"IMAGE_PROVIDER": "pexels"}):
                            service = ImageGenerationService(mock_images_directory)
                            assert service.image_gen_func == service.get_image_from_pexels
    
    def test_get_image_gen_func_dalle3_selected(self, mock_images_directory):
        """
        Test function selection when DALL-E 3 is selected
        - Checks if the correct function is selected based on environment variable
        - Ensures that the function is set to generate_image_openai when DALL-E 3 is selected
        """
        with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
            with patch('services.image_generation_service.is_pixels_selected', return_value=False):
                with patch('services.image_generation_service.is_gemini_flash_selected', return_value=False):
                    with patch('services.image_generation_service.is_dalle3_selected', return_value=True):
                        with patch.dict(os.environ, {"IMAGE_PROVIDER": "dall-e-3"}):
                            service = ImageGenerationService(mock_images_directory)
                            assert service.image_gen_func == service.generate_image_openai
    
    def test_is_stock_provider_selected(self, mock_images_directory):
        """
        Test if stock provider is selected based on environment variable
        - Checks if the stock provider is selected correctly based on environment variable
        - Ensures that is_stock_provider_selected returns True for Pexels or Pixabay
        """
        with patch('services.image_generation_service.is_pixels_selected', return_value=True):
            with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
                with patch.dict(os.environ, {"IMAGE_PROVIDER": "pexels"}):
                    service = ImageGenerationService(mock_images_directory)
                    assert service.is_stock_provider_selected() is True
        
        with patch('services.image_generation_service.is_pixels_selected', return_value=False):
            with patch('services.image_generation_service.is_pixabay_selected', return_value=True):
                with patch.dict(os.environ, {"IMAGE_PROVIDER": "pixabay"}):
                    service = ImageGenerationService(mock_images_directory)
                    assert service.is_stock_provider_selected() is True
        
        with patch('services.image_generation_service.is_pixels_selected', return_value=False):
            with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
                with patch.dict(os.environ, {"IMAGE_PROVIDER": "dall-e-3"}):
                    service = ImageGenerationService(mock_images_directory)
                    assert service.is_stock_provider_selected() is False
    
    def test_generate_image_with_pexels_success(self, mock_images_directory, sample_image_prompt):
        """
        Test successful image generation with Pexels provider
        - Mocks the Pexels API to return a valid image URL
        - Ensures that the image generation function returns the expected URL
        - Checks if the image generation function is called with the correct prompt
        """
        async def run_test():
            with patch.dict(os.environ, {"IMAGE_PROVIDER": "pexels", "PEXELS_API_KEY": "test_key"}):
                with patch('services.image_generation_service.is_pixels_selected', return_value=True):
                    with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
                        with patch('services.image_generation_service.is_gemini_flash_selected', return_value=False):
                            with patch('services.image_generation_service.is_dalle3_selected', return_value=False):
                                service = ImageGenerationService(mock_images_directory)
                                
                                mock_response = AsyncMock()
                                mock_response.json = AsyncMock(return_value={
                                    "photos": [{
                                        "src": {
                                            "large": "https://example.com/image.jpg"
                                        }
                                    }]
                                })
                                
                                mock_session = AsyncMock()
                                mock_session.get = AsyncMock(return_value=mock_response)
                                mock_session.__aenter__ = AsyncMock(return_value=mock_session)
                                mock_session.__aexit__ = AsyncMock(return_value=None)
                                
                                with patch('aiohttp.ClientSession', return_value=mock_session):
                                    result = await service.generate_image(sample_image_prompt)
                                    assert result == "https://example.com/image.jpg"
        
        asyncio.run(run_test())
    
    def test_generate_image_with_dalle3_success(self, mock_images_directory, sample_image_prompt):
        """
        Test successful image generation with DALL-E 3 provider
        - Mocks the OpenAI client to return a valid image URL
        - Ensures that the image generation function returns the expected URL
        - Checks if the image generation function is called with the correct prompt
        """
        async def run_test():
            with patch.dict(os.environ, {"IMAGE_PROVIDER": "dall-e-3"}):
                with patch('services.image_generation_service.is_pixels_selected', return_value=False):
                    with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
                        with patch('services.image_generation_service.is_gemini_flash_selected', return_value=False):
                            with patch('services.image_generation_service.is_dalle3_selected', return_value=True):
                                service = ImageGenerationService(mock_images_directory)
                                
                                # Create a real test file
                                test_image_path = f"{mock_images_directory}/test_image.jpg"
                                with open(test_image_path, 'w') as f:
                                    f.write("fake image content")
                                
                                # Mock generate_image_openai to return the test file path
                                async def mock_openai_generate(prompt, output_dir):
                                    return test_image_path
                                
                                service.generate_image_openai = mock_openai_generate
                                
                                result = await service.generate_image(sample_image_prompt)
                                
                                # Should return ImageAsset for AI providers
                                assert isinstance(result, ImageAsset)
                                assert result.path == test_image_path
                                assert result.extras["prompt"] == sample_image_prompt.prompt
    
    def test_generate_image_no_provider_selected(self, mock_images_directory, sample_image_prompt):
        """
        Test generate_image when no provider is selected
        - Mocks the environment variable to simulate no provider selected
        - Ensures that the function returns a placeholder image path
        - Checks if the image generation function is called with the correct prompt
        """
        async def run_test():
            with patch('services.image_generation_service.is_pixels_selected', return_value=False):
                with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
                    with patch('services.image_generation_service.is_gemini_flash_selected', return_value=False):
                        with patch('services.image_generation_service.is_dalle3_selected', return_value=False):
                            with patch.dict(os.environ, {"IMAGE_PROVIDER": "pexels"}):
                                service = ImageGenerationService(mock_images_directory)
                                
                                result = await service.generate_image(sample_image_prompt)
                                
                                # Should return placeholder
                                assert result == "/static/images/placeholder.jpg"
        
        asyncio.run(run_test())
    
    def test_generate_image_provider_error(self, mock_images_directory, sample_image_prompt):
        """
        Test generate_image when provider function raises an error
        - Mocks the Pexels API to raise an exception
        - Ensures that the function returns a placeholder image path
        - Checks if the image generation function is called with the correct prompt
        """
        async def run_test():
            with patch('services.image_generation_service.is_pixels_selected', return_value=True):
                with patch('services.image_generation_service.is_pixabay_selected', return_value=False):
                    with patch('services.image_generation_service.is_gemini_flash_selected', return_value=False):
                        with patch('services.image_generation_service.is_dalle3_selected', return_value=False):
                            with patch.dict(os.environ, {"IMAGE_PROVIDER": "pexels"}):
                                service = ImageGenerationService(mock_images_directory)
                                
                                async def mock_pexels_error(*args, **kwargs):
                                    raise Exception("API Error")
                                
                                service.get_image_from_pexels = mock_pexels_error
                                
                                result = await service.generate_image(sample_image_prompt)
                                
                                assert result == "/static/images/placeholder.jpg"
        
        asyncio.run(run_test())
    
    def test_get_image_from_pexels_real_function(self, mock_images_directory):
        """T
        Test REAL Pexels function with mocked HTTP call
        - Mocks the Pexels API to return a valid image URL
        - Ensures that the function returns the expected URL
        - Checks if the HTTP call is made with the correct parameters
        """
        async def run_test():
            with patch.dict(os.environ, {"IMAGE_PROVIDER": "pexels", "PEXELS_API_KEY": "test_pexels_key"}):
                service = ImageGenerationService(mock_images_directory)
                
                mock_response = AsyncMock()
                mock_response.json = AsyncMock(return_value={
                    "photos": [{
                        "src": {
                            "large": "https://example.com/pexels_image.jpg"
                        }
                    }]
                })
                
                mock_session = AsyncMock()
                mock_session.get = AsyncMock(return_value=mock_response)
                mock_session.__aenter__ = AsyncMock(return_value=mock_session)
                mock_session.__aexit__ = AsyncMock(return_value=None)
                
                with patch('aiohttp.ClientSession', return_value=mock_session):
                    result = await service.get_image_from_pexels("sunset")
                    
                    assert result == "https://example.com/pexels_image.jpg"
                    mock_session.get.assert_called_once()
        
        asyncio.run(run_test())
    
    def test_get_image_from_pixabay_real_function(self, mock_images_directory):
        """
        Test REAL Pixabay function with mocked HTTP call
        - Mocks the Pixabay API to return a valid image URL
        - Ensures that the function returns the expected URL
        - Checks if the HTTP call is made with the correct parameters
        """
        async def run_test():
            with patch.dict(os.environ, {"IMAGE_PROVIDER": "pixabay", "PIXABAY_API_KEY": "test_pixabay_key"}):
                service = ImageGenerationService(mock_images_directory)
                
                mock_response = AsyncMock()
                mock_response.json = AsyncMock(return_value={
                    "hits": [{
                        "largeImageURL": "https://example.com/pixabay_image.jpg"
                    }]
                })
                
                mock_session = AsyncMock()
                mock_session.get = AsyncMock(return_value=mock_response)
                mock_session.__aenter__ = AsyncMock(return_value=mock_session)
                mock_session.__aexit__ = AsyncMock(return_value=None)
                
                with patch('aiohttp.ClientSession', return_value=mock_session):
                    result = await service.get_image_from_pixabay("sunset")
                    
                    assert result == "https://example.com/pixabay_image.jpg"
                    mock_session.get.assert_called_once()
        
        asyncio.run(run_test())


class TestImageGenerationEndpoint:
    """
    Testing the Image Generation API Endpoint
    """
    
    @pytest.fixture
    def app(self):
        """Create FastAPI app with the images router"""
        app = FastAPI()
        app.include_router(IMAGES_ROUTER)
        return app
    
    @pytest.fixture
    def client(self, app):
        """Create test client"""
        return TestClient(app)
    
    @pytest.fixture
    def mock_images_directory(self, tmp_path):
        """Mock images directory"""
        images_dir = tmp_path / "images"
        images_dir.mkdir()
        return str(images_dir)
    
    def test_generate_image_endpoint_success_stock_provider(self, client, mock_images_directory):
        """
        Test successful image generation via API endpoint with stock provider
        - Mocks the ImageGenerationService to return a stock image URL
        - Ensures that the endpoint returns the expected URL
        - Checks if the image generation function is called with the correct prompt
        """
        test_prompt = "A beautiful sunset over mountains"
        
        with patch('api.v1.ppt.endpoints.images.get_images_directory', return_value=mock_images_directory):
            with patch('api.v1.ppt.endpoints.images.ImageGenerationService') as mock_service_class:
                mock_service_instance = Mock()
                mock_service_instance.generate_image = AsyncMock(return_value="https://example.com/stock_image.jpg")
                mock_service_class.return_value = mock_service_instance
                response = client.get(f"/images/generate?prompt={test_prompt}")
                assert response.status_code == 200
    
    def test_generate_image_endpoint_success_ai_provider(self, client, mock_images_directory):
        """
        Test successful image generation via API endpoint with AI provider
        - Mocks the ImageGenerationService to return an ImageAsset object
        - Ensures that the endpoint returns the expected ImageAsset object
        - Checks if the image generation function is called with the correct prompt
        """
        test_prompt = "A beautiful sunset over mountains"
        
        test_image_asset = ImageAsset(
            path=f"{mock_images_directory}/test_image.jpg",
            extras={"prompt": test_prompt, "theme_prompt": "professional"}
        )
        
        with patch('api.v1.ppt.endpoints.images.get_images_directory', return_value=mock_images_directory):
            with patch('api.v1.ppt.endpoints.images.ImageGenerationService') as mock_service_class:
                mock_service_instance = Mock()
                mock_service_instance.generate_image = AsyncMock(return_value=test_image_asset)
                mock_service_class.return_value = mock_service_instance
                
                response = client.get(f"/images/generate?prompt={test_prompt}")
                
                assert response.status_code == 200
    
    def test_generate_image_endpoint_placeholder_response(self, client, mock_images_directory):
        """
        Test endpoint returns placeholder image when no provider is selected
        - Mocks the ImageGenerationService to return a placeholder image path
        - Ensures that the endpoint returns the placeholder image path
        - Checks if the image generation function is called with the correct prompt
        """
        test_prompt = "Test prompt"
        
        with patch('api.v1.ppt.endpoints.images.get_images_directory', return_value=mock_images_directory):
            with patch('api.v1.ppt.endpoints.images.ImageGenerationService') as mock_service_class:
                mock_service_instance = Mock()
                mock_service_instance.generate_image = AsyncMock(return_value="/static/images/placeholder.jpg")
                mock_service_class.return_value = mock_service_instance
                
                response = client.get(f"/images/generate?prompt={test_prompt}")
                
                assert response.status_code == 200

    def test_generate_image_endpoint_with_async_client(self, mock_images_directory):
        """
        Test the image generation endpoint using an async client
        - Mocks the ImageGenerationService to return a valid image URL
        - Ensures that the endpoint returns the expected URL
        - Checks if the image generation function is called with the correct prompt
        """
        async def run_test():
            app = FastAPI()
            app.include_router(IMAGES_ROUTER)
            
            transport = httpx.ASGITransport(app=app)
            async with httpx.AsyncClient(transport=transport, base_url="http://test") as ac:
                with patch('api.v1.ppt.endpoints.images.get_images_directory', return_value=mock_images_directory):
                    with patch('api.v1.ppt.endpoints.images.ImageGenerationService') as mock_service_class:
                        mock_service_instance = Mock()
                        mock_service_instance.generate_image = AsyncMock(return_value="https://example.com/image.jpg")
                        mock_service_class.return_value = mock_service_instance
                        
                        response = await ac.get("/images/generate?prompt=test")
                        assert response.status_code == 200
        
        asyncio.run(run_test())

