"""
Tests for LLM config provider instantiation.

Tests cover:
- Provider instantiation for various providers
"""

import pytest


class TestProviderInstantiation:
    """Tests for provider instantiation."""

    def test_anthropic_instantiation_with_api_key(self):
        """Anthropic instantiation with API key."""
        api_key = "sk-ant-test-key"  # pragma: allowlist secret
        model = "claude-3-opus-20240229"

        # Configuration check
        assert api_key is not None
        assert model.startswith("claude")

        params = {
            "model": model,
            "anthropic_api_key": api_key,
            "temperature": 0.7,
        }

        assert "anthropic_api_key" in params
        assert params["model"] == model

    def test_anthropic_instantiation_fallback_env(self):
        """Anthropic falls back to env var."""
        api_key_from_settings = None
        api_key_from_env = "sk-ant-env-key"  # pragma: allowlist secret

        api_key = api_key_from_settings or api_key_from_env

        assert api_key == api_key_from_env

    def test_openai_optional_params_api_base(self):
        """OpenAI accepts custom API base."""
        api_base = "https://custom.openai.com/v1"

        params = {
            "model": "gpt-4",
            "api_key": "sk-test",
        }  # pragma: allowlist secret

        if api_base:
            params["openai_api_base"] = api_base

        assert params["openai_api_base"] == api_base

    def test_openai_optional_params_organization(self):
        """OpenAI accepts organization ID."""
        organization = "org-12345"

        params = {
            "model": "gpt-4",
            "api_key": "sk-test",
        }  # pragma: allowlist secret

        if organization:
            params["openai_organization"] = organization

        assert params["openai_organization"] == organization

    def test_openai_optional_params_streaming(self):
        """OpenAI accepts streaming parameter."""
        streaming = True

        params = {
            "model": "gpt-4",
            "api_key": "sk-test",
        }  # pragma: allowlist secret

        if streaming is not None:
            params["streaming"] = streaming

        assert params["streaming"] is True

    def test_openai_endpoint_url_normalization(self):
        """OpenAI endpoint URL is normalized."""
        urls = [
            ("https://api.example.com/", "https://api.example.com"),
            ("https://api.example.com", "https://api.example.com"),
            ("http://localhost:8000/v1/", "http://localhost:8000/v1"),
        ]

        for raw_url, expected in urls:
            normalized = raw_url.rstrip("/")
            assert normalized == expected

    def test_lmstudio_chat_openai_wrapper(self):
        """LM Studio uses ChatOpenAI wrapper."""
        lmstudio_url = "http://localhost:1234/v1"
        model = "local-model"

        # LM Studio uses fake API key
        params = {
            "model": model,
            "api_key": "lm-studio",  # pragma: allowlist secret
            "base_url": lmstudio_url,
            "temperature": 0.7,
        }

        assert params["api_key"] == "lm-studio"  # pragma: allowlist secret
        assert params["base_url"] == lmstudio_url

    def test_llamacpp_local_mode_path_validation(self):
        """LlamaCpp local mode validates model path."""
        model_path = "/models/llama-2-7b.gguf"

        # Path validation
        is_valid = model_path.endswith(".gguf") or model_path.endswith(".bin")

        assert is_valid

    def test_llamacpp_rejects_invalid_extension(self):
        """LlamaCpp rejects model files without .gguf or .bin extension."""
        import sys
        import tempfile
        from pathlib import Path
        from unittest.mock import patch, MagicMock

        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a file with invalid extension
            bad_model = Path(tmpdir) / "model.txt"
            bad_model.write_text("not a real model")

            # Mock the LlamaCpp import (llama-cpp-python may not be installed)
            mock_llamacpp_module = MagicMock()
            with (
                patch.dict(
                    sys.modules,
                    {"langchain_community.llms.llamacpp": mock_llamacpp_module},
                ),
                patch(
                    "langchain_community.llms.LlamaCpp",
                    mock_llamacpp_module.LlamaCpp,
                    create=True,
                ),
                patch(
                    "local_deep_research.config.llm_config.get_setting_from_snapshot",
                    side_effect=lambda key, *a, **kw: (
                        str(bad_model) if "model_path" in key else None
                    ),
                ),
                patch(
                    "local_deep_research.security.path_validator.PathValidator.validate_model_path",
                    return_value=bad_model,
                ),
                pytest.raises(ValueError, match="Invalid model file extension"),
            ):
                from local_deep_research.config.llm_config import get_llm

                get_llm(provider="llamacpp")

    def test_llamacpp_gpu_layers_config(self):
        """LlamaCpp GPU layers configuration."""
        n_gpu_layers = 35  # Number of layers to offload to GPU

        params = {
            "model_path": "/models/model.gguf",
            "n_gpu_layers": n_gpu_layers,
            "n_batch": 512,
            "f16_kv": True,
        }

        assert params["n_gpu_layers"] == 35
        assert params["n_batch"] == 512
        assert params["f16_kv"] is True


class TestProviderValidation:
    """Tests for provider validation."""

    def test_valid_providers_list(self):
        """VALID_PROVIDERS contains expected providers."""
        from local_deep_research.config.llm_config import VALID_PROVIDERS

        expected = [
            "ollama",
            "openai",
            "anthropic",
            "google",
            "openrouter",
            "openai_endpoint",
            "lmstudio",
            "llamacpp",
            "none",
        ]

        for provider in expected:
            assert provider in VALID_PROVIDERS

    def test_invalid_provider_raises_error(self):
        """Invalid provider raises ValueError."""
        provider = "invalid_provider"
        valid_providers = ["ollama", "openai", "anthropic"]

        if provider not in valid_providers:
            with pytest.raises(ValueError):
                raise ValueError(f"Invalid provider: {provider}")

    def test_provider_name_cleaning(self):
        """Provider name is cleaned of whitespace and quotes."""
        dirty_names = [
            '" ollama "',
            "'openai'",
            "  anthropic  ",
            '"google"',
        ]

        for name in dirty_names:
            cleaned = name.strip().strip("\"'").strip()
            assert cleaned in ["ollama", "openai", "anthropic", "google"]


class TestProviderAvailabilityChecks:
    """Tests for provider availability checks."""

    def test_openai_available_with_key(self):
        """OpenAI available when API key present."""
        api_key = "sk-test"  # pragma: allowlist secret

        is_available = bool(api_key)

        assert is_available

    def test_anthropic_available_with_key(self):
        """Anthropic available when API key present."""
        api_key = "sk-ant-test"  # pragma: allowlist secret

        is_available = bool(api_key)

        assert is_available

    def test_google_delegates_to_provider(self):
        """Google availability check delegates to GoogleProvider."""
        # Google provider has its own is_available method
        google_available = True  # Simulated

        assert google_available is not None

    def test_openrouter_delegates_to_provider(self):
        """OpenRouter availability check delegates to provider."""
        # OpenRouter provider has its own is_available method
        openrouter_available = True  # Simulated

        assert openrouter_available is not None
