"""Index management namespace implementation.

This module provides the implementation for managing Pinecone indexes including
listing, creating, deleting, and describing indexes.
"""

from __future__ import annotations

import re

from typing import TYPE_CHECKING, Any, cast

import orjson

from pinecone.__interface__.indexes import IndexNamespaceProtocol
from pinecone._internal.adapters.indexes_adapter import IndexesAdapter
from pinecone._internal.constants import INDEXES_API_VERSION
from pinecone._internal.introspection import InteractiveIntrospectionMixin
from pinecone._internal.logging import get_logger
from pinecone._internal.validation import require_in_range, require_non_empty
from pinecone.exceptions import APIError
from pinecone.models.pagination import Page, Paginator


if TYPE_CHECKING:
    import builtins

    from pinecone.__interface__ import IndexSpec
    from pinecone.__interface__.indexes import IndexInfoProtocol
    from pinecone._internal.http import HTTPClient


logger = get_logger(__name__)


class IndexNamespaceOld(InteractiveIntrospectionMixin, IndexNamespaceProtocol):
    """Implementation of index management operations.

    This class provides CRUD operations for managing Pinecone indexes including
    listing, creating, deleting, and describing indexes.

    Args:
        http_client: HTTP client for making API requests
        base_url: Base URL for the Index management API

    Example:
        >>> from pinecone import Pinecone
        >>> pc = Pinecone(api_key="...")
        >>> indexes = pc.indexes
        >>>
        >>> # List all indexes
        >>> index_list = indexes.list()
        >>> for index in index_list:
        ...     print(f"{index.name}: {index.config.dimension}D")
    """

    def __init__(
        self,
        http_client: HTTPClient,
        base_url: str = "https://api.pinecone.io",
    ):
        """Initialize IndexNamespace.

        Args:
            http_client: HTTP client for making API requests
            base_url: Base URL for the Index management API. Defaults to production endpoint.
        """
        self._http = http_client
        self._base_url = base_url
        self._adapter = IndexesAdapter()

    def _normalize_spec(self, spec: dict[str, Any]) -> dict[str, object]:
        """Normalize spec to API format.

        Handles both simple serverless specs and full spec objects.

        Args:
            spec: Index specification dict

        Returns:
            Normalized spec dict for API request
        """
        # If spec has "cloud" key directly, it's a simple serverless spec
        if "cloud" in spec:
            if "region" not in spec:
                raise ValueError(
                    "Serverless spec requires both 'cloud' and 'region' keys. "
                    f"Got spec with 'cloud': {spec['cloud']!r} but missing 'region'."
                )
            serverless_spec: dict[str, object] = {
                "cloud": spec["cloud"],
                "region": spec["region"],
            }

            # Add read_capacity if specified
            if "read_capacity" in spec:
                serverless_spec["read_capacity"] = spec["read_capacity"]

            return {"serverless": serverless_spec}

        # If spec has "environment" key, determine if it's a pod or BYOC spec
        if "environment" in spec:
            # Check if it's a pod spec or BYOC spec
            if "pod_type" in spec:
                # It's a pod spec
                return {"pod": spec}

            # Check for pod-specific fields that would indicate incomplete pod spec
            pod_fields = {
                "pods",
                "replicas",
                "shards",
                "pod_size",
                "metadata_config",
                "source_collection",
            }
            has_pod_fields = any(field in spec for field in pod_fields)

            if has_pod_fields:
                # Has pod-specific fields but missing pod_type - incomplete pod spec
                raise ValueError(
                    "Incomplete pod spec detected. Spec has 'environment' and pod-specific "
                    "fields (pods/replicas/shards/pod_size/metadata_config/source_collection) "
                    "but is missing required 'pod_type'. "
                    f"Got spec: {spec}"
                )

            # It's a BYOC spec (environment without pod_type or pod-specific fields)
            byoc_spec: dict[str, object] = {
                "environment": spec["environment"],
            }
            if "read_capacity" in spec:
                byoc_spec["read_capacity"] = spec["read_capacity"]
            return {"byoc": byoc_spec}

        # Otherwise assume it's already in the correct nested format
        return cast("dict[str, object]", spec)

    def list(
        self,
        *,
        limit: int | None = None,
        pagination_token: str | None = None,
    ) -> Paginator[IndexInfoProtocol]:
        """List all indexes in the project.

        Args:
            limit: Maximum number of items to return (across all pages).
            pagination_token: Token to resume pagination from a previous call.

        Returns:
            Paginator over IndexInfo objects. Iterate directly for items,
            or use .pages() for page-level access.

        Raises:
            APIError: If the API returns an error response
            APIConnectionError: If the request fails to connect
            TimeoutError: If the request times out

        Example:
            >>> for index in pc.indexes.list():
            ...     print(f"{index.name}: {index.config.dimension}D, {index.config.metric}")
            ...     print(f"  Host: {index.host}")
            ...     print(f"  Status: {index.status}")

        Example with page-level access:
            >>> for page in pc.indexes.list().pages():
            ...     print(f"Got {len(page.items)} indexes")
        """

        def fetch_page(token: str | None) -> Page[IndexInfoProtocol]:
            """Fetch a page of indexes."""
            logger.info("Listing indexes", pagination_token=token)

            try:
                response = self._http.get(
                    f"{self._base_url}/indexes",
                    headers={
                        "X-Pinecone-Api-Version": INDEXES_API_VERSION,
                    },
                )

                data = orjson.loads(response.content)
                result = self._adapter.to_index_list(data)

                logger.debug(
                    "Successfully listed indexes",
                    index_count=len(result.indexes),
                )

                # Cast to list[IndexInfoProtocol] for type checker
                # IndexModel structurally satisfies IndexInfoProtocol
                items: builtins.list[IndexInfoProtocol] = cast(
                    "builtins.list[IndexInfoProtocol]", result.indexes
                )

                # API currently doesn't support pagination, so return all items in one page
                return Page(items=items, pagination_token=None)

            except Exception:
                logger.exception("Failed to list indexes")
                raise

        return Paginator(
            fetch_page=fetch_page,
            initial_token=pagination_token,
            limit=limit,
            item_type="IndexModel",
        )

    def create(
        self,
        name: str,
        *,
        dimension: int | None = None,
        metric: str = "cosine",
        spec: IndexSpec,
        deletion_protection: str = "disabled",
        tags: dict[str, str] | None = None,
        vector_type: str = "dense",
        timeout: int | None = None,
    ) -> IndexInfoProtocol:
        """Create a new index.

        Args:
            name: Unique index name (1-45 chars, lowercase alphanumeric or '-')
            dimension: Vector dimensionality (1-20000). Required for dense indexes,
                omit for sparse indexes
            metric: Distance metric ('cosine', 'euclidean', or 'dotproduct').
                Defaults to 'cosine'. Must be 'dotproduct' for sparse indexes
            spec: Index specification. Can be a ServerlessSpec, PodSpec, or ByocSpec dict:
                - Serverless (on-demand): {"cloud": "aws"|"gcp"|"azure", "region": "..."}
                - Serverless (dedicated): {"cloud": "...", "region": "...", "read_capacity": {...}}
                - Pod: {"environment": "...", "pod_type": "...", "pods": 1, "replicas": 1, "shards": 1}
                  (pods, replicas, and shards are optional and default to 1)
                - BYOC: {"environment": "...", "read_capacity": {...}}
            deletion_protection: Whether deletion protection is enabled
                ('disabled' or 'enabled'). Defaults to 'disabled'
            tags: Optional key-value tags for the index. Keys must be 80 chars or less,
                values 120 chars or less
            vector_type: Type of vectors ('dense' or 'sparse'). Defaults to 'dense'
            timeout: Optional timeout in seconds

        Returns:
            IndexInfo with the created index metadata and configuration

        Raises:
            BadRequestError: If parameters are invalid
            ConflictError: If index with the name already exists
            NotFoundError: If cloud/region combination is invalid for serverless
            APIError: If the API returns an error response
            APIConnectionError: If the request fails to connect
            TimeoutError: If the request times out

        Example with serverless (on-demand):
            >>> info = pc.indexes.create(
            ...     name="my-serverless-index",
            ...     dimension=1536,
            ...     metric="cosine",
            ...     spec={"cloud": "aws", "region": "us-east-1"},
            ... )
            >>> print(f"Created {info.name} at {info.host}")

        Example with serverless (dedicated capacity):
            >>> info = pc.indexes.create(
            ...     name="my-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},
            ...             },
            ...         },
            ...     },
            ... )

        Example with sparse vectors:
            >>> info = pc.indexes.create(
            ...     name="sparse-index",
            ...     metric="dotproduct",
            ...     vector_type="sparse",
            ...     spec={"cloud": "gcp", "region": "us-east1"},
            ... )

        Example with tags and deletion protection:
            >>> info = pc.indexes.create(
            ...     name="production-index",
            ...     dimension=768,
            ...     spec={"cloud": "aws", "region": "us-west-2"},
            ...     deletion_protection="enabled",
            ...     tags={"environment": "prod", "team": "ml"},
            ... )
        """
        # 1. Validate inputs
        require_non_empty("name", name)
        require_non_empty("metric", metric)
        require_non_empty("vector_type", vector_type)

        # Validate index name format
        if len(name) > 45:
            raise ValueError(f"Index name '{name}' exceeds 45 character limit ({len(name)} chars)")

        if not re.match(r"^[a-z0-9-]+$", name):
            raise ValueError(
                f"Index name '{name}' contains invalid characters. "
                "Name must contain only lowercase letters, numbers, and hyphens."
            )

        if dimension is not None:
            require_in_range("dimension", dimension, 1, 20000)

        # Validate dimension is required for dense vectors
        if vector_type == "dense" and dimension is None:
            raise ValueError("dimension is required for dense indexes")

        # Validate dimension is not provided for sparse vectors
        if vector_type == "sparse" and dimension is not None:
            raise ValueError("dimension should not be specified for sparse indexes")

        # Validate tags
        if tags:
            for key, value in tags.items():
                if len(key) > 80:
                    raise ValueError(
                        f"Tag key '{key}' exceeds 80 character limit ({len(key)} chars)"
                    )
                if len(value) > 120:
                    raise ValueError(
                        f"Tag value for key '{key}' exceeds 120 character limit ({len(value)} chars)"
                    )

        # 2. Log operation start
        logger.info(
            "Creating index",
            name=name,
            dimension=dimension,
            metric=metric,
            vector_type=vector_type,
        )

        # 3. Build request body
        request_body: dict[str, object] = {
            "name": name,
            "metric": metric,
            "vector_type": vector_type,
            "deletion_protection": deletion_protection,
            "spec": self._normalize_spec(cast("dict[str, Any]", spec)),
        }

        if dimension is not None:
            request_body["dimension"] = dimension

        if tags:
            request_body["tags"] = tags

        try:
            # 4. Make HTTP request
            response = self._http.post(
                f"{self._base_url}/indexes",
                content=orjson.dumps(request_body),
                headers={
                    "Content-Type": "application/json",
                    "X-Pinecone-Api-Version": INDEXES_API_VERSION,
                },
                timeout=timeout,
            )

            # 5. Transform response via adapter
            data = orjson.loads(response.content)
            result = self._adapter.to_index_model(data)

            # 6. Log success
            logger.debug(
                "Index created successfully",
                name=name,
                host=result.host,
            )

            # Cast to protocol type - IndexModel structurally satisfies IndexInfoProtocol
            return cast("IndexInfoProtocol", result)

        except Exception:
            # 7. Log failure with context
            logger.exception(
                "Failed to create index",
                name=name,
                dimension=dimension,
                metric=metric,
            )
            raise

    def delete(self, name: str, *, timeout: int | None = None) -> None:
        """Delete an index.

        Warning: This operation is irreversible. The index and all its data
            will be permanently deleted.

        Args:
            name: Index name (string) to delete.
            timeout: Optional timeout in seconds. If None (default), uses the
                client's configured timeout. Increase for indexes that may take
                longer to delete (e.g., large pod-based indexes).

        Returns:
            None

        Raises:
            ValueError: If name is empty or timeout is not positive
            NotFoundError: If index does not exist
            APIError: If the API returns an error response (including 412 if there
                is a pending collection from this index)
            APIConnectionError: If the request fails to connect
            TimeoutError: If the request times out

        Example:
            >>> pc.indexes.delete("my-index")

        Example with timeout:
            >>> # Give extra time for large index deletion
            >>> pc.indexes.delete("large-index", timeout=120)

        Example with confirmation:
            >>> # Check before deleting
            >>> try:
            ...     info = pc.indexes.describe("my-index")
            ...     if info.config.dimension == 1536:
            ...         pc.indexes.delete("my-index")
            ... except NotFoundError:
            ...     print("Index doesn't exist")
        """
        require_non_empty("name", name)
        if timeout is not None and timeout <= 0:
            raise ValueError("timeout must be a positive integer")

        logger.info("Deleting index", index_name=name, timeout=timeout)

        try:
            self._http.delete(
                f"{self._base_url}/indexes/{name}",
                headers={
                    "X-Pinecone-Api-Version": INDEXES_API_VERSION,
                },
                timeout=timeout,
            )

            logger.debug("Successfully deleted index", index_name=name)

        except Exception:
            logger.exception("Failed to delete index", index_name=name)
            raise

    def describe(self, name: str) -> IndexInfoProtocol:
        """Describe a specific index.

        Args:
            name: Index name (string) to describe.

        Returns:
            IndexInfo with metadata and configuration.

        Raises:
            ValueError: If name is empty
            NotFoundError: If index does not exist
            APIError: If the API returns an error response
            APIConnectionError: If the request fails to connect
            TimeoutError: If the request times out

        Example:
            >>> info = pc.indexes.describe("my-index")
            >>> print(f"Dimension: {info.config.dimension}")
            >>> print(f"Metric: {info.config.metric}")
            >>> print(f"Host: {info.host}")
            >>> print(f"Status: {info.status}")

        Example checking status:
            >>> info = pc.indexes.describe("my-index")
            >>> if info.status.ready:
            ...     print("Index is ready for queries")
            ... else:
            ...     print(f"Index state: {info.status.state}")

        Example waiting for index to be ready:
            >>> import time
            >>> # Poll until index is ready
            >>> while True:
            ...     info = pc.indexes.describe("my-index")
            ...     if info.status.ready:
            ...         print("Index is ready!")
            ...         break
            ...     print(f"Index state: {info.status.state}")
            ...     time.sleep(5)
        """
        require_non_empty("name", name)

        logger.info("Describing index", index_name=name)

        try:
            response = self._http.get(
                f"{self._base_url}/indexes/{name}",
                headers={
                    "X-Pinecone-Api-Version": INDEXES_API_VERSION,
                },
            )

            data = orjson.loads(response.content)
            result = self._adapter.to_index_model(data)

            logger.debug("Successfully described index", index_name=name)

            # Cast to IndexInfoProtocol for type checker
            return cast("IndexInfoProtocol", result)

        except (ValueError, KeyError, TypeError) as e:
            logger.exception("Failed to parse index response", index_name=name)
            raise APIError(
                status_code=500,
                message=f"Failed to parse response for index '{name}'. "
                f"The API may have returned an unexpected format. "
                f"Original error: {e}",
            ) from e
        except Exception:
            logger.exception("Failed to describe index", index_name=name)
            raise

    def configure(
        self,
        name: str,
        *,
        deletion_protection: str | None = None,
        tags: dict[str, str] | None = None,
        spec: dict[str, Any] | None = None,
        embed: dict[str, Any] | None = None,
    ) -> IndexInfoProtocol:
        """Update index configuration.

        Args:
            name: Index name to configure
            deletion_protection: Set to "enabled" or "disabled"
            tags: Update tags (set value to "" to delete a tag)
            spec: Update spec configuration:
                - Pod: {"pod": {"replicas": 4, "pod_type": "p1.x2"}}
                - Serverless: {"serverless": {"read_capacity": {...}}}
            embed: Update embedding configuration:
                - {"field_map": {...}, "read_parameters": {...}, "write_parameters": {...}}
                - Note: model cannot be changed once set

        Returns:
            Updated IndexInfo

        Raises:
            NotFoundError: If index does not exist
            BadRequestError: If configuration is invalid
            APIError: If the API returns an error response
            APIConnectionError: If the request fails to connect
            TimeoutError: If the request times out

        Example:
            >>> # Update deletion protection
            >>> pc.indexes.configure("my-index", deletion_protection="enabled")

        Example scaling pod replicas:
            >>> # Scale pod replicas
            >>> pc.indexes.configure("my-index", spec={"pod": {"replicas": 4}})

        Example with dedicated read nodes:
            >>> # Update serverless read capacity (dedicated nodes)
            >>> pc.indexes.configure(
            ...     "my-index",
            ...     spec={
            ...         "serverless": {
            ...             "read_capacity": {
            ...                 "mode": "Dedicated",
            ...                 "dedicated": {
            ...                     "node_type": "b1",
            ...                     "scaling": "Manual",
            ...                     "manual": {"shards": 2, "replicas": 2},
            ...                 },
            ...             }
            ...         }
            ...     },
            ... )
        """
        # 1. Validate inputs
        require_non_empty("name", name)

        # Validate at least one parameter is provided
        if deletion_protection is None and not tags and not spec and not embed:
            raise ValueError(
                "At least one configuration parameter must be provided: "
                "deletion_protection, tags, spec, or embed"
            )

        # Validate tags if provided
        if tags:
            for key, value in tags.items():
                if len(key) > 80:
                    raise ValueError(
                        f"Tag key '{key}' exceeds 80 character limit ({len(key)} chars)"
                    )
                if len(value) > 120:
                    raise ValueError(
                        f"Tag value for key '{key}' exceeds 120 character limit ({len(value)} chars)"
                    )

        # 2. Log operation start
        logger.info("Configuring index", index_name=name)

        # 3. Build request body
        request_body: dict[str, Any] = {}

        if deletion_protection is not None:
            request_body["deletion_protection"] = deletion_protection

        if tags:
            request_body["tags"] = tags

        if spec is not None:
            request_body["spec"] = spec

        if embed is not None:
            request_body["embed"] = embed

        try:
            # 4. Make HTTP request
            response = self._http.patch(
                f"{self._base_url}/indexes/{name}",
                content=orjson.dumps(request_body),
                headers={
                    "Content-Type": "application/json",
                    "X-Pinecone-Api-Version": INDEXES_API_VERSION,
                },
            )

            # 5. Transform response via adapter
            data = orjson.loads(response.content)
            result = self._adapter.to_index_model(data)

            # 6. Log success
            logger.debug("Index configured successfully", name=name)

            return cast("IndexInfoProtocol", result)

        except Exception:
            # 7. Log failure with context
            logger.exception("Failed to configure index", index_name=name)
            raise

    def create_for_model(  # noqa: PLR0912
        self,
        name: str,
        cloud: str,
        region: str,
        *,
        embed: dict[str, Any],
        deletion_protection: str = "disabled",
        tags: dict[str, str] | None = None,
        schema: dict[str, Any] | None = None,
        read_capacity: dict[str, Any] | None = None,
        timeout: int | None = None,
    ) -> IndexInfoProtocol:
        """Create index with integrated embedding model.

        Creates a serverless index with automatic text embedding using a hosted model.
        The model's dimension and metric are automatically configured.

        Args:
            name: Unique index name (1-45 chars, lowercase alphanumeric or '-')
            cloud: Cloud provider ("aws", "gcp", or "azure")
            region: Region for deployment
            embed: Embedding configuration with required fields:
                - model: Model name (e.g., "multilingual-e5-large")
                - field_map: {"text": "your-field-name"} mapping to text field
                - Optional: read_parameters, write_parameters, dimension, metric
            deletion_protection: "disabled" or "enabled" (default: "disabled")
            tags: Optional key-value tags
            schema: Optional metadata schema for indexing specific fields
            read_capacity: Optional read capacity configuration for dedicated nodes
            timeout: Optional timeout in seconds

        Returns:
            IndexInfo with created index metadata

        Raises:
            BadRequestError: If parameters are invalid
            ConflictError: If index name already exists
            NotFoundError: If cloud/region combination is invalid
            APIError: If the API returns an error response
            APIConnectionError: If the request fails to connect
            TimeoutError: If the request times out

        Example:
            >>> # Create with multilingual embedding model
            >>> info = pc.indexes.create_for_model(
            ...     name="semantic-search",
            ...     cloud="aws",
            ...     region="us-east-1",
            ...     embed={"model": "multilingual-e5-large", "field_map": {"text": "content"}},
            ... )

        Example with custom parameters:
            >>> # Create with custom parameters
            >>> info = pc.indexes.create_for_model(
            ...     name="custom-search",
            ...     cloud="gcp",
            ...     region="us-east1",
            ...     embed={
            ...         "model": "llama-text-embed-v2",
            ...         "field_map": {"text": "text_content"},
            ...         "read_parameters": {"input_type": "query", "truncate": "NONE"},
            ...         "write_parameters": {"input_type": "passage"},
            ...     },
            ...     deletion_protection="enabled",
            ...     tags={"environment": "production"},
            ... )
        """
        # 1. Validate inputs
        require_non_empty("name", name)
        require_non_empty("cloud", cloud)
        require_non_empty("region", region)

        # Validate index name format
        if len(name) > 45:
            raise ValueError(f"Index name '{name}' exceeds 45 character limit ({len(name)} chars)")

        if not re.match(r"^[a-z0-9-]+$", name):
            raise ValueError(
                f"Index name '{name}' contains invalid characters. "
                "Name must contain only lowercase letters, numbers, and hyphens."
            )

        # Validate embed configuration
        if not embed or not isinstance(embed, dict):
            raise ValueError("embed configuration is required and must be a dictionary")

        if "model" not in embed:
            raise ValueError("embed configuration must include 'model' field")

        if "field_map" not in embed:
            raise ValueError("embed configuration must include 'field_map' field")

        # Validate tags
        if tags:
            for key, value in tags.items():
                if len(key) > 80:
                    raise ValueError(
                        f"Tag key '{key}' exceeds 80 character limit ({len(key)} chars)"
                    )
                if len(value) > 120:
                    raise ValueError(
                        f"Tag value for key '{key}' exceeds 120 character limit ({len(value)} chars)"
                    )

        # 2. Log operation start
        logger.info(
            "Creating index with embedding model",
            name=name,
            cloud=cloud,
            region=region,
            model=embed.get("model"),
        )

        # 3. Build request body
        request_body: dict[str, Any] = {
            "name": name,
            "cloud": cloud,
            "region": region,
            "embed": embed,
            "deletion_protection": deletion_protection,
        }

        if tags:
            request_body["tags"] = tags

        if schema:
            request_body["schema"] = schema

        if read_capacity:
            request_body["read_capacity"] = read_capacity

        try:
            # 4. Make HTTP request
            response = self._http.post(
                f"{self._base_url}/indexes/create-for-model",
                content=orjson.dumps(request_body),
                headers={
                    "Content-Type": "application/json",
                    "X-Pinecone-Api-Version": INDEXES_API_VERSION,
                },
                timeout=timeout,
            )

            # 5. Transform response via adapter
            data = orjson.loads(response.content)
            result = self._adapter.to_index_model(data)

            # 6. Log success
            logger.debug(
                "Index with embedding model created successfully",
                name=name,
                host=result.host,
            )

            return cast("IndexInfoProtocol", result)

        except Exception:
            # 7. Log failure with context
            logger.exception(
                "Failed to create index with embedding model",
                name=name,
                cloud=cloud,
                region=region,
            )
            raise


__all__ = ["IndexNamespaceOld"]
