"""Index data plane client for vector operations.

This module provides the sync client for performing data plane operations
on a specific Pinecone index, including upserting, querying, fetching,
and deleting vectors.
"""

from __future__ import annotations

import os

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    from types import TracebackType

    from pinecone.bulk_imports import BulkImports
    from pinecone.index import (
        _delete,
        _describe_index_stats,
        _fetch,
        _fetch_by_metadata,
        _helpers,
        _list,
        _query,
        _update,
        _upsert,
    )
    from pinecone.namespaces import Namespaces
    from pinecone.records import Records


class Index:
    """Client for data plane operations on a Pinecone index.

    The Index client connects to a specific index host and provides methods
    for vector operations. It can be constructed directly or via the
    ``Pinecone.index()`` factory method.

    Direct construction is recommended when you already know the index host,
    as it avoids a control plane call::

        from pinecone import Index

        index = Index(host="my-index-abc123.svc.pinecone.io", api_key="...")
        index.upsert(vectors=[{"id": "v1", "values": [0.1, 0.2]}])
        results = index.query(vector=[0.1, 0.2], top_k=10)

    Using the factory method on an existing Pinecone client shares
    configuration automatically::

        pc = Pinecone(api_key="...", timeout=60)
        index = pc.index(host="my-index-abc123.svc.pinecone.io")

    Args:
        host: Index host URL for data plane operations.
        api_key: Pinecone API key. If not provided, reads from the
            PINECONE_API_KEY environment variable.
        timeout: Request timeout in seconds. Can be a single number or a dict
            with keys 'connect', 'read', 'write', 'pool'. Defaults to 30.
        additional_headers: Extra headers to include in every request.

    Raises:
        ValueError: If api_key is not provided and PINECONE_API_KEY
            environment variable is not set.

    Example:
        >>> from pinecone import Index
        >>>
        >>> with Index(host="my-index-abc123.svc.pinecone.io", api_key="...") as index:
        ...     index.upsert(vectors=[{"id": "v1", "values": [0.1, 0.2, 0.3]}])
        ...     results = index.query(vector=[0.1, 0.2, 0.3], top_k=10)
        ...     for match in results.matches:
        ...         print(f"{match.id}: {match.score}")
    """

    def __init__(
        self,
        *,
        host: str,
        api_key: str | None = None,
        timeout: int | float | dict[str, float] = 30,
        additional_headers: dict[str, str] | None = None,
    ):
        resolved_api_key = api_key or os.environ.get("PINECONE_API_KEY")
        if not resolved_api_key:
            raise ValueError(
                "api_key is required. Provide it as a parameter or set "
                "the PINECONE_API_KEY environment variable."
            )

        self._host = host
        self._base_url = f"https://{host}"
        self._api_key = resolved_api_key
        self._timeout = timeout
        self._additional_headers = additional_headers

        # Lazy sub-namespace instances
        self._bulk_imports: BulkImports | None = None
        self._namespaces: Namespaces | None = None
        self._records: Records | None = None

        from pinecone._internal.http import HTTPClient, HTTPConfig

        config = HTTPConfig(timeout=timeout)
        self._http = HTTPClient(api_key=resolved_api_key, config=config)

        from pinecone.index import (
            _delete,
            _describe_index_stats,
            _fetch,
            _fetch_by_metadata,
            _helpers,
            _list,
            _query,
            _update,
            _upsert,
        )

        self._make_data_plane_request = _helpers._make_data_plane_request.__get__(self, type(self))
        self.upsert = _upsert.upsert.__get__(self, type(self))  # type: ignore[method-assign]
        self.query = _query.query.__get__(self, type(self))  # type: ignore[method-assign]
        self.fetch = _fetch.fetch.__get__(self, type(self))  # type: ignore[method-assign]
        self.delete = _delete.delete.__get__(self, type(self))  # type: ignore[method-assign]
        self.update = _update.update.__get__(self, type(self))  # type: ignore[method-assign]
        self.list = _list.list.__get__(self, type(self))  # type: ignore[method-assign]
        self.fetch_by_metadata = _fetch_by_metadata.fetch_by_metadata.__get__(self, type(self))  # type: ignore[method-assign]
        self.describe_index_stats = (  # type: ignore[method-assign]
            _describe_index_stats.describe_index_stats.__get__(self, type(self))
        )

    # Protocol method stubs (overridden in __init__)
    def upsert(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for upsert method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    def query(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for query method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    def fetch(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for fetch method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    def delete(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for delete method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    def update(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for update method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    def list(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for list method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    def fetch_by_metadata(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for fetch_by_metadata method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    def describe_index_stats(self, *args, **kwargs):  # type: ignore[no-untyped-def]
        """Stub for describe_index_stats method (overridden in __init__)."""
        raise NotImplementedError  # pragma: no cover

    @property
    def bulk_imports(self) -> BulkImports:
        """Sub-namespace for bulk import operations.

        Example:
            >>> imports = index.bulk_imports.list()
        """
        if self._bulk_imports is None:
            from pinecone.bulk_imports import BulkImports

            self._bulk_imports = BulkImports(http=self._http, base_url=self._base_url)
        return self._bulk_imports

    @property
    def namespaces(self) -> Namespaces:
        """Sub-namespace for namespace management operations.

        Example:
            >>> ns_list = index.namespaces.list()
        """
        if self._namespaces is None:
            from pinecone.namespaces import Namespaces

            self._namespaces = Namespaces(http=self._http, base_url=self._base_url)
        return self._namespaces

    @property
    def records(self) -> Records:
        """Sub-namespace for records API operations.

        Example:
            >>> results = index.records.search(
            ...     namespace="ns", query={"top_k": 10, "inputs": {"text": "hello"}}
            ... )
        """
        if self._records is None:
            from pinecone.records import Records

            self._records = Records(http=self._http, base_url=self._base_url)
        return self._records

    def close(self) -> None:
        """Close the client and release connections.

        After calling close(), the client should not be used.

        Example:
            >>> index = Index(host="...", api_key="...")
            >>> try:
            ...     results = index.query(vector=[0.1, 0.2], top_k=5)
            ... finally:
            ...     index.close()
        """
        self._http.close()

    def __enter__(self) -> Index:
        """Enable context manager usage.

        Example:
            >>> with Index(host="...", api_key="...") as index:
            ...     results = index.query(vector=[0.1, 0.2], top_k=5)
        """
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        """Clean up when exiting context manager."""
        self.close()

    def __repr__(self) -> str:
        """Return string representation."""
        return f"Index(host='{self._host}')"
