"""
Tests for API key configuration and LLM execution.
Tests that API keys can be set and used properly for different LLM providers.
"""

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

from local_deep_research.config.llm_config import (
    get_llm,
    is_openai_available,
    is_anthropic_available,
    is_openai_endpoint_available,
)
from local_deep_research.settings import SettingsManager

# Skip all tests in this module when using fallback LLM
pytestmark = pytest.mark.skipif(
    os.environ.get("LDR_USE_FALLBACK_LLM", "") == "true",
    reason="These tests require real LLM providers, not fallback",
)


class TestAPIKeyConfiguration:
    """Test API key configuration for different providers."""

    @pytest.fixture
    def mock_db_session(self):
        """Create a mock database session."""
        session = Mock()
        return session

    @pytest.fixture
    def settings_manager(self, mock_db_session):
        """Create a settings manager with mock session."""
        return SettingsManager(mock_db_session)

    def _get_base_settings(self):
        """Get base settings that all tests need."""
        return {
            "llm.supports_max_tokens": True,
            "llm.max_tokens": 4096,
            "llm.context_window_unrestricted": False,
            "llm.context_window_size": 8192,
            "llm.local_context_window_size": 4096,
            "rate_limiting.llm_enabled": False,
        }

    @pytest.fixture
    def settings_snapshot_with_openai(self):
        """Create a settings snapshot with OpenAI API key."""
        settings = self._get_base_settings()
        settings.update(
            {
                "llm.provider": "openai",
                "llm.model": "gpt-4",
                "llm.temperature": 0.7,
                "llm.openai.api_key": "test-openai-api-key",
            }
        )
        return settings

    @pytest.fixture
    def settings_snapshot_with_anthropic(self):
        """Create a settings snapshot with Anthropic API key."""
        settings = self._get_base_settings()
        settings.update(
            {
                "llm.provider": "anthropic",
                "llm.model": "claude-3-opus-20240229",
                "llm.temperature": 0.7,
                "llm.anthropic.api_key": "test-anthropic-api-key",
                "llm.context_window_size": 200000,
            }
        )
        return settings

    @pytest.fixture
    def settings_snapshot_with_openai_endpoint(self):
        """Create a settings snapshot with OpenAI endpoint API key."""
        settings = self._get_base_settings()
        settings.update(
            {
                "llm.provider": "openai_endpoint",
                "llm.model": "claude-3-opus",
                "llm.temperature": 0.5,
                "llm.openai_endpoint.api_key": "test-openrouter-api-key",
                "llm.openai_endpoint.url": "https://openrouter.ai/api/v1",
                "llm.context_window_unrestricted": True,
                "llm.context_window_size": 128000,
            }
        )
        return settings

    def test_openai_api_key_configuration(self, settings_snapshot_with_openai):
        """Test that OpenAI API key can be configured and used."""
        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            # Create a mock LLM instance
            mock_llm_instance = Mock()
            mock_openai.return_value = mock_llm_instance

            # Get LLM with OpenAI settings
            get_llm(settings_snapshot=settings_snapshot_with_openai)

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

            assert call_args.kwargs["model"] == "gpt-4"
            assert call_args.kwargs["api_key"] == "test-openai-api-key"
            assert call_args.kwargs["temperature"] == 0.7
            assert "max_tokens" in call_args.kwargs

    def test_anthropic_api_key_configuration(
        self, settings_snapshot_with_anthropic
    ):
        """Test that Anthropic API key can be configured and used."""
        with patch(
            "local_deep_research.config.llm_config.ChatAnthropic"
        ) as mock_anthropic:
            # Create a mock LLM instance
            mock_llm_instance = Mock()
            mock_anthropic.return_value = mock_llm_instance

            # Get LLM with Anthropic settings
            get_llm(settings_snapshot=settings_snapshot_with_anthropic)

            # Verify ChatAnthropic was called with correct parameters
            mock_anthropic.assert_called_once()
            call_args = mock_anthropic.call_args

            assert call_args.kwargs["model"] == "claude-3-opus-20240229"
            assert (
                call_args.kwargs["anthropic_api_key"]
                == "test-anthropic-api-key"
            )
            assert call_args.kwargs["temperature"] == 0.7
            assert "max_tokens" in call_args.kwargs

    def test_openai_endpoint_configuration(
        self, settings_snapshot_with_openai_endpoint
    ):
        """Test that OpenAI endpoint (OpenRouter) API key can be configured and used."""
        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            # Create a mock LLM instance
            mock_llm_instance = Mock()
            mock_openai.return_value = mock_llm_instance

            # Get LLM with OpenAI endpoint settings
            get_llm(settings_snapshot=settings_snapshot_with_openai_endpoint)

            # Verify ChatOpenAI was called with correct parameters for endpoint
            mock_openai.assert_called_once()
            call_args = mock_openai.call_args

            assert call_args.kwargs["model"] == "claude-3-opus"
            assert call_args.kwargs["api_key"] == "test-openrouter-api-key"
            assert (
                call_args.kwargs["openai_api_base"]
                == "https://openrouter.ai/api/v1"
            )
            assert call_args.kwargs["temperature"] == 0.5

    def test_llm_execution_with_api_key(self, settings_snapshot_with_openai):
        """Test that LLM can actually be invoked with API key."""
        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            # Create a mock LLM instance with invoke method
            mock_llm_instance = Mock()
            mock_response = Mock()
            mock_response.content = "Test response from OpenAI"
            mock_llm_instance.invoke.return_value = mock_response
            mock_openai.return_value = mock_llm_instance

            # Get LLM and test invocation
            llm = get_llm(settings_snapshot=settings_snapshot_with_openai)
            response = llm.invoke("Test prompt")

            # Verify the LLM was invoked
            mock_llm_instance.invoke.assert_called_once_with("Test prompt")
            assert "Test response from OpenAI" in response.content

    def test_multiple_provider_switching(
        self,
        settings_snapshot_with_openai,
        settings_snapshot_with_anthropic,
        settings_snapshot_with_openai_endpoint,
    ):
        """Test switching between different providers with their API keys."""
        # Test OpenAI
        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            mock_openai.return_value = Mock()
            get_llm(settings_snapshot=settings_snapshot_with_openai)
            assert mock_openai.called
            assert (
                mock_openai.call_args.kwargs["api_key"] == "test-openai-api-key"
            )

        # Test Anthropic
        with patch(
            "local_deep_research.config.llm_config.ChatAnthropic"
        ) as mock_anthropic:
            mock_anthropic.return_value = Mock()
            get_llm(settings_snapshot=settings_snapshot_with_anthropic)
            assert mock_anthropic.called
            assert (
                mock_anthropic.call_args.kwargs["anthropic_api_key"]
                == "test-anthropic-api-key"
            )

        # Test OpenAI Endpoint
        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai_endpoint:
            mock_openai_endpoint.return_value = Mock()
            get_llm(settings_snapshot=settings_snapshot_with_openai_endpoint)
            assert mock_openai_endpoint.called
            assert (
                mock_openai_endpoint.call_args.kwargs["api_key"]
                == "test-openrouter-api-key"
            )
            assert (
                mock_openai_endpoint.call_args.kwargs["openai_api_base"]
                == "https://openrouter.ai/api/v1"
            )

    def test_missing_api_key_fallback(self):
        """Test that missing API key falls back to fallback model."""
        settings_snapshot = self._get_base_settings()
        settings_snapshot.update(
            {
                "llm.provider": "openai",
                "llm.model": "gpt-4",
                "llm.temperature": 0.7,
                "llm.openai.api_key": None,  # No API key
            }
        )

        with patch(
            "local_deep_research.config.llm_config.get_fallback_model"
        ) as mock_fallback:
            mock_fallback.return_value = Mock()

            get_llm(settings_snapshot=settings_snapshot)

            # Should have fallen back to fallback model
            mock_fallback.assert_called_once()

    def test_research_with_api_configured_llm(
        self, settings_snapshot_with_openai_endpoint
    ):
        """Test that research can use LLM with configured API key."""
        from local_deep_research.api.research_functions import quick_summary

        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            # Setup mock LLM
            mock_llm_instance = Mock()
            mock_response = Mock()
            mock_response.content = "Research summary about test topic"
            mock_llm_instance.invoke.return_value = mock_response
            mock_openai.return_value = mock_llm_instance

            # Mock the search system to avoid network calls
            with patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem"
            ) as mock_search_system:
                mock_system_instance = Mock()
                mock_system_instance.analyze_topic.return_value = {
                    "current_knowledge": "Research summary about test topic",
                    "sources": ["https://example.com/test-topic"],
                    "all_links_of_system": ["https://example.com/test-topic"],
                }
                mock_search_system.return_value = mock_system_instance

                # Run research with API-configured LLM
                quick_summary(
                    query="Test research query",
                    settings_snapshot=settings_snapshot_with_openai_endpoint,
                )

                # Verify LLM was created with API key
                assert mock_openai.called
                assert (
                    mock_openai.call_args.kwargs["api_key"]
                    == "test-openrouter-api-key"
                )

    def test_api_availability_checks(self):
        """Test the availability check functions for different providers."""
        # Test OpenAI availability with settings snapshot
        settings_with_openai = {
            "llm.openai.api_key": {"value": "test-openai-key", "type": "str"}
        }
        assert (
            is_openai_available(settings_snapshot=settings_with_openai) is True
        )

        settings_without_openai = {
            "llm.openai.api_key": {"value": None, "type": "str"}
        }
        assert (
            is_openai_available(settings_snapshot=settings_without_openai)
            is False
        )

        # Test Anthropic availability
        settings_with_anthropic = {
            "llm.anthropic.api_key": {
                "value": "test-anthropic-key",
                "type": "str",
            }
        }
        assert (
            is_anthropic_available(settings_snapshot=settings_with_anthropic)
            is True
        )

        settings_without_anthropic = {
            "llm.anthropic.api_key": {"value": None, "type": "str"}
        }
        assert (
            is_anthropic_available(settings_snapshot=settings_without_anthropic)
            is False
        )

        # Test OpenAI endpoint availability
        settings_with_endpoint = {
            "llm.openai_endpoint.api_key": {
                "value": "test-endpoint-key",
                "type": "str",
            }
        }
        assert (
            is_openai_endpoint_available(
                settings_snapshot=settings_with_endpoint
            )
            is True
        )

        settings_without_endpoint = {
            "llm.openai_endpoint.api_key": {"value": None, "type": "str"}
        }
        assert (
            is_openai_endpoint_available(
                settings_snapshot=settings_without_endpoint
            )
            is False
        )


class TestLLMIntegration:
    """Integration tests for LLM execution with real-like scenarios."""

    def _get_base_settings(self):
        """Get base settings that all tests need."""
        return {
            "llm.supports_max_tokens": True,
            "llm.max_tokens": 4096,
            "llm.context_window_unrestricted": False,
            "llm.context_window_size": 8192,
            "llm.local_context_window_size": 4096,
            "rate_limiting.llm_enabled": False,
        }

    def test_llm_with_token_counting(self):
        """Test LLM execution with token counting enabled."""
        settings_snapshot = self._get_base_settings()
        settings_snapshot.update(
            {
                "llm.provider": "openai",
                "llm.model": "gpt-4",
                "llm.temperature": 0.7,
                "llm.openai.api_key": "test-openai-api-key",
            }
        )

        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            # Setup mock LLM with callbacks
            mock_llm_instance = Mock()
            mock_llm_instance.callbacks = []
            mock_response = Mock()
            mock_response.content = "Test response"
            mock_llm_instance.invoke.return_value = mock_response
            mock_openai.return_value = mock_llm_instance

            # Get LLM with research_id for token counting
            get_llm(
                settings_snapshot=settings_snapshot,
                research_id="test-research-123",
                research_context={"phase": "testing"},
            )

            # Verify LLM was created with research_id
            assert mock_openai.called

    def test_llm_error_handling(self):
        """Test LLM error handling when API calls fail."""
        settings_snapshot = self._get_base_settings()
        settings_snapshot.update(
            {
                "llm.provider": "openai",
                "llm.model": "gpt-4",
                "llm.temperature": 0.7,
                "llm.openai.api_key": "test-openai-api-key",
            }
        )

        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            # Setup mock LLM that raises error
            mock_llm_instance = Mock()
            mock_llm_instance.invoke.side_effect = Exception(
                "API rate limit exceeded"
            )
            mock_openai.return_value = mock_llm_instance

            # Get LLM and test error handling
            llm = get_llm(settings_snapshot=settings_snapshot)

            with pytest.raises(Exception) as exc_info:
                llm.invoke("Test prompt")

            assert "API rate limit exceeded" in str(exc_info.value)

    def test_custom_endpoint_url_configuration(self):
        """Test configuring custom endpoint URLs for different providers."""
        settings_snapshot = self._get_base_settings()
        settings_snapshot.update(
            {
                "llm.provider": "openai_endpoint",
                "llm.model": "custom-model",
                "llm.temperature": 0.7,
                "llm.openai_endpoint.api_key": "test-key",
                "llm.openai_endpoint.url": "https://custom-llm-provider.com/v1",
                "llm.max_tokens": 2048,
            }
        )

        with patch(
            "local_deep_research.config.llm_config.ChatOpenAI"
        ) as mock_openai:
            mock_openai.return_value = Mock()

            get_llm(settings_snapshot=settings_snapshot)

            # Verify custom URL was used
            assert (
                mock_openai.call_args.kwargs["openai_api_base"]
                == "https://custom-llm-provider.com/v1"
            )
            assert mock_openai.call_args.kwargs["api_key"] == "test-key"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
