import pytest
import asyncio
from unittest.mock import patch, MagicMock, AsyncMock

import google.generativeai as genai
from google.api_core import exceptions as google_exceptions

# Modules to test
from app.core import gemini
from app.core.exceptions import (
    OCRServiceUnavailableError,
    OCRServiceConfigError,
    OCRUnexpectedError,
    OCRQuotaExceededError
)

# Default Mock Settings
@pytest.fixture
def mock_gemini_settings():
    settings_mock = MagicMock()
    settings_mock.GEMINI_API_KEY = "test_api_key"
    settings_mock.GEMINI_MODEL_NAME = "gemini-pro-vision"
    settings_mock.GEMINI_SAFETY_SETTINGS = {
        "HARM_CATEGORY_HARASSMENT": "BLOCK_NONE",
        "HARM_CATEGORY_HATE_SPEECH": "BLOCK_NONE",
    }
    settings_mock.GEMINI_GENERATION_CONFIG = {"temperature": 0.7}
    settings_mock.OCR_ITEM_EXTRACTION_PROMPT = "Extract items:"
    return settings_mock

@pytest.fixture
def mock_generative_model_instance():
    model_instance = AsyncMock(spec=genai.GenerativeModel)
    model_instance.generate_content_async = AsyncMock()
    return model_instance

@pytest.fixture
def patch_google_ai_client(mock_generative_model_instance):
    with patch('google.generativeai.GenerativeModel', return_value=mock_generative_model_instance) as mock_generative_model, \
         patch('google.generativeai.configure') as mock_configure:
        yield mock_configure, mock_generative_model, mock_generative_model_instance


# --- Test Gemini Client Initialization (Global Client) ---

# Parametrize to test different scenarios for the global client init
@pytest.mark.parametrize(
    "api_key_present, configure_raises, model_init_raises, expected_error_message_part",
    [
        (True, None, None, None), # Success
        (False, None, None, "GEMINI_API_KEY not configured"), # API key missing
        (True, Exception("Config error"), None, "Failed to initialize Gemini AI client: Config error"), # genai.configure error
        (True, None, Exception("Model init error"), "Failed to initialize Gemini AI client: Model init error"), # GenerativeModel error
    ]
)
@patch('app.core.gemini.genai') # Patch genai within the gemini module
def test_global_gemini_client_initialization(
    mock_genai_module, 
    mock_gemini_settings, 
    api_key_present, 
    configure_raises, 
    model_init_raises, 
    expected_error_message_part
):
    """Tests the global gemini_flash_client initialization logic in app.core.gemini."""
    # We need to reload the module to re-trigger its top-level initialization code.
    # This is a bit tricky. A common pattern is to put init logic in a function.
    # For now, we'll try to simulate it by controlling mocks before module access.

    with patch('app.core.gemini.settings', mock_gemini_settings):
        if not api_key_present:
            mock_gemini_settings.GEMINI_API_KEY = None
        
        mock_genai_module.configure = MagicMock()
        mock_genai_module.GenerativeModel = MagicMock()
        mock_genai_module.types = genai.types # Keep original types
        mock_genai_module.HarmCategory = genai.HarmCategory
        mock_genai_module.HarmBlockThreshold = genai.HarmBlockThreshold

        if configure_raises:
            mock_genai_module.configure.side_effect = configure_raises
        if model_init_raises:
            mock_genai_module.GenerativeModel.side_effect = model_init_raises
        
        # Python modules are singletons. To re-run top-level code, we need to unload and reload.
        # This is generally discouraged. It's better to have an explicit init function.
        # For this test, we'll check the state variables set by the module's import-time code.
        import importlib
        importlib.reload(gemini) # This re-runs the try-except block at the top of gemini.py

        if expected_error_message_part:
            assert gemini.gemini_initialization_error is not None
            assert expected_error_message_part in gemini.gemini_initialization_error
            assert gemini.gemini_flash_client is None
        else:
            assert gemini.gemini_initialization_error is None
            assert gemini.gemini_flash_client is not None
            mock_genai_module.configure.assert_called_once_with(api_key="test_api_key")
            mock_genai_module.GenerativeModel.assert_called_once()
            # Could add more assertions about safety_settings and generation_config here

    # Clean up after reload for other tests
    importlib.reload(gemini)

# --- Test get_gemini_client --- 
# Assuming the global client tests above set the stage for these

@patch('app.core.gemini.gemini_flash_client', new_callable=MagicMock)
@patch('app.core.gemini.gemini_initialization_error', None)
def test_get_gemini_client_success(mock_client_var, mock_error_var):
    mock_client_var.return_value = MagicMock(spec=genai.GenerativeModel) # Simulate an initialized client
    gemini.gemini_flash_client = mock_client_var # Assign the mock
    gemini.gemini_initialization_error = None
    client = gemini.get_gemini_client()
    assert client is not None

@patch('app.core.gemini.gemini_flash_client', None)
@patch('app.core.gemini.gemini_initialization_error', "Test init error")
def test_get_gemini_client_init_error(mock_client_var, mock_error_var):
    gemini.gemini_flash_client = None
    gemini.gemini_initialization_error = "Test init error"
    with pytest.raises(RuntimeError, match="Gemini client could not be initialized: Test init error"):
        gemini.get_gemini_client()

@patch('app.core.gemini.gemini_flash_client', None)
@patch('app.core.gemini.gemini_initialization_error', None) # No init error, but client is None
def test_get_gemini_client_none_client_unknown_issue(mock_client_var, mock_error_var):
    gemini.gemini_flash_client = None
    gemini.gemini_initialization_error = None
    with pytest.raises(RuntimeError, match="Gemini client is not available \(unknown initialization issue\)."):
        gemini.get_gemini_client()


# --- Tests for extract_items_from_image_gemini --- (Simplified for brevity, needs more cases)
@pytest.mark.asyncio
async def test_extract_items_from_image_gemini_success(
    mock_gemini_settings,
    mock_generative_model_instance,
    patch_google_ai_client
):
    mock_response = MagicMock()
    mock_response.text = "Item 1\nItem 2\n  Item 3  \n\nAnother Item"
    mock_candidate = MagicMock()
    mock_candidate.content.parts = [MagicMock(text=mock_response.text)]
    mock_candidate.finish_reason = 'STOP'
    mock_candidate.safety_ratings = []
    mock_response.candidates = [mock_candidate]
    
    mock_generative_model_instance.generate_content_async.return_value = mock_response

    with patch('app.core.gemini.settings', mock_gemini_settings), \
         patch('app.core.gemini.gemini_flash_client', mock_generative_model_instance), \
         patch('app.core.gemini.gemini_initialization_error', None):

        image_bytes = b"dummy_image_bytes"
        mime_type = "image/png"

        items = await gemini.extract_items_from_image_gemini(image_bytes, mime_type)

        mock_generative_model_instance.generate_content_async.assert_called_once_with([
            mock_gemini_settings.OCR_ITEM_EXTRACTION_PROMPT,
            {"mime_type": mime_type, "data": image_bytes}
        ])
        assert items == ["Item 1", "Item 2", "Item 3", "Another Item"]

@pytest.mark.asyncio
async def test_extract_items_from_image_gemini_client_not_init(mock_gemini_settings):
    with patch('app.core.gemini.settings', mock_gemini_settings), \
         patch('app.core.gemini.gemini_flash_client', None), \
         patch('app.core.gemini.gemini_initialization_error', "Initialization failed explicitly"):
        
        image_bytes = b"dummy_image_bytes"
        with pytest.raises(RuntimeError, match="Gemini client could not be initialized: Initialization failed explicitly"):
            await gemini.extract_items_from_image_gemini(image_bytes)

@pytest.mark.asyncio
async def test_extract_items_from_image_gemini_api_quota_error(
    mock_gemini_settings,
    mock_generative_model_instance
):
    mock_generative_model_instance.generate_content_async.side_effect = google_exceptions.ResourceExhausted("Quota exceeded")

    with patch('app.core.gemini.settings', mock_gemini_settings), \
         patch('app.core.gemini.gemini_flash_client', mock_generative_model_instance), \
         patch('app.core.gemini.gemini_initialization_error', None):

        image_bytes = b"dummy_image_bytes"
        with pytest.raises(google_exceptions.ResourceExhausted, match="Quota exceeded"):
            await gemini.extract_items_from_image_gemini(image_bytes)


# --- Tests for GeminiOCRService --- (Example tests, more needed)

@patch('app.core.gemini.genai.configure')
@patch('app.core.gemini.genai.GenerativeModel')
def test_gemini_ocr_service_init_success(MockGenerativeModel, MockConfigure, mock_gemini_settings, mock_generative_model_instance):
    MockGenerativeModel.return_value = mock_generative_model_instance
    with patch('app.core.gemini.settings', mock_gemini_settings):
        service = gemini.GeminiOCRService()
        MockConfigure.assert_called_once_with(api_key=mock_gemini_settings.GEMINI_API_KEY)
        MockGenerativeModel.assert_called_once_with(mock_gemini_settings.GEMINI_MODEL_NAME)
        assert service.model == mock_generative_model_instance
        # Could add assertions for safety_settings and generation_config if they are set directly on model

@patch('app.core.gemini.genai.configure')
@patch('app.core.gemini.genai.GenerativeModel', side_effect=Exception("Init model failed"))
def test_gemini_ocr_service_init_failure(MockGenerativeModel, MockConfigure, mock_gemini_settings):
    with patch('app.core.gemini.settings', mock_gemini_settings):
        with pytest.raises(OCRServiceConfigError):
            gemini.GeminiOCRService()

@pytest.mark.asyncio
async def test_gemini_ocr_service_extract_items_success(
    mock_gemini_settings,
    mock_generative_model_instance
):
    mock_response = MagicMock()
    mock_response.text = "Item 1\nItem 2\n  Item 3  \n\nAnother Item"
    mock_candidate = MagicMock()
    mock_candidate.content.parts = [MagicMock(text=mock_response.text)]
    mock_candidate.finish_reason = 'STOP'
    mock_candidate.safety_ratings = []
    mock_response.candidates = [mock_candidate]
    
    mock_generative_model_instance.generate_content_async.return_value = mock_response

    with patch('app.core.gemini.settings', mock_gemini_settings), \
         patch('app.core.gemini.gemini_flash_client', mock_generative_model_instance), \
         patch('app.core.gemini.gemini_initialization_error', None):

        service = gemini.GeminiOCRService()
        image_bytes = b"dummy_image_bytes"
        mime_type = "image/png"

        items = await service.extract_items(image_bytes, mime_type)

        mock_generative_model_instance.generate_content_async.assert_called_once_with([
            mock_gemini_settings.OCR_ITEM_EXTRACTION_PROMPT,
            {"mime_type": mime_type, "data": image_bytes}
        ])
        assert items == ["Item 1", "Item 2", "Item 3", "Another Item"]

@pytest.mark.asyncio
async def test_gemini_ocr_service_extract_items_quota_error(
    mock_gemini_settings,
    mock_generative_model_instance
):
    mock_generative_model_instance.generate_content_async.side_effect = google_exceptions.ResourceExhausted("Quota exceeded")

    with patch('app.core.gemini.settings', mock_gemini_settings), \
         patch('app.core.gemini.gemini_flash_client', mock_generative_model_instance), \
         patch('app.core.gemini.gemini_initialization_error', None):

        service = gemini.GeminiOCRService()
        image_bytes = b"dummy_image_bytes"

        with pytest.raises(OCRQuotaExceededError):
            await service.extract_items(image_bytes)

@pytest.mark.asyncio
async def test_gemini_ocr_service_extract_items_api_unavailable(
    mock_gemini_settings,
    mock_generative_model_instance
):
    mock_generative_model_instance.generate_content_async.side_effect = google_exceptions.ServiceUnavailable("Service unavailable")

    with patch('app.core.gemini.settings', mock_gemini_settings), \
         patch('app.core.gemini.gemini_flash_client', mock_generative_model_instance), \
         patch('app.core.gemini.gemini_initialization_error', None):

        service = gemini.GeminiOCRService()
        image_bytes = b"dummy_image_bytes"

        with pytest.raises(OCRServiceUnavailableError):
            await service.extract_items(image_bytes)

@pytest.mark.asyncio
async def test_gemini_ocr_service_extract_items_no_text_response(
    mock_gemini_settings,
    mock_generative_model_instance
):
    mock_response = MagicMock()
    mock_response.text = ""
    mock_candidate = MagicMock()
    mock_candidate.content.parts = [MagicMock(text=mock_response.text)]
    mock_candidate.finish_reason = 'STOP'
    mock_candidate.safety_ratings = []
    mock_response.candidates = [mock_candidate]
    
    mock_generative_model_instance.generate_content_async.return_value = mock_response

    with patch('app.core.gemini.settings', mock_gemini_settings), \
         patch('app.core.gemini.gemini_flash_client', mock_generative_model_instance), \
         patch('app.core.gemini.gemini_initialization_error', None):

        service = gemini.GeminiOCRService()
        image_bytes = b"dummy_image_bytes"

        items = await service.extract_items(image_bytes)
        assert items == [] 