"""
Test .env file loading and development environment setup in main.py

Tests the critical functionality for library usage:
- _load_env_file() priority order
- _setup_development_environment() detection
- create_runnable_app() initialization
"""
import os
import sys
import tempfile
import pytest
from pathlib import Path
from unittest.mock import patch, MagicMock, call
# Import functions - these are private but we need to test them
# Use importlib to ensure we get the module, not a function
import importlib
api_main_module = importlib.import_module("aipartnerupflow.api.main")

# Access private functions for testing
_load_env_file = api_main_module._load_env_file
_setup_development_environment = api_main_module._setup_development_environment
create_runnable_app = api_main_module.create_runnable_app


class TestLoadEnvFile:
    """Test _load_env_file() function"""
    
    def test_load_env_file_priority_current_working_directory(self, tmp_path, monkeypatch):
        """Test that current working directory .env is loaded first"""
        # Create .env in current working directory
        cwd_env = tmp_path / ".env"
        cwd_env.write_text("TEST_VAR=cwd_value\n")
        
        # Create .env in a different location (simulating script directory)
        script_dir = tmp_path / "script_dir"
        script_dir.mkdir()
        script_env = script_dir / ".env"
        script_env.write_text("TEST_VAR=script_value\n")
        
        # Change to tmp_path
        monkeypatch.chdir(tmp_path)
        
        # Mock sys.argv to point to script_dir
        with patch("sys.argv", [str(script_dir / "script.py")]):
            # Clear any existing TEST_VAR
            monkeypatch.delenv("TEST_VAR", raising=False)
            
            # Load .env
            _load_env_file()
            
            # Should load from current working directory (priority 1)
            assert os.getenv("TEST_VAR") == "cwd_value"
    
    def test_load_env_file_priority_script_directory(self, tmp_path, monkeypatch):
        """Test that script directory .env is loaded if cwd .env doesn't exist"""
        # No .env in current working directory
        # Create .env in script directory
        script_dir = tmp_path / "script_dir"
        script_dir.mkdir()
        script_env = script_dir / ".env"
        script_env.write_text("TEST_VAR=script_value\n")
        
        # Create actual script file so is_file() check passes
        script_file = script_dir / "script.py"
        script_file.write_text("# test script\n")
        
        # Change to tmp_path (no .env here)
        monkeypatch.chdir(tmp_path)
        
        # Mock sys.argv to point to script_dir
        with patch("sys.argv", [str(script_file)]):
            # Clear any existing TEST_VAR
            monkeypatch.delenv("TEST_VAR", raising=False)
            
            # Load .env
            _load_env_file()
            
            # Should load from script directory (priority 2)
            assert os.getenv("TEST_VAR") == "script_value"
    
    def test_load_env_file_skips_when_dotenv_not_installed(self, tmp_path, monkeypatch):
        """Test that _load_env_file() gracefully handles missing python-dotenv"""
        # This test verifies that the function handles ImportError gracefully
        # Since dotenv is actually installed in the test environment, we test
        # the error handling path by ensuring the function has proper try-except
        # We can't easily mock the import inside the function, so we verify
        # the code structure handles it correctly
        
        # The actual implementation has:
        # try:
        #     from dotenv import load_dotenv
        # except ImportError:
        #     return
        # 
        # So we verify the function exists and can be called without error
        # even if dotenv import would fail (which we can't easily test without
        # actually uninstalling dotenv)
        
        # For now, we verify the function structure is correct by checking
        # it doesn't crash when called normally
        cwd_env = tmp_path / ".env"
        cwd_env.write_text("TEST_VAR=test_value\n")
        
        monkeypatch.chdir(tmp_path)
        monkeypatch.delenv("TEST_VAR", raising=False)
        
        # Function should complete without error
        # (In a real scenario where dotenv is missing, it would return early)
        _load_env_file()
        
        # If dotenv is available, TEST_VAR should be set
        # If not available, it would be None (but we can't test that easily)
        # This test at least verifies the function doesn't crash
        assert True  # Function completed without error
    
    def test_load_env_file_respects_existing_env_vars(self, tmp_path, monkeypatch):
        """Test that _load_env_file() doesn't override existing environment variables"""
        cwd_env = tmp_path / ".env"
        cwd_env.write_text("TEST_VAR=env_file_value\n")
        
        monkeypatch.chdir(tmp_path)
        
        # Set existing env var
        monkeypatch.setenv("TEST_VAR", "existing_value")
        
        # Load .env (override=False)
        _load_env_file()
        
        # Existing value should be preserved
        assert os.getenv("TEST_VAR") == "existing_value"
    
    def test_load_env_file_handles_invalid_paths_gracefully(self, monkeypatch):
        """Test that _load_env_file() handles invalid paths gracefully"""
        # Mock Path operations to raise exceptions
        with patch("pathlib.Path.cwd", side_effect=OSError("Permission denied")):
            # Should not raise exception
            _load_env_file()
            
            # Function should complete without error
            assert True


class TestSetupDevelopmentEnvironment:
    """Test _setup_development_environment() function"""
    
    def test_setup_dev_env_skips_when_installed_as_package(self, monkeypatch):
        """Test that _setup_development_environment() skips when installed as package"""
        # Mock library root to be in site-packages
        with patch("aipartnerupflow.api.main.Path") as mock_path:
            mock_file = MagicMock()
            mock_file.parent.parent.parent.parent = Path("/usr/lib/python3.11/site-packages/aipartnerupflow")
            mock_path.return_value = mock_file
            
            # Mock sys.path operations
            with patch("sys.path") as mock_sys_path:
                with patch("warnings.filterwarnings") as mock_warnings:
                    _setup_development_environment()
                    
                    # Should not modify sys.path or warnings
                    assert not mock_sys_path.insert.called
                    assert not mock_warnings.called
    
    def test_setup_dev_env_runs_in_development_mode(self, monkeypatch):
        """Test that _setup_development_environment() runs in development mode"""
        # Mock library root to be in development directory (not site-packages)
        with patch("aipartnerupflow.api.main.Path") as mock_path:
            mock_file = MagicMock()
            dev_root = Path("/Users/dev/aipartnerupflow")
            mock_file.parent.parent.parent.parent = dev_root
            mock_path.return_value = mock_file
            
            # Mock sys.path
            original_sys_path = sys.path.copy()
            try:
                with patch("warnings.filterwarnings") as mock_warnings:
                    _setup_development_environment()
                    
                    # Should set up warnings filters
                    assert mock_warnings.called
            finally:
                # Restore sys.path
                sys.path[:] = original_sys_path
    
    def test_setup_dev_env_handles_errors_gracefully(self):
        """Test that _setup_development_environment() handles errors gracefully"""
        # Mock Path to raise exception
        with patch("aipartnerupflow.api.main.Path", side_effect=Exception("Error")):
            # Should not raise exception
            _setup_development_environment()
            
            # Function should complete without error
            assert True


class TestCreateRunnableApp:
    """Test create_runnable_app() function"""
    
    @pytest.mark.asyncio
    async def test_create_runnable_app_loads_env_file(self, tmp_path, monkeypatch):
        """Test that create_runnable_app() loads .env file"""
        # Create .env in current working directory
        cwd_env = tmp_path / ".env"
        cwd_env.write_text("TEST_VAR=app_value\n")
        
        monkeypatch.chdir(tmp_path)
        monkeypatch.delenv("TEST_VAR", raising=False)
        
        # Mock create_app_by_protocol to avoid full initialization
        with patch("aipartnerupflow.api.main.create_app_by_protocol") as mock_create:
            mock_app = MagicMock()
            mock_create.return_value = mock_app
            
            # Mock other initialization functions
            with patch("aipartnerupflow.api.main.initialize_extensions"):
                with patch("aipartnerupflow.api.main._load_custom_task_model"):
                    with patch("aipartnerupflow.api.main._setup_development_environment"):
                        app = create_runnable_app(protocol="a2a")
                        
                        # .env should be loaded
                        assert os.getenv("TEST_VAR") == "app_value"
                        assert app is not None
    
    @pytest.mark.asyncio
    async def test_create_runnable_app_calls_setup_dev_env(self, monkeypatch):
        """Test that create_runnable_app() calls _setup_development_environment()"""
        with patch("aipartnerupflow.api.main.create_app_by_protocol") as mock_create:
            mock_app = MagicMock()
            mock_create.return_value = mock_app
            
            with patch("aipartnerupflow.api.main.initialize_extensions"):
                with patch("aipartnerupflow.api.main._load_custom_task_model"):
                    with patch("aipartnerupflow.api.main._load_env_file"):
                        with patch("aipartnerupflow.api.main._setup_development_environment") as mock_setup:
                            app = create_runnable_app(protocol="a2a")
                            
                            # Should call _setup_development_environment
                            assert mock_setup.called
                            assert app is not None

    @pytest.mark.asyncio
    async def test_create_runnable_app_initializes_extensions(self, monkeypatch):
        """Test that create_runnable_app() initializes extensions by default"""
        with patch("aipartnerupflow.api.main.create_app_by_protocol") as mock_create:
            mock_app = MagicMock()
            mock_create.return_value = mock_app
            
            with patch("aipartnerupflow.api.main.initialize_extensions") as mock_init:
                with patch("aipartnerupflow.api.main._load_custom_task_model"):
                    with patch("aipartnerupflow.api.main._load_env_file"):
                        with patch("aipartnerupflow.api.main._setup_development_environment"):
                            app = create_runnable_app(protocol="a2a")
                            
                            # Should initialize extensions by default
                            mock_init.assert_called_once()
                            assert app is not None
    
    @pytest.mark.asyncio
    async def test_create_runnable_app_skips_extension_init_when_disabled(self, monkeypatch):
        """Test that create_runnable_app() can skip extension initialization"""
        with patch("aipartnerupflow.api.main.create_app_by_protocol") as mock_create:
            mock_app = MagicMock()
            mock_create.return_value = mock_app
            
            with patch("aipartnerupflow.api.main.initialize_extensions") as mock_init:
                with patch("aipartnerupflow.api.main._load_custom_task_model"):
                    with patch("aipartnerupflow.api.main._load_env_file"):
                        with patch("aipartnerupflow.api.main._setup_development_environment"):
                            app = create_runnable_app(
                                protocol="a2a",
                                auto_initialize_extensions=False
                            )
                            
                            # Should not initialize extensions
                            mock_init.assert_not_called()
                            assert app is not None
    
    @pytest.mark.asyncio
    async def test_create_runnable_app_passes_kwargs_to_create_app(self, monkeypatch):
        """Test that create_runnable_app() passes kwargs to create_app_by_protocol"""
        with patch("aipartnerupflow.api.main.create_app_by_protocol") as mock_create:
            mock_app = MagicMock()
            mock_create.return_value = mock_app
            
            with patch("aipartnerupflow.api.main.initialize_extensions"):
                with patch("aipartnerupflow.api.main._load_custom_task_model"):
                    with patch("aipartnerupflow.api.main._load_env_file"):
                        with patch("aipartnerupflow.api.main._setup_development_environment"):
                            # Pass custom kwargs
                            custom_routes = [MagicMock()]
                            custom_middleware = [MagicMock()]
                            
                            app = create_runnable_app(
                                protocol="a2a",
                                custom_routes=custom_routes,
                                custom_middleware=custom_middleware,
                            )
                            
                            # Should pass kwargs to create_app_by_protocol
                            mock_create.assert_called_once()
                            call_kwargs = mock_create.call_args[1]
                            assert call_kwargs["protocol"] == "a2a"
                            assert call_kwargs["custom_routes"] == custom_routes
                            assert call_kwargs["custom_middleware"] == custom_middleware
                            assert app is not None

