"""
Tests for uncovered code paths in OpenAICompatibleProvider.

Targets:
- create_llm: no api_key_setting (dummy key), max_tokens/streaming/retries/timeout from settings
- create_llm: NoSettingsContextError catch paths for optional params
- create_llm: base_url normalization, default model fallback
"""

from unittest.mock import Mock, patch

import pytest

from local_deep_research.llm.providers.openai_base import (
    OpenAICompatibleProvider,
)

MODULE = "local_deep_research.llm.providers.openai_base"


class TestCreateLlmApiKeyHandling:
    @patch(f"{MODULE}.ChatOpenAI")
    @patch(f"{MODULE}.get_setting_from_snapshot")
    def test_no_api_key_setting_uses_dummy(self, mock_get_setting, mock_chat):
        """Provider without api_key_setting uses dummy key."""
        mock_get_setting.return_value = None

        class NoKeyProvider(OpenAICompatibleProvider):
            provider_name = "local_test"
            api_key_setting = None  # No API key needed
            default_base_url = "http://localhost:8080/v1"
            default_model = "local-model"

        mock_chat.return_value = Mock()

        NoKeyProvider.create_llm(model_name="test-model")

        call_kwargs = mock_chat.call_args[1]
        assert call_kwargs["api_key"] == "dummy-key"

    @patch(f"{MODULE}.ChatOpenAI")
    @patch(f"{MODULE}.get_setting_from_snapshot")
    def test_missing_api_key_raises(self, mock_get_setting, mock_chat):
        """Raises ValueError when required API key is missing."""
        mock_get_setting.return_value = None

        with pytest.raises(ValueError, match="API key not configured"):
            OpenAICompatibleProvider.create_llm(
                settings_snapshot={"some": "settings"}
            )

    @patch(f"{MODULE}.ChatOpenAI")
    @patch(f"{MODULE}.get_setting_from_snapshot")
    def test_api_key_from_settings(self, mock_get_setting, mock_chat):
        """API key is read from settings snapshot."""

        def side_effect(key, default=None, settings_snapshot=None):
            if "api_key" in key:
                return "sk-test-key"
            return default

        mock_get_setting.side_effect = side_effect
        mock_chat.return_value = Mock()

        OpenAICompatibleProvider.create_llm(
            settings_snapshot={"llm.openai_endpoint.api_key": "sk-test-key"}
        )

        call_kwargs = mock_chat.call_args[1]
        assert call_kwargs["api_key"] == "sk-test-key"


def _api_key_side_effect(key, default=None, settings_snapshot=None):
    """Return API key for api_key settings, None for everything else."""
    if "api_key" in key:
        return "sk-key"
    return default


class TestCreateLlmModelAndUrl:
    @patch(f"{MODULE}.ChatOpenAI")
    @patch(
        f"{MODULE}.get_setting_from_snapshot", side_effect=_api_key_side_effect
    )
    def test_default_model_used(self, mock_get_setting, mock_chat):
        """Uses default_model when none specified."""
        mock_chat.return_value = Mock()

        OpenAICompatibleProvider.create_llm(
            model_name=None,
            settings_snapshot={},
        )

        call_kwargs = mock_chat.call_args[1]
        assert call_kwargs["model"] == "gpt-3.5-turbo"

    @patch(f"{MODULE}.ChatOpenAI")
    @patch(
        f"{MODULE}.get_setting_from_snapshot", side_effect=_api_key_side_effect
    )
    def test_custom_base_url(self, mock_get_setting, mock_chat):
        """Custom base_url is normalized and used."""
        mock_chat.return_value = Mock()

        OpenAICompatibleProvider.create_llm(
            model_name="gpt-4",
            base_url="http://localhost:1234/v1",
            settings_snapshot={},
        )

        call_kwargs = mock_chat.call_args[1]
        assert "localhost:1234" in call_kwargs["base_url"]


class TestCreateLlmOptionalParams:
    @patch(f"{MODULE}.ChatOpenAI")
    @patch(f"{MODULE}.get_setting_from_snapshot")
    def test_max_tokens_from_settings(self, mock_get_setting, mock_chat):
        """max_tokens is read from settings and passed to ChatOpenAI."""

        def side_effect(key, default=None, settings_snapshot=None):
            if key == "llm.openai_endpoint.api_key":
                return "sk-key"
            if key == "llm.max_tokens":
                return 2048
            return default

        mock_get_setting.side_effect = side_effect
        mock_chat.return_value = Mock()

        OpenAICompatibleProvider.create_llm(settings_snapshot={})

        call_kwargs = mock_chat.call_args[1]
        assert call_kwargs["max_tokens"] == 2048

    @patch(f"{MODULE}.ChatOpenAI")
    @patch(f"{MODULE}.get_setting_from_snapshot")
    def test_streaming_from_settings(self, mock_get_setting, mock_chat):
        """streaming is read from settings."""

        def side_effect(key, default=None, settings_snapshot=None):
            if key == "llm.openai_endpoint.api_key":
                return "sk-key"
            if key == "llm.streaming":
                return True
            return default

        mock_get_setting.side_effect = side_effect
        mock_chat.return_value = Mock()

        OpenAICompatibleProvider.create_llm(settings_snapshot={})

        call_kwargs = mock_chat.call_args[1]
        assert call_kwargs["streaming"] is True

    @patch(f"{MODULE}.ChatOpenAI")
    def test_no_settings_context_error_handled(self, mock_chat):
        """NoSettingsContextError is caught for optional params."""
        from local_deep_research.config.thread_settings import (
            NoSettingsContextError,
        )

        call_count = [0]

        def side_effect(key, default=None, settings_snapshot=None):
            call_count[0] += 1
            if key == "llm.openai_endpoint.api_key":
                return "sk-key"
            raise NoSettingsContextError(f"No context for {key}")

        mock_chat.return_value = Mock()

        with patch(
            f"{MODULE}.get_setting_from_snapshot", side_effect=side_effect
        ):
            # Should not raise despite NoSettingsContextError for optional params
            OpenAICompatibleProvider.create_llm(settings_snapshot={})

        mock_chat.assert_called_once()
