"""
Tests for auto_trace() - One-line SDK initialization

TDD Red Phase - These tests define expected behavior.
All tests should FAIL initially because stub returns wrong values.

Critical requirement: auto_trace() must NEVER crash Swisper.
Even if Redis is down, misconfigured, or SDK has bugs - Swisper continues normally.

Test Categories:
- Golden Path: Core initialization flow
- Edge Case: Missing configuration
- Failure Mode: SDK must never crash host app
"""

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


# =============================================================================
# GOLDEN PATH TESTS
# =============================================================================

class TestAutoTraceDisabled:
    """Test behavior when tracing is disabled"""
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_disabled_when_env_not_set(self):
        """
        Business: When SWISPER_STUDIO_ENABLED is not set, tracing should
        not activate. This prevents accidental tracing in environments
        where it's not configured.
        
        Red phase: Stub returns wrong status.
        """
        from swisper_studio_sdk import auto_trace
        
        # Clear environment
        env = {
            'SWISPER_STUDIO_ENABLED': 'false',
        }
        
        with patch.dict(os.environ, env, clear=False):
            status = await auto_trace()
        
        assert status['enabled'] is False, "Should report disabled"
        assert status['initialized'] is False, "Should not initialize"
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_disabled_when_env_empty(self):
        """Test that empty SWISPER_STUDIO_ENABLED means disabled"""
        from swisper_studio_sdk import auto_trace
        
        env = {'SWISPER_STUDIO_ENABLED': ''}
        
        with patch.dict(os.environ, env, clear=False):
            status = await auto_trace()
        
        assert status['enabled'] is False


class TestAutoTraceInitialization:
    """Test successful initialization flow"""
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_calls_patch_langgraph_on_success(self):
        """
        Business: When configuration is valid, auto_trace() should
        automatically patch LangGraph for zero-config tracing.
        This is the core value proposition.
        
        Red phase: Stub doesn't call patch_langgraph.
        """
        from swisper_studio_sdk import auto_trace
        
        env = {
            'SWISPER_STUDIO_ENABLED': 'true',
            'SWISPER_STUDIO_REDIS_URL': 'redis://localhost:6379',
            'SWISPER_STUDIO_PROJECT_ID': 'test-project-123',
        }
        
        mock_safe_init = AsyncMock(return_value={
            'initialized': True,
            'redis': True,
            'write': True,
            'consumer': True,
        })
        
        mock_patch = MagicMock(return_value=True)
        
        with patch.dict(os.environ, env, clear=False):
            with patch('swisper_studio_sdk.safe_initialize', mock_safe_init):
                # Patch at module level where it's imported
                with patch('swisper_studio_sdk.patch_langgraph', mock_patch):
                    status = await auto_trace()
        
        # Verify patch_langgraph was called
        mock_patch.assert_called_once()
        assert status['langgraph_patched'] is True, "Should report LangGraph patched"
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_includes_environment_tag(self):
        """
        Business: When SWISPER_STUDIO_ENVIRONMENT is set, it should be
        included in status for environment tagging (dev/prod/demo).
        
        Red phase: Stub doesn't capture environment.
        """
        from swisper_studio_sdk import auto_trace
        
        env = {
            'SWISPER_STUDIO_ENABLED': 'true',
            'SWISPER_STUDIO_REDIS_URL': 'redis://localhost:6379',
            'SWISPER_STUDIO_PROJECT_ID': 'test-project-123',
            'SWISPER_STUDIO_ENVIRONMENT': 'production',
        }
        
        mock_safe_init = AsyncMock(return_value={'initialized': True})
        mock_patch = MagicMock(return_value=True)
        
        with patch.dict(os.environ, env, clear=False):
            with patch('swisper_studio_sdk.safe_initialize', mock_safe_init):
                with patch('swisper_studio_sdk.tracing.langgraph_patch.patch_langgraph', mock_patch):
                    status = await auto_trace()
        
        assert status.get('environment') == 'production', \
            f"Should include environment tag, got {status.get('environment')}"


# =============================================================================
# EDGE CASE TESTS
# =============================================================================

class TestAutoTraceMissingConfig:
    """Test handling of missing configuration"""
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_returns_error_when_redis_url_missing(self):
        """
        Business: When Redis URL is missing, return clear error status
        instead of crashing. Helps developers diagnose config issues.
        
        Red phase: Stub doesn't validate config.
        """
        from swisper_studio_sdk import auto_trace
        
        env = {
            'SWISPER_STUDIO_ENABLED': 'true',
            'SWISPER_STUDIO_PROJECT_ID': 'test-project-123',
            # No SWISPER_STUDIO_REDIS_URL
        }
        
        # Clear REDIS_URL if it exists
        with patch.dict(os.environ, env, clear=False):
            with patch.dict(os.environ, {'SWISPER_STUDIO_REDIS_URL': ''}, clear=False):
                status = await auto_trace()
        
        assert status.get('error') == 'missing_redis_url', \
            f"Should return missing_redis_url error, got {status.get('error')}"
        assert status['initialized'] is False
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_returns_error_when_project_id_missing(self):
        """
        Business: When Project ID is missing, return clear error status.
        Project ID is critical for connecting traces to the right project.
        
        Red phase: Stub doesn't validate config.
        """
        from swisper_studio_sdk import auto_trace
        
        env = {
            'SWISPER_STUDIO_ENABLED': 'true',
            'SWISPER_STUDIO_REDIS_URL': 'redis://localhost:6379',
            # No SWISPER_STUDIO_PROJECT_ID
        }
        
        with patch.dict(os.environ, env, clear=False):
            with patch.dict(os.environ, {'SWISPER_STUDIO_PROJECT_ID': ''}, clear=False):
                status = await auto_trace()
        
        assert status.get('error') == 'missing_project_id', \
            f"Should return missing_project_id error, got {status.get('error')}"
        assert status['initialized'] is False


# =============================================================================
# FAILURE MODE TESTS (CRITICAL - Swisper Protection)
# =============================================================================

class TestAutoTraceNeverCrashes:
    """
    CRITICAL: auto_trace() must NEVER crash Swisper.
    
    Even if Redis is down, SDK has bugs, or anything else goes wrong,
    Swisper must continue working normally. Tracing is observability,
    not critical path.
    """
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_never_raises_exceptions(self):
        """
        Business: SDK must NEVER crash the host application.
        Even if something goes horribly wrong internally, auto_trace()
        returns a status dict with error info, not an exception.
        
        Red phase: Stub might not catch all exceptions.
        """
        from swisper_studio_sdk import auto_trace
        
        env = {
            'SWISPER_STUDIO_ENABLED': 'true',
            'SWISPER_STUDIO_REDIS_URL': 'redis://localhost:6379',
            'SWISPER_STUDIO_PROJECT_ID': 'test-project-123',
        }
        
        # Mock safe_initialize to raise an exception
        mock_safe_init = AsyncMock(side_effect=Exception("Redis exploded!"))
        
        with patch.dict(os.environ, env, clear=False):
            with patch('swisper_studio_sdk.safe_initialize', mock_safe_init):
                # This should NOT raise - must return status dict
                try:
                    status = await auto_trace()
                except Exception as e:
                    pytest.fail(f"auto_trace() raised exception: {e}")
        
        # Should return error status, not crash
        assert status['initialized'] is False, "Should report not initialized"
        assert 'error' in status or status.get('error') is not None, \
            "Should include error info"
    
    @pytest.mark.unit
    @pytest.mark.asyncio
    async def test_auto_trace_swisper_continues_when_redis_fails(self):
        """
        Business: Even if Redis is completely down, Swisper must work.
        Tracing is optional observability, not critical path.
        
        This is the MOST IMPORTANT test - it validates the core safety guarantee.
        
        Red phase: Stub might not handle Redis failures gracefully.
        """
        from swisper_studio_sdk import auto_trace
        
        env = {
            'SWISPER_STUDIO_ENABLED': 'true',
            'SWISPER_STUDIO_REDIS_URL': 'redis://localhost:6379',
            'SWISPER_STUDIO_PROJECT_ID': 'test-project-123',
        }
        
        # Simulate Redis connection failure
        mock_safe_init = AsyncMock(return_value={
            'initialized': False,
            'redis': False,
            'error': 'Connection refused',
        })
        
        with patch.dict(os.environ, env, clear=False):
            with patch('swisper_studio_sdk.safe_initialize', mock_safe_init):
                # Should NOT raise, should return gracefully
                status = await auto_trace()
        
        # Verify graceful degradation
        assert status['initialized'] is False
        assert status.get('langgraph_patched', False) is False, \
            "Should not patch if Redis failed"
        # Most importantly: we got here without crashing!


# =============================================================================
# EXPORT TESTS
# =============================================================================

class TestAutoTraceExports:
    """Test that auto_trace is properly exported"""
    
    @pytest.mark.unit
    def test_auto_trace_is_exported(self):
        """
        Business: auto_trace() should be easily importable from the SDK.
        
        Red phase: Not in __all__.
        """
        from swisper_studio_sdk import auto_trace
        
        # Should be callable
        assert callable(auto_trace), "auto_trace should be callable"
    
    @pytest.mark.unit
    def test_patch_langgraph_is_exported(self):
        """
        Business: patch_langgraph should be importable for advanced users.
        
        Red phase: Not in __all__.
        """
        from swisper_studio_sdk import patch_langgraph
        
        assert callable(patch_langgraph), "patch_langgraph should be callable"
    
    @pytest.mark.unit
    def test_set_trace_name_is_exported(self):
        """
        Business: set_trace_name should be importable for explicit naming.
        
        Red phase: Not in __all__.
        """
        from swisper_studio_sdk import set_trace_name
        
        assert callable(set_trace_name), "set_trace_name should be callable"
