"""
Comprehensive Integration Tests for Examples

Tests all examples to ensure they:
1. Can be imported without syntax errors
2. Have a main function or entry point
3. Handle expected error conditions gracefully
4. Follow consistent patterns
"""

import os
import sys
import pytest
from unittest.mock import MagicMock, patch, AsyncMock
from pathlib import Path
import inspect
from io import StringIO

# Add src and examples to path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
sys.path.insert(0, str(Path(__file__).parent.parent / "examples"))

# Test utilities
def create_mock_settings(provider="openai", api_key="test-key"):
    """Create mock settings for testing."""
    from ai_utilities import AiSettings
    return AiSettings(provider=provider, api_key=api_key, model="gpt-3.5-turbo")

def create_mock_client():
    """Create a mock AI client."""
    from ai_utilities import AiClient
    client = AiClient(settings=create_mock_settings())
    
    # Mock the ask method
    mock_result = MagicMock()
    mock_result.text = "Test response"
    mock_result.usage = MagicMock()
    mock_result.usage.total_tokens = 10
    client.ask = MagicMock(return_value=mock_result)
    
    return client

def get_example_functions(example_name):
    """Get all callable functions from an example module."""
    try:
        module = __import__(example_name)
        functions = [getattr(module, attr) for attr in dir(module) 
                   if callable(getattr(module, attr)) and not attr.startswith('_')]
        return functions
    except Exception:
        return []


class TestCoreExamples:
    """Test core examples that are most commonly used."""
    
    def test_getting_started_example(self):
        """Test getting_started.py example."""
        import getting_started
        
        # Should have main function
        assert hasattr(getting_started, 'main')
        assert callable(getting_started.main)
        
        # Test structure with mocked client
        with patch('ai_utilities.AiClient') as mock_client_class:
            mock_client = MagicMock()
            mock_result = MagicMock()
            mock_result.text = "Test response"
            mock_result.usage.total_tokens = 10
            mock_client.ask.return_value = mock_result
            mock_client_class.return_value = mock_client
            
            # Should not raise an exception
            try:
                getting_started.main()
            except Exception as e:
                # Expected to fail due to API key, but structure should be valid
                assert "api" in str(e).lower() or "key" in str(e).lower()
    
    def test_files_quickstart_example(self):
        """Test files_quickstart.py example."""
        import files_quickstart
        
        # Should have basic functions
        assert hasattr(files_quickstart, 'basic_example')
        assert hasattr(files_quickstart, 'async_example')
        assert callable(files_quickstart.basic_example)
        
        # Check if async function is properly defined
        assert callable(files_quickstart.async_example)
        assert inspect.iscoroutinefunction(files_quickstart.async_example)
        
        # Test structure with mocked client
        with patch('ai_utilities.AiClient') as mock_client_class:
            mock_client = MagicMock()
            mock_file = MagicMock()
            mock_file.file_id = "test-file-id"
            mock_client.upload_file.return_value = mock_file
            mock_client.download_file.return_value = b"test content"
            mock_client_class.return_value = mock_client
            
            try:
                files_quickstart.basic_example()
            except Exception as e:
                # Expected to fail due to file not found, but structure should be valid
                assert True
    
    def test_audio_quickstart_example(self):
        """Test audio_quickstart.py example."""
        import audio_quickstart
        
        # Should have main function
        assert hasattr(audio_quickstart, 'main')
        assert callable(audio_quickstart.main)
        
        # Test structure with mocked client
        with patch('ai_utilities.AiClient') as mock_client_class:
            mock_client = MagicMock()
            mock_result = MagicMock()
            mock_result.text = "Test transcription"
            mock_client.transcribe_audio.return_value = mock_result
            mock_client_class.return_value = mock_client
            
            try:
                audio_quickstart.main()
            except Exception as e:
                # Expected to fail due to audio file not found, but structure should be valid
                assert True
    
    def test_model_validation_example(self):
        """Test model_validation.py example."""
        import model_validation
        
        # Should have main function
        assert hasattr(model_validation, 'main')
        assert callable(model_validation.main)
    
    def test_main_example(self):
        """Test main.py example."""
        import main
        
        # Should have main function
        assert hasattr(main, 'main')
        assert callable(main.main)


class TestProviderExamples:
    """Test provider-specific examples."""
    
    @pytest.mark.parametrize("example_name", [
        "fastchat_example",
        "text_generation_webui_example", 
        "environment_isolation_example",
    ])
    def test_provider_examples_import_and_structure(self, example_name):
        """Test that provider examples can be imported and have main function."""
        module = __import__(example_name)
        
        # Should have main function
        assert hasattr(module, 'main')
        assert callable(module.main)
        
        # Test structure with mocked client
        with patch('ai_utilities.AiClient') as mock_client_class:
            mock_client = create_mock_client()
            mock_client_class.return_value = mock_client
            
            try:
                module.main()
            except Exception as e:
                # Expected to fail due to local server not running, but structure should be valid
                assert True


class TestFeatureExamples:
    """Test feature-specific examples."""
    
    @pytest.mark.parametrize("example_name", [
        "ask_parameters_demo",
        "usage_tracking_example",
        "simple_document_ai",  # Uses analyze_document instead of main
        "audio_transcription_demo",
        "document_ai_demo",
    ])
    def test_feature_examples_import_and_structure(self, example_name):
        """Test that feature examples can be imported and have main function."""
        module = __import__(example_name)
        
        # Check for main function or equivalent
        if hasattr(module, 'main'):
            entry_func = module.main
        elif hasattr(module, 'analyze_document'):
            entry_func = module.analyze_document
        else:
            pytest.fail(f"{example_name} missing expected entry function")
            
        assert callable(entry_func)
        
        # Test structure with mocked client
        with patch('ai_utilities.AiClient') as mock_client_class:
            mock_client = create_mock_client()
            mock_client_class.return_value = mock_client
            
            try:
                if hasattr(module, 'main'):
                    module.main()
                elif hasattr(module, 'analyze_document'):
                    module.analyze_document()
            except Exception as e:
                # Expected to fail due to API key or missing resources, but structure should be valid
                assert True


class TestAdvancedExamples:
    """Test advanced examples."""
    
    @pytest.mark.parametrize("example_name", [
        "interactive_setup_example",
        "smart_setup_example",
        "setup_examples",
    ])
    def test_setup_examples_import_and_structure(self, example_name):
        """Test that setup examples can be imported and have main function."""
        module = __import__(example_name)
        
        # Should have main function
        assert hasattr(module, 'main')
        assert callable(module.main)
    
    @pytest.mark.parametrize("example_name", [
        "simple_image_generation",  # Uses generate_and_download_image
        "audio_generation_demo",
        "image_generation_demo",
    ])
    def test_generation_examples_import_and_structure(self, example_name):
        """Test that generation examples can be imported and have main function."""
        module = __import__(example_name)
        
        # Check for main function or equivalent
        if hasattr(module, 'main'):
            entry_func = module.main
        elif hasattr(module, 'generate_and_download_image'):
            entry_func = module.generate_and_download_image
        else:
            pytest.fail(f"{example_name} missing expected entry function")
            
        assert callable(entry_func)
        
        # Test structure with mocked client
        with patch('ai_utilities.AiClient') as mock_client_class:
            mock_client = create_mock_client()
            mock_client_class.return_value = mock_client
            
            try:
                if hasattr(module, 'main'):
                    module.main()
                elif hasattr(module, 'generate_and_download_image'):
                    module.generate_and_download_image()
            except Exception as e:
                # Expected to fail due to API key or missing capabilities, but structure should be valid
                assert True
    
    @pytest.mark.parametrize("example_name", [
        "files_demo",
        "complete_content_workflow",
        "knowledge_example",
        "parallel_usage_tracking_example",  # Uses parallel_ai_calls_example
    ])
    def test_workflow_examples_import_and_structure(self, example_name):
        """Test that workflow examples can be imported and have main function."""
        module = __import__(example_name)
        
        # Check for main function or equivalent
        if hasattr(module, 'main'):
            entry_func = module.main
        elif hasattr(module, 'parallel_ai_calls_example'):
            entry_func = module.parallel_ai_calls_example
        else:
            pytest.fail(f"{example_name} missing expected entry function")
            
        assert callable(entry_func)
        
        # Test structure with mocked client
        with patch('ai_utilities.AiClient') as mock_client_class:
            mock_client = create_mock_client()
            mock_client_class.return_value = mock_client
            
            try:
                if hasattr(module, 'main'):
                    module.main()
                elif hasattr(module, 'parallel_ai_calls_example'):
                    module.parallel_ai_calls_example()
            except Exception as e:
                # Expected to fail due to API key or missing resources, but structure should be valid
                assert True


class TestSpecialCases:
    """Test special cases and edge conditions."""
    
    def test_async_examples_have_async_functions(self):
        """Test that examples with async in name have async functions."""
        async_examples = [
            "files_quickstart",
            "audio_quickstart",
        ]
        
        for example_name in async_examples:
            module = __import__(example_name)
            functions = get_example_functions(example_name)
            
            # Check if any function is async
            has_async = any(inspect.iscoroutinefunction(func) for func in functions)
            # At least files_quickstart should have async functions
            if example_name == "files_quickstart":
                assert has_async, f"{example_name} should have async functions"


# Test that all examples can be imported without errors
@pytest.mark.parametrize("example_name", [
    "ask_parameters_demo",
    "audio_generation_demo", 
    "audio_quickstart",
    "audio_transcription_demo",
    "complete_content_workflow",
    "document_ai_demo",
    "environment_isolation_example",
    "fastchat_example",
    "files_demo",
    "files_quickstart",
    "getting_started",
    "image_generation_demo",
    "interactive_setup_example",
    "knowledge_example",
    "parallel_usage_tracking_example",
    "setup_examples",
    "simple_document_ai",
    "simple_image_generation",
    "smart_setup_example",
    "text_generation_webui_example",
    "usage_tracking_example",
    "model_validation",
    "main",
])
def test_all_examples_import(example_name):
    """Test that all examples can be imported without syntax errors."""
    try:
        __import__(example_name)
    except ImportError as e:
        pytest.fail(f"Failed to import {example_name}: {e}")
    except Exception as e:
        pytest.fail(f"Unexpected error importing {example_name}: {e}")


# Test that all examples have main function or equivalent entry point
@pytest.mark.parametrize("example_name,expected_functions", [
    ("ask_parameters_demo", ["main"]),
    ("audio_generation_demo", ["main"]), 
    ("audio_quickstart", ["main"]),
    ("audio_transcription_demo", ["main"]),
    ("complete_content_workflow", ["main"]),
    ("document_ai_demo", ["main"]),
    ("environment_isolation_example", ["main"]),
    ("fastchat_example", ["main"]),
    ("files_demo", ["main"]),
    ("getting_started", ["main"]),
    ("image_generation_demo", ["main"]),
    ("interactive_setup_example", ["main"]),
    ("knowledge_example", ["main"]),
    ("setup_examples", ["main"]),
    ("smart_setup_example", ["main"]),
    ("text_generation_webui_example", ["main"]),
    ("usage_tracking_example", ["main"]),
    ("model_validation", ["main"]),
    ("main", ["main"]),
    # Examples with different function patterns
    ("files_quickstart", ["basic_example", "async_example"]),
    ("simple_document_ai", ["analyze_document"]),
    ("simple_image_generation", ["generate_and_download_image"]),
    ("parallel_usage_tracking_example", ["parallel_ai_calls_example", "concurrent_stress_test"]),
])
def test_examples_have_entry_functions(example_name, expected_functions):
    """Test that all examples have expected entry point functions."""
    module = __import__(example_name)
    
    for func_name in expected_functions:
        assert hasattr(module, func_name), f"{example_name} missing function: {func_name}"
        assert callable(getattr(module, func_name)), f"{example_name}.{func_name} is not callable"


# Test examples with special function patterns
@pytest.mark.parametrize("example_name,expected_functions", [
    ("files_quickstart", ["basic_example", "async_example", "error_handling_example"]),
])
def test_examples_have_special_functions(example_name, expected_functions):
    """Test that examples with special function patterns have expected functions."""
    module = __import__(example_name)
    
    for func_name in expected_functions:
        assert hasattr(module, func_name), f"{example_name} missing function: {func_name}"
        assert callable(getattr(module, func_name)), f"{example_name}.{func_name} is not callable"


class TestExampleErrorHandling:
    """Test that examples handle errors gracefully."""
    
    def test_examples_handle_missing_api_keys(self):
        """Test that examples handle missing API keys gracefully."""
        examples_to_test = [
            "getting_started",
            "ask_parameters_demo",
            "usage_tracking_example",
        ]
        
        for example_name in examples_to_test:
            module = __import__(example_name)
            
            # Should either raise an exception or handle it gracefully
            try:
                with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
                    with patch('sys.stderr', new_callable=StringIO) as mock_stderr:
                        # Mock interactive setup to avoid stdin issues
                        with patch('ai_utilities.AiSettings.interactive_setup') as mock_setup:
                            mock_setup.side_effect = Exception("No API key provided")
                            module.main()
                
                # If no exception was raised, check if error was printed
                output = mock_stdout.getvalue() + mock_stderr.getvalue()
                output_lower = output.lower()
                
                # Should mention API key or authentication in the output
                assert any(keyword in output_lower for keyword in ["api", "key", "auth", "token", "error"]), \
                    f"{example_name} should mention API/key/auth/error in output, got: {output}"
                    
            except Exception as e:
                # Should mention API key or authentication in the error
                error_str = str(e).lower()
                assert any(keyword in error_str for keyword in ["api", "key", "auth", "token", "error", "no api key"]), \
                    f"{example_name} should mention API/key/auth/error in error, got: {error_str}"
    
    def test_examples_handle_missing_files(self):
        """Test that file examples handle missing files gracefully."""
        file_examples = [
            "files_quickstart",
            "audio_quickstart",
        ]
        
        for example_name in file_examples:
            module = __import__(example_name)
            
            # Should handle missing files gracefully
            try:
                # Mock interactive setup to avoid stdin issues
                with patch('ai_utilities.AiSettings.interactive_setup') as mock_setup:
                    mock_setup.side_effect = Exception("No API key provided")
                    
                    if hasattr(module, 'basic_example'):
                        module.basic_example()
                    elif hasattr(module, 'main'):
                        module.main()
            except Exception as e:
                # Should be a file-related error or API key error, not a syntax error
                error_str = str(e).lower()
                assert any(keyword in error_str for keyword in ["file", "not found", "no such file", "permission", "api", "key"]), \
                    f"{example_name} should handle file errors gracefully, got: {error_str}"
