"""Unit tests for IndexNamespace implementation."""

from __future__ import annotations

import orjson
import pytest
import respx

from httpx import Response

from pinecone import Pinecone
from pinecone.__interface__.indexes import IndexNamespaceProtocol
from pinecone._internal.http import HTTPClient
from pinecone.client.indexes_old import IndexNamespaceOld as IndexNamespace
from pinecone.exceptions import APIError, NotFoundError


class TestIndexNamespaceProtocolCompliance:
    """Test that IndexNamespace satisfies IndexNamespaceProtocol."""

    def test_index_namespace_satisfies_protocol(self):
        """Verify IndexNamespace implements IndexNamespaceProtocol."""
        client = HTTPClient(api_key="test-key")
        instance = IndexNamespace(client)

        # Verify protocol satisfaction
        assert isinstance(instance, IndexNamespaceProtocol)

        # Verify all required methods exist
        assert hasattr(instance, "list")
        assert callable(instance.list)
        assert hasattr(instance, "create")
        assert callable(instance.create)
        assert hasattr(instance, "delete")
        assert callable(instance.delete)
        assert hasattr(instance, "describe")
        assert callable(instance.describe)


class TestIndexNamespaceList:
    """Test list() method."""

    @respx.mock
    def test_list_indexes_success(self):
        """Test successful list indexes operation."""
        # Mock API response
        mock_response = {
            "indexes": [
                {
                    "name": "test-index-1",
                    "metric": "cosine",
                    "vector_type": "dense",
                    "dimension": 1536,
                    "host": "test-index-1.pinecone.io",
                    "status": {"ready": True, "state": "Ready"},
                    "spec": {
                        "serverless": {
                            "cloud": "aws",
                            "region": "us-east-1",
                            "read_capacity": {
                                "mode": "OnDemand",
                                "status": {"state": "Ready"},
                            },
                        }
                    },
                },
                {
                    "name": "test-index-2",
                    "metric": "euclidean",
                    "vector_type": "dense",
                    "dimension": 768,
                    "host": "test-index-2.pinecone.io",
                    "deletion_protection": "enabled",
                    "tags": {"environment": "production"},
                    "status": {"ready": False, "state": "Initializing"},
                    "spec": {
                        "pod": {
                            "environment": "us-east1-gcp",
                            "pod_type": "p1.x1",
                            "pods": 1,
                            "replicas": 1,
                            "shards": 1,
                        }
                    },
                },
            ]
        }

        route = respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        # Create client and call list
        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)  # Iterate to trigger API call

        # Verify request
        assert route.called
        request = route.calls.last.request
        assert request.headers["X-Pinecone-Api-Version"] == "2025-10"
        assert request.headers["Api-Key"] == "test-key"

        # Verify response
        assert len(indexes) == 2

        # Verify first index
        index1 = indexes[0]
        assert index1.name == "test-index-1"
        assert index1.metric == "cosine"
        assert index1.vector_type == "dense"
        assert index1.dimension == 1536
        assert index1.host == "test-index-1.pinecone.io"
        assert index1.status_obj.ready is True
        assert index1.status_obj.state == "Ready"
        assert index1.status == "Ready"
        assert index1.spec.serverless is not None
        assert index1.spec.serverless.cloud == "aws"
        assert index1.spec.serverless.region == "us-east-1"
        assert index1.spec.serverless.read_capacity.mode == "OnDemand"
        assert index1.spec.pod is None

        # Verify config properties
        assert index1.config.dimension == 1536
        assert index1.config.metric == "cosine"
        assert index1.config.spec == index1.spec

        # Verify second index
        index2 = indexes[1]
        assert index2.name == "test-index-2"
        assert index2.metric == "euclidean"
        assert index2.dimension == 768
        assert index2.deletion_protection == "enabled"
        assert index2.tags == {"environment": "production"}
        assert index2.status_obj.ready is False
        assert index2.status_obj.state == "Initializing"
        assert index2.status == "Initializing"
        assert index2.spec.pod is not None
        assert index2.spec.pod.environment == "us-east1-gcp"
        assert index2.spec.serverless is None

    @respx.mock
    def test_index_model_status_api(self):
        """Test that IndexModel.status returns IndexStatus object (not string).

        Breaking change: .status should be the IndexStatus object (with .ready and .state)
        and .state should be the string shortcut.
        """
        mock_response = {
            "indexes": [
                {
                    "name": "test-index",
                    "metric": "cosine",
                    "vector_type": "dense",
                    "dimension": 1536,
                    "host": "test-index.pinecone.io",
                    "status": {"ready": True, "state": "Ready"},
                    "spec": {
                        "serverless": {
                            "cloud": "aws",
                            "region": "us-east-1",
                            "read_capacity": {
                                "mode": "OnDemand",
                                "status": {
                                    "state": "Ready",
                                },
                            },
                        }
                    },
                }
            ]
        }

        respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)

        index = indexes[0]

        # Old API: .status_obj is the IndexStatus object with .ready and .state
        assert index.status_obj.ready is True
        assert index.status_obj.state == "Ready"

        # Old API: .status is a convenience property that returns the state string
        assert index.status == "Ready"

        # Verify the object has the expected structure
        assert hasattr(index, "status_obj")

    @respx.mock
    def test_list_indexes_empty(self):
        """Test list indexes with no indexes."""
        mock_response = {"indexes": []}

        respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)  # Iterate to trigger API call

        assert len(indexes) == 0
        assert indexes == []

    @respx.mock
    def test_list_indexes_sparse(self):
        """Test list indexes with sparse index (no dimension)."""
        mock_response = {
            "indexes": [
                {
                    "name": "sparse-index",
                    "metric": "dotproduct",
                    "vector_type": "sparse",
                    "host": "sparse-index.pinecone.io",
                    "status": {"ready": True, "state": "Ready"},
                    "spec": {
                        "serverless": {
                            "cloud": "aws",
                            "region": "us-east-1",
                            "read_capacity": {
                                "mode": "OnDemand",
                                "status": {"state": "Ready"},
                            },
                        }
                    },
                }
            ]
        }

        respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)  # Iterate to trigger API call

        assert len(indexes) == 1
        index = indexes[0]
        assert index.name == "sparse-index"
        assert index.vector_type == "sparse"
        assert index.dimension is None

    @respx.mock
    def test_list_indexes_with_dedicated_read_capacity(self):
        """Test list indexes with dedicated read capacity configuration."""
        mock_response = {
            "indexes": [
                {
                    "name": "dedicated-index",
                    "metric": "cosine",
                    "vector_type": "dense",
                    "dimension": 1024,
                    "host": "dedicated-index.pinecone.io",
                    "status": {"ready": True, "state": "Ready"},
                    "spec": {
                        "serverless": {
                            "cloud": "aws",
                            "region": "us-east-1",
                            "read_capacity": {
                                "mode": "Dedicated",
                                "status": {
                                    "state": "Ready",
                                    "current_shards": 2,
                                    "current_replicas": 3,
                                },
                                "dedicated": {
                                    "node_type": "b1",
                                    "scaling": "Manual",
                                    "manual": {"shards": 2, "replicas": 3},
                                },
                            },
                        }
                    },
                }
            ]
        }

        respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)  # Iterate to trigger API call

        assert len(indexes) == 1
        index = indexes[0]
        assert index.name == "dedicated-index"
        assert index.spec.serverless is not None
        rc = index.spec.serverless.read_capacity
        assert rc.mode == "Dedicated"
        assert rc.status.state == "Ready"
        assert rc.status.current_shards == 2
        assert rc.status.current_replicas == 3
        assert rc.dedicated is not None
        assert rc.dedicated.node_type == "b1"
        assert rc.dedicated.scaling == "Manual"
        assert rc.dedicated.manual is not None
        assert rc.dedicated.manual.shards == 2
        assert rc.dedicated.manual.replicas == 3

    @respx.mock
    def test_list_indexes_with_embed_field(self):
        """Test list indexes with embed configuration (from create_index_for_model)."""
        mock_response = {
            "indexes": [
                {
                    "name": "model-index",
                    "metric": "cosine",
                    "vector_type": "dense",
                    "dimension": 1024,
                    "host": "model-index.pinecone.io",
                    "status": {"ready": True, "state": "Ready"},
                    "embed": {
                        "model": "multilingual-e5-large",
                        "metric": "cosine",
                        "dimension": 1024,
                        "field_map": {"text": "content"},
                        "read_parameters": {"input_type": "query", "truncate": "NONE"},
                        "write_parameters": {"input_type": "passage"},
                    },
                    "spec": {
                        "serverless": {
                            "cloud": "aws",
                            "region": "us-east-1",
                            "read_capacity": {
                                "mode": "OnDemand",
                                "status": {"state": "Ready"},
                            },
                        }
                    },
                }
            ]
        }

        respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)

        assert len(indexes) == 1
        index = indexes[0]
        assert index.name == "model-index"
        assert index.embed is not None
        assert index.embed["model"] == "multilingual-e5-large"
        assert index.embed["metric"] == "cosine"
        assert index.embed["dimension"] == 1024
        assert index.embed["field_map"] == {"text": "content"}
        assert index.embed["read_parameters"]["input_type"] == "query"
        assert index.embed["write_parameters"]["input_type"] == "passage"

    @respx.mock
    def test_list_indexes_with_schema_field(self):
        """Test list indexes with schema configuration (metadata filtering)."""
        mock_response = {
            "indexes": [
                {
                    "name": "schema-index",
                    "metric": "cosine",
                    "vector_type": "dense",
                    "dimension": 1536,
                    "host": "schema-index.pinecone.io",
                    "status": {"ready": True, "state": "Ready"},
                    "spec": {
                        "serverless": {
                            "cloud": "gcp",
                            "region": "us-east1",
                            "read_capacity": {
                                "mode": "OnDemand",
                                "status": {"state": "Ready"},
                            },
                            "schema": {
                                "fields": {
                                    "genre": {"filterable": True},
                                    "year": {"filterable": True},
                                    "title": {"filterable": True},
                                }
                            },
                        }
                    },
                }
            ]
        }

        respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)

        assert len(indexes) == 1
        index = indexes[0]
        assert index.name == "schema-index"
        assert index.spec.serverless is not None
        assert index.spec.serverless.schema is not None
        schema_fields = index.spec.serverless.schema["fields"]
        assert "genre" in schema_fields
        assert "year" in schema_fields
        assert "title" in schema_fields
        assert schema_fields["genre"]["filterable"] is True

    @respx.mock
    def test_list_indexes_with_source_collection_field(self):
        """Test list indexes with source_collection field."""
        mock_response = {
            "indexes": [
                {
                    "name": "from-collection",
                    "metric": "cosine",
                    "vector_type": "dense",
                    "dimension": 768,
                    "host": "from-collection.pinecone.io",
                    "status": {"ready": True, "state": "Ready"},
                    "spec": {
                        "serverless": {
                            "cloud": "aws",
                            "region": "us-west-2",
                            "read_capacity": {
                                "mode": "OnDemand",
                                "status": {"state": "Ready"},
                            },
                            "source_collection": "my-backup-collection",
                        }
                    },
                }
            ]
        }

        respx.get("https://api.pinecone.io/indexes").mock(
            return_value=Response(200, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        paginator = pc.indexes_old.list()
        indexes = list(paginator)

        assert len(indexes) == 1
        index = indexes[0]
        assert index.name == "from-collection"
        assert index.spec.serverless is not None
        assert index.spec.serverless.source_collection == "my-backup-collection"


class TestIndexNamespaceCreate:
    """Test create() method."""

    @respx.mock
    def test_create_serverless_index_success(self):
        """Test successful serverless index creation."""
        mock_response = {
            "name": "test-serverless-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "test-serverless-index.pinecone.io",
            "status": {"ready": False, "state": "Initializing"},
            "deletion_protection": "disabled",
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {
                        "mode": "OnDemand",
                        "status": {"state": "Ready"},
                    },
                }
            },
        }

        route = respx.post("https://api.pinecone.io/indexes").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.create(
            name="test-serverless-index",
            dimension=1536,
            metric="cosine",
            spec={"cloud": "aws", "region": "us-east-1"},
        )

        # Verify request
        assert route.called
        request = route.calls.last.request
        assert request.headers["X-Pinecone-Api-Version"] == "2025-10"
        assert request.headers["Api-Key"] == "test-key"
        assert request.headers["Content-Type"] == "application/json"

        # Verify request body
        import orjson

        body = orjson.loads(request.content)
        assert body["name"] == "test-serverless-index"
        assert body["dimension"] == 1536
        assert body["metric"] == "cosine"
        assert body["vector_type"] == "dense"
        assert body["deletion_protection"] == "disabled"
        assert body["spec"]["serverless"]["cloud"] == "aws"
        assert body["spec"]["serverless"]["region"] == "us-east-1"

        # Verify response
        assert result.name == "test-serverless-index"
        assert result.host == "test-serverless-index.pinecone.io"
        assert result.status == "Initializing"

    @respx.mock
    def test_create_serverless_index_with_dedicated_capacity(self):
        """Test serverless index creation with dedicated read capacity."""
        mock_response = {
            "name": "dedicated-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "dedicated-index.pinecone.io",
            "status": {"ready": False, "state": "Initializing"},
            "deletion_protection": "enabled",
            "tags": {"environment": "prod"},
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {
                        "mode": "Dedicated",
                        "status": {"state": "Migrating"},
                        "dedicated": {
                            "node_type": "b1",
                            "scaling": "Manual",
                            "manual": {"shards": 2, "replicas": 1},
                        },
                    },
                }
            },
        }

        route = respx.post("https://api.pinecone.io/indexes").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.create(
            name="dedicated-index",
            dimension=1536,
            metric="cosine",
            spec={
                "cloud": "aws",
                "region": "us-east-1",
                "read_capacity": {
                    "mode": "Dedicated",
                    "dedicated": {
                        "node_type": "b1",
                        "scaling": "Manual",
                        "manual": {"shards": 2, "replicas": 1},
                    },
                },
            },
            deletion_protection="enabled",
            tags={"environment": "prod"},
        )

        # Verify request
        assert route.called
        request = route.calls.last.request
        import orjson

        body = orjson.loads(request.content)
        assert body["deletion_protection"] == "enabled"
        assert body["tags"] == {"environment": "prod"}
        assert body["spec"]["serverless"]["read_capacity"]["mode"] == "Dedicated"

        # Verify response
        assert result.name == "dedicated-index"
        assert result.spec.serverless is not None
        assert result.spec.serverless.read_capacity.mode == "Dedicated"

    @respx.mock
    def test_create_sparse_index(self):
        """Test sparse index creation (no dimension)."""
        mock_response = {
            "name": "sparse-index",
            "metric": "dotproduct",
            "vector_type": "sparse",
            "host": "sparse-index.pinecone.io",
            "status": {"ready": False, "state": "Initializing"},
            "deletion_protection": "disabled",
            "spec": {
                "serverless": {
                    "cloud": "gcp",
                    "region": "us-east1",
                    "read_capacity": {
                        "mode": "OnDemand",
                        "status": {"state": "Ready"},
                    },
                }
            },
        }

        route = respx.post("https://api.pinecone.io/indexes").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.create(
            name="sparse-index",
            metric="dotproduct",
            vector_type="sparse",
            spec={"cloud": "gcp", "region": "us-east1"},
        )

        # Verify request
        assert route.called
        request = route.calls.last.request
        import orjson

        body = orjson.loads(request.content)
        assert body["vector_type"] == "sparse"
        assert "dimension" not in body  # dimension should not be in request

        # Verify response
        assert result.name == "sparse-index"
        assert result.vector_type == "sparse"

    @respx.mock
    def test_create_pod_index(self):
        """Test pod-based index creation."""
        mock_response = {
            "name": "pod-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "pod-index.pinecone.io",
            "status": {"ready": False, "state": "Initializing"},
            "deletion_protection": "disabled",
            "spec": {
                "pod": {
                    "environment": "us-east1-gcp",
                    "pod_type": "p1.x1",
                    "pods": 1,
                    "replicas": 1,
                    "shards": 1,
                }
            },
        }

        route = respx.post("https://api.pinecone.io/indexes").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.create(
            name="pod-index",
            dimension=1536,
            metric="cosine",
            spec={
                "environment": "us-east1-gcp",
                "pod_type": "p1.x1",
                "pods": 1,
                "replicas": 1,
                "shards": 1,
            },
        )

        # Verify request
        assert route.called
        request = route.calls.last.request
        import orjson

        body = orjson.loads(request.content)
        assert body["spec"]["pod"]["environment"] == "us-east1-gcp"
        assert body["spec"]["pod"]["pod_type"] == "p1.x1"
        assert body["spec"]["pod"]["pods"] == 1

        # Verify response
        assert result.name == "pod-index"
        assert result.spec.pod is not None
        assert result.spec.pod.environment == "us-east1-gcp"
        assert result.spec.pod.pod_type == "p1.x1"

    @respx.mock
    def test_create_pod_index_minimal_spec(self):
        """Test pod-based index creation with minimal spec (no pods/replicas/shards)."""
        mock_response = {
            "name": "minimal-pod-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "minimal-pod-index.pinecone.io",
            "status": {"ready": False, "state": "Initializing"},
            "deletion_protection": "disabled",
            "spec": {
                "pod": {
                    "environment": "us-west1-gcp",
                    "pod_type": "p1.x1",
                    "pods": 1,
                    "replicas": 1,
                    "shards": 1,
                }
            },
        }

        route = respx.post("https://api.pinecone.io/indexes").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        # Create with only required fields (environment and pod_type)
        result = pc.indexes_old.create(
            name="minimal-pod-index",
            dimension=1536,
            metric="cosine",
            spec={
                "environment": "us-west1-gcp",
                "pod_type": "p1.x1",
            },
        )

        # Verify request - pods/replicas/shards should not be in request if not specified
        assert route.called
        request = route.calls.last.request
        import orjson

        body = orjson.loads(request.content)
        assert body["spec"]["pod"]["environment"] == "us-west1-gcp"
        assert body["spec"]["pod"]["pod_type"] == "p1.x1"
        # These fields should either not be present or be None in the request
        # (API will use defaults)

        # Verify response - API returns default values
        assert result.name == "minimal-pod-index"
        assert result.spec.pod is not None
        assert result.spec.pod.environment == "us-west1-gcp"
        assert result.spec.pod.pod_type == "p1.x1"
        assert result.spec.pod.pods == 1  # API default
        assert result.spec.pod.replicas == 1  # API default
        assert result.spec.pod.shards == 1  # API default

    @respx.mock
    def test_create_byoc_index(self):
        """Test BYOC index creation."""
        mock_response = {
            "name": "byoc-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "byoc-index.svc.aped-4627-b74a.pinecone.io",
            "private_host": "byoc-index.svc.private.aped-4627-b74a.pinecone.io",
            "status": {"ready": True, "state": "Ready"},
            "deletion_protection": "disabled",
            "spec": {
                "byoc": {
                    "environment": "aws-us-east-1-b921",
                    "read_capacity": {
                        "mode": "OnDemand",
                        "status": {"state": "Ready"},
                    },
                }
            },
        }

        route = respx.post("https://api.pinecone.io/indexes").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.create(
            name="byoc-index",
            dimension=1536,
            metric="cosine",
            spec={"environment": "aws-us-east-1-b921"},
        )

        # Verify request
        assert route.called
        request = route.calls.last.request
        import orjson

        body = orjson.loads(request.content)
        assert body["spec"]["byoc"]["environment"] == "aws-us-east-1-b921"

        # Verify response
        assert result.name == "byoc-index"
        assert result.spec.byoc is not None
        assert result.spec.byoc.environment == "aws-us-east-1-b921"
        assert result.host == "byoc-index.svc.aped-4627-b74a.pinecone.io"
        assert result.private_host == "byoc-index.svc.private.aped-4627-b74a.pinecone.io"

    def test_create_validates_empty_name(self):
        """Test create validates empty name."""
        pc = Pinecone(api_key="test-key")

        with pytest.raises(ValueError, match="name must be a non-empty string"):
            pc.indexes_old.create(
                name="",
                dimension=1536,
                spec={"cloud": "aws", "region": "us-east-1"},
            )

    def test_create_validates_dimension_range(self):
        """Test create validates dimension is in valid range."""
        pc = Pinecone(api_key="test-key")

        with pytest.raises(ValueError, match="dimension must be between 1 and 20000"):
            pc.indexes_old.create(
                name="test-index",
                dimension=0,
                spec={"cloud": "aws", "region": "us-east-1"},
            )

        with pytest.raises(ValueError, match="dimension must be between 1 and 20000"):
            pc.indexes_old.create(
                name="test-index",
                dimension=20001,
                spec={"cloud": "aws", "region": "us-east-1"},
            )

    def test_create_validates_dimension_required_for_dense(self):
        """Test create validates dimension is required for dense indexes."""
        pc = Pinecone(api_key="test-key")

        with pytest.raises(ValueError, match="dimension is required for dense indexes"):
            pc.indexes_old.create(
                name="test-index",
                vector_type="dense",
                spec={"cloud": "aws", "region": "us-east-1"},
            )

    def test_create_validates_dimension_not_allowed_for_sparse(self):
        """Test create validates dimension should not be provided for sparse indexes."""
        pc = Pinecone(api_key="test-key")

        with pytest.raises(
            ValueError, match="dimension should not be specified for sparse indexes"
        ):
            pc.indexes_old.create(
                name="test-index",
                dimension=1536,
                vector_type="sparse",
                metric="dotproduct",
                spec={"cloud": "aws", "region": "us-east-1"},
            )

    def test_create_validates_name_length(self):
        """Test create validates index name length."""
        pc = Pinecone(api_key="test-key")

        # Name too long (46 characters)
        long_name = "a" * 46
        with pytest.raises(ValueError, match="exceeds 45 character limit"):
            pc.indexes_old.create(
                name=long_name,
                dimension=1536,
                spec={"cloud": "aws", "region": "us-east-1"},
            )

    def test_create_validates_name_format(self):
        """Test create validates index name format."""
        pc = Pinecone(api_key="test-key")

        # Name with uppercase letters
        with pytest.raises(ValueError, match="contains invalid characters"):
            pc.indexes_old.create(
                name="Test-Index",
                dimension=1536,
                spec={"cloud": "aws", "region": "us-east-1"},
            )

        # Name with underscores
        with pytest.raises(ValueError, match="contains invalid characters"):
            pc.indexes_old.create(
                name="test_index",
                dimension=1536,
                spec={"cloud": "aws", "region": "us-east-1"},
            )

        # Name with special characters
        with pytest.raises(ValueError, match="contains invalid characters"):
            pc.indexes_old.create(
                name="test@index",
                dimension=1536,
                spec={"cloud": "aws", "region": "us-east-1"},
            )

    def test_create_validates_tag_key_length(self):
        """Test create validates tag key length."""
        pc = Pinecone(api_key="test-key")

        # Tag key too long (81 characters)
        long_key = "k" * 81
        with pytest.raises(ValueError, match=r"Tag key .* exceeds 80 character limit"):
            pc.indexes_old.create(
                name="test-index",
                dimension=1536,
                spec={"cloud": "aws", "region": "us-east-1"},
                tags={long_key: "value"},
            )

    def test_create_validates_tag_value_length(self):
        """Test create validates tag value length."""
        pc = Pinecone(api_key="test-key")

        # Tag value too long (121 characters)
        long_value = "v" * 121
        with pytest.raises(ValueError, match=r"Tag value .* exceeds 120 character limit"):
            pc.indexes_old.create(
                name="test-index",
                dimension=1536,
                spec={"cloud": "aws", "region": "us-east-1"},
                tags={"key": long_value},
            )

    def test_create_validates_serverless_spec_requires_region(self):
        """Test create validates serverless spec has both cloud and region."""
        pc = Pinecone(api_key="test-key")

        # Missing region key
        with pytest.raises(
            ValueError, match=r"Serverless spec requires both 'cloud' and 'region' keys"
        ):
            pc.indexes_old.create(
                name="test-index",
                dimension=1536,
                spec={"cloud": "aws"},
            )

    def test_create_validates_incomplete_pod_spec_with_metadata_config(self):
        """Test create detects incomplete pod spec with metadata_config but missing pod_type."""
        pc = Pinecone(api_key="test-key")

        # Has environment and metadata_config but missing pod_type
        with pytest.raises(
            ValueError, match=r"Incomplete pod spec detected.*missing required 'pod_type'"
        ):
            pc.indexes_old.create(
                name="test-index",
                dimension=1536,
                spec={
                    "environment": "us-west1-gcp",
                    "metadata_config": {"indexed": ["genre", "year"]},
                },
            )

    def test_create_validates_incomplete_pod_spec_with_source_collection(self):
        """Test create detects incomplete pod spec with source_collection but missing pod_type."""
        pc = Pinecone(api_key="test-key")

        # Has environment and source_collection but missing pod_type
        with pytest.raises(
            ValueError, match=r"Incomplete pod spec detected.*missing required 'pod_type'"
        ):
            pc.indexes_old.create(
                name="test-index",
                dimension=1536,
                spec={
                    "environment": "us-west1-gcp",
                    "source_collection": "my-collection",
                },
            )


class TestIndexNamespaceConfigure:
    """Test configure() method."""

    @respx.mock
    def test_configure_deletion_protection(self):
        """Test updating deletion protection."""
        mock_response = {
            "name": "test-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "test-index.pinecone.io",
            "deletion_protection": "enabled",
            "status": {"ready": True, "state": "Ready"},
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {"mode": "OnDemand", "status": {"state": "Ready"}},
                }
            },
        }

        route = respx.patch("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(202, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.configure("test-index", deletion_protection="enabled")

        assert route.called
        request = route.calls.last.request
        assert request.headers["X-Pinecone-Api-Version"] == "2025-10"
        request_body = orjson.loads(request.content)
        assert request_body["deletion_protection"] == "enabled"
        assert result.name == "test-index"
        assert result.deletion_protection == "enabled"

    @respx.mock
    def test_configure_tags(self):
        """Test updating tags."""
        mock_response = {
            "name": "test-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "test-index.pinecone.io",
            "tags": {"environment": "production", "team": "ml"},
            "status": {"ready": True, "state": "Ready"},
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {"mode": "OnDemand", "status": {"state": "Ready"}},
                }
            },
        }

        route = respx.patch("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(202, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.configure(
            "test-index", tags={"environment": "production", "team": "ml"}
        )

        assert route.called
        request_body = orjson.loads(route.calls.last.request.content)
        assert request_body["tags"] == {"environment": "production", "team": "ml"}
        assert result.tags == {"environment": "production", "team": "ml"}

    @respx.mock
    def test_configure_spec_pod_replicas(self):
        """Test scaling pod replicas."""
        mock_response = {
            "name": "test-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "test-index.pinecone.io",
            "status": {"ready": True, "state": "Ready"},
            "spec": {
                "pod": {
                    "environment": "us-east1-gcp",
                    "pod_type": "p1.x1",
                    "pods": 1,
                    "replicas": 4,
                    "shards": 1,
                }
            },
        }

        route = respx.patch("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(202, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.configure("test-index", spec={"pod": {"replicas": 4}})

        assert route.called
        request_body = orjson.loads(route.calls.last.request.content)
        assert request_body["spec"]["pod"]["replicas"] == 4
        assert result.spec.pod is not None
        assert result.spec.pod.replicas == 4

    @respx.mock
    def test_configure_spec_serverless_read_capacity(self):
        """Test updating serverless read capacity."""
        mock_response = {
            "name": "test-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1536,
            "host": "test-index.pinecone.io",
            "status": {"ready": True, "state": "Ready"},
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {
                        "mode": "Dedicated",
                        "dedicated": {
                            "node_type": "b1",
                            "scaling": "Manual",
                            "manual": {"shards": 2, "replicas": 2},
                        },
                        "status": {"state": "Scaling", "current_shards": 1, "current_replicas": 1},
                    },
                }
            },
        }

        route = respx.patch("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(202, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.configure(
            "test-index",
            spec={
                "serverless": {
                    "read_capacity": {
                        "mode": "Dedicated",
                        "dedicated": {
                            "node_type": "b1",
                            "scaling": "Manual",
                            "manual": {"shards": 2, "replicas": 2},
                        },
                    }
                }
            },
        )

        assert route.called
        request_body = orjson.loads(route.calls.last.request.content)
        assert request_body["spec"]["serverless"]["read_capacity"]["mode"] == "Dedicated"
        assert result.spec.serverless is not None
        assert result.spec.serverless.read_capacity.mode == "Dedicated"

    @respx.mock
    def test_configure_embed_parameters(self):
        """Test updating embedding model parameters."""
        mock_response = {
            "name": "test-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1024,
            "host": "test-index.pinecone.io",
            "status": {"ready": True, "state": "Ready"},
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {"mode": "OnDemand", "status": {"state": "Ready"}},
                }
            },
            "embed": {
                "model": "multilingual-e5-large",
                "field_map": {"text": "content"},
                "read_parameters": {"input_type": "query", "truncate": "NONE"},
            },
        }

        route = respx.patch("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(202, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.configure(
            "test-index",
            embed={
                "field_map": {"text": "content"},
                "read_parameters": {"input_type": "query", "truncate": "NONE"},
            },
        )

        assert route.called
        request_body = orjson.loads(route.calls.last.request.content)
        assert request_body["embed"]["field_map"]["text"] == "content"
        assert result.embed is not None
        assert result.embed["field_map"]["text"] == "content"

    def test_configure_validates_empty_name(self):
        """Test validation of empty index name."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match=r"name.*empty"):
            pc.indexes_old.configure("", deletion_protection="enabled")

    def test_configure_requires_parameter(self):
        """Test that at least one parameter is required."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match="At least one configuration parameter"):
            pc.indexes_old.configure("test-index")

    def test_configure_rejects_empty_tags_dict(self):
        """Test that empty tags dict is rejected."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match="At least one configuration parameter"):
            pc.indexes_old.configure("test-index", tags={})

    def test_configure_rejects_empty_spec_dict(self):
        """Test that empty spec dict is rejected."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match="At least one configuration parameter"):
            pc.indexes_old.configure("test-index", spec={})

    def test_configure_rejects_empty_embed_dict(self):
        """Test that empty embed dict is rejected."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match="At least one configuration parameter"):
            pc.indexes_old.configure("test-index", embed={})

    def test_configure_validates_tag_key_length(self):
        """Test validation of tag key length."""
        pc = Pinecone(api_key="test-key")
        long_key = "x" * 81
        with pytest.raises(ValueError, match=r"Tag key.*exceeds 80 character limit"):
            pc.indexes_old.configure("test-index", tags={long_key: "value"})

    def test_configure_validates_tag_value_length(self):
        """Test validation of tag value length."""
        pc = Pinecone(api_key="test-key")
        long_value = "x" * 121
        with pytest.raises(ValueError, match=r"Tag value.*exceeds 120 character limit"):
            pc.indexes_old.configure("test-index", tags={"key": long_value})


class TestIndexNamespaceCreateForModel:
    """Test create_for_model() method."""

    @respx.mock
    def test_create_for_model_success(self):
        """Test successful index creation with embedding model."""
        mock_response = {
            "name": "embedding-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1024,
            "host": "embedding-index.pinecone.io",
            "status": {"ready": False, "state": "Initializing"},
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {"mode": "OnDemand", "status": {"state": "Ready"}},
                }
            },
            "embed": {
                "model": "multilingual-e5-large",
                "field_map": {"text": "content"},
                "dimension": 1024,
                "metric": "cosine",
            },
        }

        route = respx.post("https://api.pinecone.io/indexes/create-for-model").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.create_for_model(
            name="embedding-index",
            cloud="aws",
            region="us-east-1",
            embed={"model": "multilingual-e5-large", "field_map": {"text": "content"}},
        )

        assert route.called
        request = route.calls.last.request
        assert request.headers["X-Pinecone-Api-Version"] == "2025-10"
        request_body = orjson.loads(request.content)
        assert request_body["name"] == "embedding-index"
        assert request_body["cloud"] == "aws"
        assert request_body["embed"]["model"] == "multilingual-e5-large"
        assert result.embed is not None
        assert result.embed["model"] == "multilingual-e5-large"

    @respx.mock
    def test_create_for_model_with_all_options(self):
        """Test create_for_model with all optional parameters."""
        mock_response = {
            "name": "full-index",
            "metric": "cosine",
            "vector_type": "dense",
            "dimension": 1024,
            "host": "full-index.pinecone.io",
            "deletion_protection": "enabled",
            "tags": {"environment": "production"},
            "status": {"ready": False, "state": "Initializing"},
            "spec": {
                "serverless": {
                    "cloud": "gcp",
                    "region": "us-east1",
                    "read_capacity": {
                        "mode": "Dedicated",
                        "dedicated": {
                            "node_type": "b1",
                            "scaling": "Manual",
                            "manual": {"shards": 2, "replicas": 1},
                        },
                        "status": {
                            "state": "Migrating",
                            "current_shards": None,
                            "current_replicas": None,
                        },
                    },
                    "schema": {"fields": {"genre": {"filterable": True}}},
                }
            },
            "embed": {
                "model": "llama-text-embed-v2",
                "field_map": {"text": "content"},
                "dimension": 1024,
                "metric": "cosine",
                "read_parameters": {"input_type": "query"},
                "write_parameters": {"input_type": "passage"},
            },
        }

        route = respx.post("https://api.pinecone.io/indexes/create-for-model").mock(
            return_value=Response(201, json=mock_response)
        )

        pc = Pinecone(api_key="test-key")
        result = pc.indexes_old.create_for_model(
            name="full-index",
            cloud="gcp",
            region="us-east1",
            embed={
                "model": "llama-text-embed-v2",
                "field_map": {"text": "content"},
                "read_parameters": {"input_type": "query"},
                "write_parameters": {"input_type": "passage"},
            },
            deletion_protection="enabled",
            tags={"environment": "production"},
            schema={"fields": {"genre": {"filterable": True}}},
            read_capacity={
                "mode": "Dedicated",
                "dedicated": {
                    "node_type": "b1",
                    "scaling": "Manual",
                    "manual": {"shards": 2, "replicas": 1},
                },
            },
        )

        assert route.called
        request_body = orjson.loads(route.calls.last.request.content)
        assert request_body["deletion_protection"] == "enabled"
        assert request_body["tags"]["environment"] == "production"
        assert request_body["schema"]["fields"]["genre"]["filterable"] is True
        assert result.deletion_protection == "enabled"

    def test_create_for_model_validates_empty_name(self):
        """Test validation of empty index name."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match=r"name.*empty"):
            pc.indexes_old.create_for_model(
                name="",
                cloud="aws",
                region="us-east-1",
                embed={"model": "multilingual-e5-large", "field_map": {"text": "content"}},
            )

    def test_create_for_model_validates_name_length(self):
        """Test validation of index name length."""
        pc = Pinecone(api_key="test-key")
        long_name = "x" * 46
        with pytest.raises(ValueError, match="exceeds 45 character limit"):
            pc.indexes_old.create_for_model(
                name=long_name,
                cloud="aws",
                region="us-east-1",
                embed={"model": "multilingual-e5-large", "field_map": {"text": "content"}},
            )

    def test_create_for_model_validates_name_format(self):
        """Test validation of index name format."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match="invalid characters"):
            pc.indexes_old.create_for_model(
                name="Invalid_Name",
                cloud="aws",
                region="us-east-1",
                embed={"model": "multilingual-e5-large", "field_map": {"text": "content"}},
            )

    def test_create_for_model_validates_embed_required(self):
        """Test that embed configuration is required."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match=r"embed.*required"):
            pc.indexes_old.create_for_model(
                name="test",
                cloud="aws",
                region="us-east-1",
                embed={},  # type: ignore[arg-type]
            )

    def test_create_for_model_validates_model_field(self):
        """Test that model field is required in embed config."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match=r"model.*field"):
            pc.indexes_old.create_for_model(
                name="test",
                cloud="aws",
                region="us-east-1",
                embed={"field_map": {"text": "content"}},
            )

    def test_create_for_model_validates_field_map(self):
        """Test that field_map is required in embed config."""
        pc = Pinecone(api_key="test-key")
        with pytest.raises(ValueError, match=r"field_map.*field"):
            pc.indexes_old.create_for_model(
                name="test",
                cloud="aws",
                region="us-east-1",
                embed={"model": "multilingual-e5-large"},
            )

    def test_create_for_model_validates_tag_key_length(self):
        """Test validation of tag key length."""
        pc = Pinecone(api_key="test-key")
        long_key = "x" * 81
        with pytest.raises(ValueError, match=r"Tag key.*exceeds 80 character limit"):
            pc.indexes_old.create_for_model(
                name="test",
                cloud="aws",
                region="us-east-1",
                embed={"model": "multilingual-e5-large", "field_map": {"text": "content"}},
                tags={long_key: "value"},
            )


class TestIndexNamespaceDescribe:
    """Test describe() method."""

    @respx.mock
    def test_describe_success(self):
        """Test successful index describe."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        mock_response = {
            "name": "test-index",
            "dimension": 1536,
            "metric": "cosine",
            "vector_type": "dense",
            "host": "test-index-abc123.svc.pinecone.io",
            "status": {"ready": True, "state": "Ready"},
            "spec": {
                "serverless": {
                    "cloud": "aws",
                    "region": "us-east-1",
                    "read_capacity": {
                        "mode": "OnDemand",
                        "status": {"state": "Ready"},
                    },
                }
            },
        }

        respx.get("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(200, json=mock_response)
        )

        result = namespace.describe("test-index")

        assert result.name == "test-index"
        assert result.config.dimension == 1536
        assert result.config.metric == "cosine"
        assert result.host == "test-index-abc123.svc.pinecone.io"
        assert result.status == "Ready"

    @respx.mock
    def test_describe_not_found(self):
        """Test describe() with non-existent index."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        respx.get("https://api.pinecone.io/indexes/nonexistent").mock(
            return_value=Response(
                404,
                json={
                    "error": {"code": "NOT_FOUND", "message": "Index not found"},
                    "status": 404,
                },
            )
        )

        with pytest.raises(NotFoundError, match="Index not found"):
            namespace.describe("nonexistent")

    def test_describe_empty_name(self):
        """Test describe() with empty name."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        with pytest.raises(ValueError, match="name must be a non-empty string"):
            namespace.describe("")


class TestIndexNamespaceDelete:
    """Test delete() method."""

    @respx.mock
    def test_delete_success(self):
        """Test successful index deletion."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        respx.delete("https://api.pinecone.io/indexes/test-index").mock(return_value=Response(202))

        # Should not raise
        namespace.delete("test-index")

    @respx.mock
    def test_delete_with_timeout(self):
        """Test delete() with custom timeout."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        mock = respx.delete("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(202)
        )

        namespace.delete("test-index", timeout=60)

        # Verify timeout was passed
        assert mock.called

    @respx.mock
    def test_delete_not_found(self):
        """Test delete() with non-existent index."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        respx.delete("https://api.pinecone.io/indexes/nonexistent").mock(
            return_value=Response(
                404,
                json={
                    "error": {"code": "NOT_FOUND", "message": "Index not found"},
                    "status": 404,
                },
            )
        )

        with pytest.raises(NotFoundError, match="Index not found"):
            namespace.delete("nonexistent")

    @respx.mock
    def test_delete_with_pending_collection(self):
        """Test delete() with pending collection."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        respx.delete("https://api.pinecone.io/indexes/test-index").mock(
            return_value=Response(
                412,
                json={
                    "error": {
                        "code": "FAILED_PRECONDITION",
                        "message": "There is a pending collection from this index",
                    },
                    "status": 412,
                },
            )
        )

        with pytest.raises(APIError, match="There is a pending collection"):
            namespace.delete("test-index")

    def test_delete_empty_name(self):
        """Test delete() with empty name."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        with pytest.raises(ValueError, match="name must be a non-empty string"):
            namespace.delete("")

    def test_delete_invalid_timeout_negative(self):
        """Test delete() with negative timeout."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        with pytest.raises(ValueError, match="timeout must be a positive integer"):
            namespace.delete("my-index", timeout=-1)

    def test_delete_invalid_timeout_zero(self):
        """Test delete() with zero timeout."""
        client = HTTPClient(api_key="test-key")
        namespace = IndexNamespace(client)

        with pytest.raises(ValueError, match="timeout must be a positive integer"):
            namespace.delete("my-index", timeout=0)
