"""
Deep coverage tests for the Ollama LLM provider.

Targets paths not exercised by test_ollama_provider.py:
- _get_auth_headers: api_key=None, settings_snapshot=None → empty dict
- _get_auth_headers: api_key from settings_snapshot is empty string → no header
- is_available: URL configured but safe_get raises unexpected exception
- is_available: safe_get returns non-200 response
- create_llm: model not found in older API format (no "models" key)
- create_llm: context_window_size is None (no num_ctx / max_tokens added)
- list_models_for_api: label ":latest" stripped correctly
- requires_auth_for_models: always returns False
"""

from unittest.mock import Mock, patch

import requests

from local_deep_research.llm.providers.implementations.ollama import (
    OllamaProvider,
)

MODULE = "local_deep_research.llm.providers.implementations.ollama"
GET_SETTING = f"{MODULE}.get_setting_from_snapshot"


# ---------------------------------------------------------------------------
# _get_auth_headers edge cases
# ---------------------------------------------------------------------------


class TestGetAuthHeadersEdgeCases:
    def test_both_none_returns_empty(self):
        headers = OllamaProvider._get_auth_headers(
            api_key=None, settings_snapshot=None
        )
        assert headers == {}

    def test_empty_string_api_key_from_settings_returns_empty(self):
        """When settings return empty string, no Authorization header should be added."""
        with patch(GET_SETTING, return_value=""):
            headers = OllamaProvider._get_auth_headers(
                settings_snapshot={"llm.ollama.api_key": ""}
            )
        assert headers == {}

    def test_explicit_api_key_takes_precedence_over_snapshot(self):
        """Explicit api_key must not be overwritten by settings lookup."""
        with patch(GET_SETTING, return_value="settings-key") as mock_get:
            headers = OllamaProvider._get_auth_headers(
                api_key="explicit-key",
                settings_snapshot={"llm.ollama.api_key": "settings-key"},
            )
        # get_setting_from_snapshot should NOT be called because api_key provided
        mock_get.assert_not_called()
        assert headers["Authorization"] == "Bearer explicit-key"


# ---------------------------------------------------------------------------
# is_available – additional paths
# ---------------------------------------------------------------------------


class TestIsAvailableAdditional:
    def test_unexpected_exception_from_safe_get_returns_false(self):
        with patch(GET_SETTING, return_value="http://localhost:11434"):
            with patch(
                f"{MODULE}.safe_get", side_effect=Exception("unexpected")
            ):
                result = OllamaProvider.is_available()
        assert result is False

    def test_non_200_response_returns_false(self):
        with patch(GET_SETTING, return_value="http://localhost:11434"):
            mock_resp = Mock()
            mock_resp.status_code = 503
            with patch(f"{MODULE}.safe_get", return_value=mock_resp):
                result = OllamaProvider.is_available()
        assert result is False

    def test_request_exception_returns_false(self):
        with patch(GET_SETTING, return_value="http://localhost:11434"):
            with patch(
                f"{MODULE}.safe_get",
                side_effect=requests.exceptions.RequestException("timeout"),
            ):
                result = OllamaProvider.is_available()
        assert result is False


# ---------------------------------------------------------------------------
# create_llm – older API format (no "models" key in response)
# ---------------------------------------------------------------------------


class TestCreateLlmOlderApiFormat:
    def test_create_llm_succeeds_without_preflight(self):
        """create_llm now skips pre-flight checks and creates ChatOllama directly."""

        def _get_setting(key, default=None, *args, **kwargs):
            return {
                "llm.ollama.url": "http://localhost:11434",
                "llm.local_context_window_size": 4096,
                "llm.supports_max_tokens": True,
                "llm.max_tokens": 100000,
            }.get(key, default)

        with patch(GET_SETTING, side_effect=_get_setting):
            with patch(f"{MODULE}.ChatOllama") as mock_chat:
                mock_chat.return_value = Mock()
                OllamaProvider.create_llm(model_name="gemma3:12b")

                mock_chat.assert_called_once()


# ---------------------------------------------------------------------------
# create_llm – context_window_size is None
# ---------------------------------------------------------------------------


class TestCreateLlmRequiresModelName:
    """Direct callers of OllamaProvider.create_llm() must specify a model."""

    def test_raises_when_no_model_name(self):
        import pytest

        def _get_setting(key, default=None, *args, **kwargs):
            return {"llm.ollama.url": "http://localhost:11434"}.get(
                key, default
            )

        with patch(GET_SETTING, side_effect=_get_setting):
            with pytest.raises(ValueError, match="Ollama model not configured"):
                OllamaProvider.create_llm()

    def test_raises_when_model_name_empty_string(self):
        import pytest

        def _get_setting(key, default=None, *args, **kwargs):
            return {"llm.ollama.url": "http://localhost:11434"}.get(
                key, default
            )

        with patch(GET_SETTING, side_effect=_get_setting):
            with pytest.raises(ValueError, match="Ollama model not configured"):
                OllamaProvider.create_llm(model_name="")


class TestCreateLlmContextWindowNone:
    def test_no_num_ctx_when_context_window_none(self):
        def _get_setting(key, default=None, *args, **kwargs):
            mapping = {
                "llm.ollama.url": "http://localhost:11434",
                "llm.local_context_window_size": None,
                "llm.supports_max_tokens": True,
                "llm.max_tokens": 100000,
            }
            return mapping.get(key, default)

        with patch(GET_SETTING, side_effect=_get_setting):
            with patch(f"{MODULE}.ChatOllama") as mock_chat:
                mock_chat.return_value = Mock()
                OllamaProvider.create_llm(model_name="gemma3:12b")

                call_kwargs = mock_chat.call_args[1]
                assert "num_ctx" not in call_kwargs
                assert "max_tokens" not in call_kwargs


# ---------------------------------------------------------------------------
# list_models_for_api – label formatting
# ---------------------------------------------------------------------------


class TestListModelsLabelFormatting:
    def test_latest_tag_stripped_from_label(self):
        with patch(
            "local_deep_research.utilities.llm_utils.fetch_ollama_models"
        ) as mock_fetch:
            mock_fetch.return_value = [
                {"value": "llama2:latest", "label": "llama2:latest"}
            ]

            result = OllamaProvider.list_models_for_api(
                base_url="http://localhost:11434"
            )

        assert len(result) == 1
        # ":latest" should be stripped from the label
        assert ":latest" not in result[0]["label"]

    def test_colon_replaced_with_space_in_label(self):
        with patch(
            "local_deep_research.utilities.llm_utils.fetch_ollama_models"
        ) as mock_fetch:
            mock_fetch.return_value = [
                {"value": "model:7b", "label": "model:7b"}
            ]

            result = OllamaProvider.list_models_for_api(
                base_url="http://localhost:11434"
            )

        assert ":" not in result[0]["label"].replace("(Ollama)", "")


# ---------------------------------------------------------------------------
# requires_auth_for_models
# ---------------------------------------------------------------------------


class TestRequiresAuthForModels:
    def test_always_returns_false(self):
        assert OllamaProvider.requires_auth_for_models() is False
