"""
Comprehensive test for OpenAI API key configuration and usage.

This test specifically verifies that OpenAI API keys can be:
1. Configured through settings
2. Properly passed to the OpenAI client
3. Used for actual research operations
"""

import pytest
from unittest.mock import Mock, patch
import os

from local_deep_research.config.llm_config import get_llm


@pytest.mark.requires_llm
class TestOpenAIAPIKeyUsage:
    """Test OpenAI API key configuration and usage throughout the system."""

    @pytest.fixture
    def openai_settings_snapshot(self):
        """Create settings snapshot with OpenAI configuration.

        Uses simplified format (raw values) to bypass get_typed_setting_value
        type coercion in get_setting_from_snapshot().
        """
        return {
            "search.tool": "searxng",
            "llm.provider": "openai",
            "llm.model": "gpt-3.5-turbo",
            "llm.temperature": 0.7,
            "llm.openai.api_key": "sk-test-1234567890abcdef",
            "llm.openai.api_base": None,
            "llm.openai.organization": None,
            "llm.streaming": None,
            "llm.max_retries": None,
            "llm.request_timeout": None,
            "llm.context_window_unrestricted": False,
            "llm.context_window_size": 128000,
            "llm.supports_max_tokens": True,
            "llm.max_tokens": 100000,
            "llm.provider.openai.context_window": 4096,
            "research.iterations": 2,
            "research.questions_per_iteration": 3,
            "research.search_engines": ["wikipedia"],
            "research.local_context": 2000,
            "research.web_context": 2000,
            "rate_limiting.llm_enabled": False,
        }

    def test_openai_api_key_in_llm_config(self, openai_settings_snapshot):
        """Test that OpenAI API key is properly passed to ChatOpenAI."""
        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            # Mock the LLM instance
            mock_llm_instance = Mock()
            mock_openai.return_value = mock_llm_instance

            # Get LLM with settings
            get_llm(settings_snapshot=openai_settings_snapshot)

            # Verify ChatOpenAI was called with correct API key
            mock_openai.assert_called_once()
            call_args = mock_openai.call_args

            # Check that API key was passed
            assert call_args is not None
            assert len(call_args) > 1
            assert call_args[1]["api_key"] == "sk-test-1234567890abcdef"
            assert call_args[1]["model"] == "gpt-3.5-turbo"
            assert call_args[1]["temperature"] == 0.7

    @pytest.mark.skipif(
        os.environ.get("CI") == "true"
        or os.environ.get("GITHUB_ACTIONS") == "true",
        reason="Skipped in CI - requires environment variable configuration",
    )
    def test_openai_api_key_from_environment(self, openai_settings_snapshot):
        """Test fallback to environment variable if API key not in settings."""
        # Modify settings to have no API key
        settings_no_key = openai_settings_snapshot.copy()
        settings_no_key["llm.openai.api_key"] = None

        # Set LDR-prefixed environment variable
        with patch.dict(
            os.environ, {"LDR_LLM_OPENAI_API_KEY": "sk-env-test-key"}
        ):
            with patch(
                "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
            ) as mock_openai:
                mock_llm_instance = Mock()
                mock_openai.return_value = mock_llm_instance

                get_llm(settings_snapshot=settings_no_key)

                # Should use environment variable
                call_args = mock_openai.call_args
                assert call_args is not None
                assert len(call_args) > 1
                assert call_args[1]["api_key"] == "sk-env-test-key"

    def test_openai_api_key_in_research_flow(self, openai_settings_snapshot):
        """Test that API key is properly passed through research flow to OpenAI."""
        # Mock the ChatOpenAI class to verify API key is passed
        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            # Create a mock LLM instance
            mock_llm = Mock()
            mock_openai.return_value = mock_llm

            # Simply get the LLM to verify the API key is passed
            from local_deep_research.config.llm_config import get_llm

            llm = get_llm(settings_snapshot=openai_settings_snapshot)

            # Verify API key was passed correctly
            assert mock_openai.called
            assert (
                mock_openai.call_args[1]["api_key"]
                == "sk-test-1234567890abcdef"
            )
            assert mock_openai.call_args[1]["model"] == "gpt-3.5-turbo"
            assert llm is not None

    def test_openai_with_custom_endpoint(self, openai_settings_snapshot):
        """Test OpenAI with custom API endpoint (e.g., Azure OpenAI)."""
        # Add custom endpoint to settings
        custom_settings = openai_settings_snapshot.copy()
        custom_settings["llm.openai.api_base"] = (
            "https://custom-openai.azure.com"
        )
        custom_settings["llm.openai.api_key"] = "custom-azure-key"

        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            mock_llm_instance = Mock()
            mock_openai.return_value = mock_llm_instance

            get_llm(settings_snapshot=custom_settings)

            # Verify custom endpoint and key were used
            call_args = mock_openai.call_args
            assert call_args is not None
            assert len(call_args) > 1
            assert call_args[1]["api_key"] == "custom-azure-key"
            assert (
                call_args[1].get("openai_api_base")
                == "https://custom-openai.azure.com"
            )

    def test_openai_error_handling_invalid_key(self, openai_settings_snapshot):
        """Test error handling when OpenAI API key is invalid."""
        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            # Simulate OpenAI authentication error
            mock_openai.side_effect = Exception("Invalid API key provided")

            # Should raise exception with clear message
            with pytest.raises(Exception, match="Invalid API key"):
                get_llm(settings_snapshot=openai_settings_snapshot)

    def test_openai_model_selection(self, openai_settings_snapshot):
        """Test different OpenAI model selections."""
        models_to_test = [
            "gpt-4",
            "gpt-4-turbo-preview",
            "gpt-3.5-turbo-16k",
            "gpt-4-1106-preview",
        ]

        for model in models_to_test:
            settings = openai_settings_snapshot.copy()
            settings["llm.model"] = model

            with patch(
                "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
            ) as mock_openai:
                mock_llm_instance = Mock()
                mock_openai.return_value = mock_llm_instance

                get_llm(settings_snapshot=settings)

                # Verify correct model was selected
                assert mock_openai.call_args[1]["model"] == model

    def test_openai_streaming_configuration(self, openai_settings_snapshot):
        """Test OpenAI streaming configuration."""
        # Add streaming setting
        streaming_settings = openai_settings_snapshot.copy()
        streaming_settings["llm.streaming"] = True

        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            mock_llm_instance = Mock()
            mock_openai.return_value = mock_llm_instance

            get_llm(settings_snapshot=streaming_settings)

            # Verify streaming was enabled
            assert mock_openai.call_args[1].get("streaming") is True

    def test_openai_retry_configuration(self, openai_settings_snapshot):
        """Test OpenAI retry and timeout configuration."""
        # Add retry settings
        retry_settings = openai_settings_snapshot.copy()
        retry_settings["llm.max_retries"] = 3
        retry_settings["llm.request_timeout"] = 60

        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            mock_llm_instance = Mock()
            mock_openai.return_value = mock_llm_instance

            get_llm(settings_snapshot=retry_settings)

            # Verify retry configuration
            call_args = mock_openai.call_args
            assert call_args[1].get("max_retries") == 3
            assert call_args[1].get("request_timeout") == 60

    def test_openai_organization_id(self, openai_settings_snapshot):
        """Test OpenAI organization ID configuration."""
        # Add organization ID
        org_settings = openai_settings_snapshot.copy()
        org_settings["llm.openai.organization"] = "org-test123"

        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            mock_llm_instance = Mock()
            mock_openai.return_value = mock_llm_instance

            get_llm(settings_snapshot=org_settings)

            # Verify organization ID was passed
            assert (
                mock_openai.call_args[1].get("openai_organization")
                == "org-test123"
            )

    def test_openai_full_integration(self, openai_settings_snapshot):
        """Full integration test verifying all OpenAI configuration parameters."""
        # Test full OpenAI configuration
        full_settings = openai_settings_snapshot.copy()
        full_settings["llm.streaming"] = True
        full_settings["llm.max_retries"] = 5
        full_settings["llm.request_timeout"] = 120
        full_settings["llm.openai.organization"] = "org-123"

        with patch(
            "local_deep_research.llm.providers.implementations.openai.ChatOpenAI"
        ) as mock_openai:
            # Create mock LLM
            mock_llm = Mock()
            mock_openai.return_value = mock_llm

            from local_deep_research.config.llm_config import get_llm

            # Get LLM with full configuration
            get_llm(settings_snapshot=full_settings)

            # Verify all parameters were passed correctly
            assert mock_openai.called
            call_kwargs = mock_openai.call_args[1]

            # Core parameters
            assert call_kwargs["api_key"] == "sk-test-1234567890abcdef"
            assert call_kwargs["model"] == "gpt-3.5-turbo"
            assert call_kwargs["temperature"] == 0.7

            # Additional parameters
            assert call_kwargs["streaming"] is True
            assert call_kwargs["max_retries"] == 5
            assert call_kwargs["request_timeout"] == 120
            assert call_kwargs["openai_organization"] == "org-123"

            # Verify max_tokens was set
            assert "max_tokens" in call_kwargs
            assert call_kwargs["max_tokens"] > 0
