"""Unit tests for Pinecone client."""

import os

from unittest.mock import MagicMock

import pytest

from pinecone import Pinecone
from pinecone.client.inference import Inference
from pinecone.index import Index


pytestmark = pytest.mark.unit


class TestPineconeClient:
    """Tests for Pinecone client initialization and configuration."""

    def test_client_requires_api_key_or_env_var(self):
        """Test that api_key is required via parameter or environment variable."""
        # Save original env var if it exists
        original_api_key = os.environ.get("PINECONE_API_KEY")

        try:
            # Clear env var to test failure case
            if "PINECONE_API_KEY" in os.environ:
                del os.environ["PINECONE_API_KEY"]

            # Should fail when neither parameter nor env var is set
            with pytest.raises(
                ValueError,
                match=r"api_key is required\. Provide it as a parameter or set "
                r"the PINECONE_API_KEY environment variable\.",
            ):
                Pinecone()

            # Should fail with empty string
            with pytest.raises(ValueError, match="api_key is required"):
                Pinecone(api_key="")
        finally:
            # Restore original env var
            if original_api_key is not None:
                os.environ["PINECONE_API_KEY"] = original_api_key

    def test_client_uses_env_var_api_key(self):
        """Test that client uses PINECONE_API_KEY environment variable."""
        # Save original env var if it exists
        original_api_key = os.environ.get("PINECONE_API_KEY")

        try:
            # Set env var
            os.environ["PINECONE_API_KEY"] = "env-test-key"

            # Should initialize successfully from env var
            pc = Pinecone()
            assert pc is not None
            assert pc._api_key == "env-test-key"
        finally:
            # Restore original env var
            if original_api_key is not None:
                os.environ["PINECONE_API_KEY"] = original_api_key
            elif "PINECONE_API_KEY" in os.environ:
                del os.environ["PINECONE_API_KEY"]

    def test_client_parameter_overrides_env_var(self):
        """Test that explicit api_key parameter takes precedence over env var."""
        # Save original env var if it exists
        original_api_key = os.environ.get("PINECONE_API_KEY")

        try:
            # Set env var to one value
            os.environ["PINECONE_API_KEY"] = "env-key"

            # Pass different value as parameter
            pc = Pinecone(api_key="param-key")

            # Parameter should take precedence
            assert pc._api_key == "param-key"
        finally:
            # Restore original env var
            if original_api_key is not None:
                os.environ["PINECONE_API_KEY"] = original_api_key
            elif "PINECONE_API_KEY" in os.environ:
                del os.environ["PINECONE_API_KEY"]

    def test_client_initialization(self):
        """Test successful client initialization."""
        pc = Pinecone(api_key="test-key")

        assert pc is not None
        assert repr(pc) == "Pinecone(base_url='https://api.pinecone.io')"

    def test_client_with_custom_base_url(self):
        """Test client with custom base URL."""
        pc = Pinecone(api_key="test-key", base_url="https://custom.pinecone.io")

        assert repr(pc) == "Pinecone(base_url='https://custom.pinecone.io')"

    def test_inference_namespace_access(self):
        """Test that inference namespace is accessible."""
        pc = Pinecone(api_key="test-key")

        assert hasattr(pc, "inference")
        assert isinstance(pc.inference, Inference)

    def test_context_manager(self):
        """Test context manager support."""
        with Pinecone(api_key="test-key") as pc:
            assert pc is not None
            assert hasattr(pc, "inference")

        # After exiting context, client should be closed
        # (HTTP client is closed, but we can't easily verify that in unit test)

    def test_manual_close(self):
        """Test manual close() method."""
        pc = Pinecone(api_key="test-key")
        pc.close()  # Should not raise

    def test_client_can_be_created_multiple_times(self):
        """Test that multiple client instances can be created."""
        pc1 = Pinecone(api_key="key1")
        pc2 = Pinecone(api_key="key2")

        assert pc1 is not pc2
        assert pc1.inference is not pc2.inference

    def test_client_with_custom_timeout(self):
        """Test client with custom timeout."""
        pc = Pinecone(api_key="test-key", timeout=60)

        assert pc is not None
        assert pc._http_client._config.timeout == 60

    def test_client_with_float_timeout(self):
        """Test client with float timeout."""
        pc = Pinecone(api_key="test-key", timeout=45.5)

        assert pc is not None
        assert pc._http_client._config.timeout == 45.5

    def test_client_with_granular_timeout(self):
        """Test client with granular timeout configuration."""
        timeout_config = {"connect": 5, "read": 120, "write": 30, "pool": 10}
        pc = Pinecone(api_key="test-key", timeout=timeout_config)

        assert pc is not None
        assert pc._http_client._config.timeout == timeout_config

    def test_client_with_partial_granular_timeout(self):
        """Test client with partial granular timeout configuration."""
        timeout_config = {"connect": 5, "read": 60}
        pc = Pinecone(api_key="test-key", timeout=timeout_config)

        assert pc is not None
        assert pc._http_client._config.timeout == timeout_config

    def test_client_with_default_timeout(self):
        """Test client uses default timeout when not specified."""
        pc = Pinecone(api_key="test-key")

        assert pc is not None
        assert pc._http_client._config.timeout == 30


class TestPineconeIndexFactory:
    """Tests for the Pinecone.index() factory method."""

    def test_index_factory_with_host(self):
        pc = Pinecone(api_key="test-key")
        index = pc.index(host="my-index-abc123.svc.pinecone.io")

        assert isinstance(index, Index)
        assert index._host == "my-index-abc123.svc.pinecone.io"

    def test_index_factory_forwards_api_key(self):
        pc = Pinecone(api_key="test-key")
        index = pc.index(host="my-index.svc.pinecone.io")

        assert index._api_key == "test-key"

    def test_index_factory_forwards_timeout(self):
        pc = Pinecone(api_key="test-key", timeout=120)
        index = pc.index(host="my-index.svc.pinecone.io")

        assert index._timeout == 120
        assert index._http._config.timeout == 120

    def test_index_factory_requires_host_or_name(self):
        pc = Pinecone(api_key="test-key")

        with pytest.raises(ValueError, match="Either 'host' or 'name' must be provided"):
            pc.index()

    def test_index_factory_with_name_resolves_host(self):
        """When name is provided without host, factory calls describe to resolve."""
        pc = Pinecone(api_key="test-key")

        mock_info = MagicMock()
        mock_info.host = "my-index-abc123.svc.pinecone.io"
        pc._indexes = MagicMock()
        pc._indexes.describe.return_value = mock_info

        index = pc.index(name="my-index")

        pc._indexes.describe.assert_called_once_with("my-index")
        assert isinstance(index, Index)
        assert index._host == "my-index-abc123.svc.pinecone.io"

    def test_index_factory_returns_fresh_instances(self):
        pc = Pinecone(api_key="test-key")
        idx1 = pc.index(host="my-index.svc.pinecone.io")
        idx2 = pc.index(host="my-index.svc.pinecone.io")

        assert idx1 is not idx2
