"""Main Pinecone client implementation.

This module implements the primary Pinecone client that serves as the main
entry point for all SDK operations.
"""

from __future__ import annotations

import os

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    from types import TracebackType

    from pinecone.client.backups import Backups
    from pinecone.client.collections import Collections
    from pinecone.client.indexes import Indexes
    from pinecone.client.indexes_old import IndexNamespaceOld
    from pinecone.client.inference import Inference
    from pinecone.client.restore_jobs import RestoreJobs
    from pinecone.index import Index


class Pinecone:
    """Main Pinecone client.

    This is the primary entry point for the Pinecone SDK. It manages configuration
    and provides access to all Pinecone services through namespaced properties.

    Args:
        api_key: Pinecone API key for authentication. If not provided, will attempt
            to read from PINECONE_API_KEY environment variable.
        base_url: Optional base URL override for API endpoints.
        timeout: Request timeout in seconds. Can be a single number to set all
            timeout types, or a dict with keys 'connect', 'read', 'write', 'pool'
            for granular control. Defaults to 30 seconds.

    Example:
        >>> from pinecone import Pinecone
        >>>
        >>> # With explicit api_key
        >>> pc = Pinecone(api_key="your-api-key")
        >>>
        >>> # Or use PINECONE_API_KEY environment variable
        >>> pc = Pinecone()
        >>>
        >>> # With custom timeout
        >>> pc = Pinecone(api_key="your-api-key", timeout=60)
        >>>
        >>> # With granular timeout control
        >>> pc = Pinecone(api_key="your-api-key", timeout={"connect": 5, "read": 120, "write": 30})
        >>>
        >>> # Use inference
        >>> response = pc.inference.embed(
        ...     model="multilingual-e5-large",
        ...     texts=["Hello world"],
        ...     parameters={"input_type": "passage", "truncate": "END"},
        ... )
        >>>
        >>> print(f"Generated {len(response.embeddings)} embeddings")

    Note:
        Currently only the Inference namespace is implemented. Index, Collection,
        and Backup management will be added in future releases.
    """

    def __init__(
        self,
        *,
        api_key: str | None = None,
        base_url: str = "https://api.pinecone.io",
        timeout: int | float | dict[str, float] = 30,
    ):
        """Initialize Pinecone client.

        Args:
            api_key: Pinecone API key for authentication. If not provided,
                will attempt to read from PINECONE_API_KEY environment variable.
            base_url: Base URL for API endpoints. Defaults to production URL.
            timeout: Request timeout in seconds. Can be a single number to set all
                timeout types, or a dict with keys 'connect', 'read', 'write', 'pool'
                for granular control. Defaults to 30 seconds.

        Raises:
            ValueError: If api_key is not provided and PINECONE_API_KEY
                environment variable is not set.
        """
        # Get API key from parameter or environment variable
        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._api_key = resolved_api_key
        self._base_url = base_url
        self._timeout = timeout

        # Create HTTP client with custom timeout (imports httpx at this point)
        from pinecone._internal.http import HTTPClient, HTTPConfig

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

        # Lazy-initialize namespace objects (created on first access)
        self._backups: Backups | None = None
        self._collections: Collections | None = None
        self._indexes: Indexes | None = None
        self._indexes_old: IndexNamespaceOld | None = None
        self._inference: Inference | None = None
        self._restore_jobs: RestoreJobs | None = None

    @property
    def indexes_old(self) -> IndexNamespaceOld:
        """Access the Index management namespace (2025-10 API, temporary).

        .. deprecated::
            This is the legacy 2025-10 API implementation, kept temporarily for
            backward compatibility during migration to the 2026-01.alpha API.
            Use this only if you need the old dimension/metric-based index creation.
            Will be removed before public release.

        The IndexNamespace is lazily imported and instantiated on first access
        to minimize import-time cost.

        Returns:
            IndexNamespace instance for managing indexes (2025-10 API).

        Example:
            >>> pc = Pinecone(api_key="your-api-key")
            >>>
            >>> # List all indexes (old API)
            >>> indexes = pc.indexes_old.list()
            >>> for index in indexes:
            ...     print(f"{index.name}: {index.config.dimension}D")
        """
        if self._indexes_old is None:
            from pinecone.client.indexes_old import IndexNamespaceOld

            self._indexes_old = IndexNamespaceOld(
                http_client=self._http_client,
                base_url=self._base_url,
            )
        return self._indexes_old

    @property
    def indexes(self) -> Indexes:
        """Access the Index management namespace (2026-01.alpha API, NEW).

        .. note::
            This is the new schema-driven API (2026-01.alpha). For the legacy
            dimension/metric-based API, use :attr:`indexes_old`.

        The new indexes namespace uses schema-based field definitions instead of
        top-level dimension/metric parameters.

        Returns:
            Indexes instance for managing indexes (2026-01.alpha API).

        Example:
            >>> pc = Pinecone(api_key="your-api-key")
            >>>
            >>> # Create index with new schema API
            >>> pc.indexes.create(
            ...     name="my-index",
            ...     schema={
            ...         "fields": {
            ...             "embedding": {"type": "dense_vector", "dimension": 1536, "metric": "cosine"}
            ...         }
            ...     },
            ...     deployment={"deployment_type": "serverless", "cloud": "aws", "region": "us-east-1"},
            ... )
        """
        if not hasattr(self, "_indexes"):
            self._indexes = None

        if self._indexes is None:
            from pinecone.client.indexes import Indexes

            self._indexes = Indexes(
                http_client=self._http_client,
                base_url=self._base_url,
            )
        return self._indexes

    @property
    def backups(self) -> Backups:
        """Access the Backups namespace for managing serverless index backups.

        The Backups namespace is lazily imported and instantiated on first access
        to minimize import-time cost. Backups are static copies of serverless indexes
        useful for disaster recovery and data protection.

        Returns:
            Backups namespace instance.

        Example:
            >>> pc = Pinecone(api_key="your-api-key")
            >>>
            >>> # Create a backup
            >>> backup = pc.backups.create(
            ...     index_name="my-index", name="example-backup", description="Monthly backup"
            ... )
            >>>
            >>> # List backups
            >>> for backup in pc.backups.list():
            ...     print(f"{backup.backup_id}: {backup.status}")
            >>>
            >>> # Describe a backup
            >>> info = pc.backups.describe(backup.backup_id)
            >>> print(f"Records: {info.record_count}")
            >>>
            >>> # Delete a backup
            >>> pc.backups.delete(backup.backup_id)
        """
        if self._backups is None:
            from pinecone.client.backups import Backups

            self._backups = Backups(
                http_client=self._http_client,
                base_url=self._base_url,
            )
        return self._backups

    @property
    def collections(self) -> Collections:
        """Access the Collections namespace for managing collection resources.

        The Collections namespace is lazily imported and instantiated on first access
        to minimize import-time cost. Collections are static copies of indexes useful
        for backups and cloning.

        Note: Serverless indexes do not support collections.

        Returns:
            Collections namespace instance.

        Example:
            >>> pc = Pinecone(api_key="your-api-key")
            >>>
            >>> # List collections
            >>> for coll in pc.collections.list():
            ...     print(f"{coll.name}: {coll.status}")
            >>>
            >>> # Create collection from index
            >>> pc.collections.create(name="my-backup", source="my-index")
        """
        if self._collections is None:
            from pinecone.client.collections import Collections

            self._collections = Collections(
                http_client=self._http_client,
                base_url=self._base_url,
            )
        return self._collections

    @property
    def inference(self) -> Inference:
        """Access the Inference namespace for embedding and reranking operations.

        The Inference namespace is lazily imported and instantiated on first access
        to minimize import-time cost. As more namespaces are added (Index, Collection,
        Backup), this ensures users only pay for what they use.

        Returns:
            Inference namespace instance.

        Example:
            >>> pc = Pinecone(api_key="your-api-key")
            >>>
            >>> # Generate embeddings
            >>> response = pc.inference.embed(
            ...     model="multilingual-e5-large",
            ...     texts=["Hello", "World"],
            ...     parameters={"input_type": "passage", "truncate": "END"},
            ... )
            >>>
            >>> # Rerank documents
            >>> response = pc.inference.rerank(
            ...     model="bge-reranker-v2-m3", query="What is AI?", documents=["AI is...", "ML is..."]
            ... )
        """
        if self._inference is None:
            from pinecone.client.inference import Inference

            self._inference = Inference(
                http_client=self._http_client,
                base_url=self._base_url,
            )
        return self._inference

    @property
    def restore_jobs(self) -> RestoreJobs:
        """Access the RestoreJobs namespace for monitoring restore operations.

        The RestoreJobs namespace is lazily imported and instantiated on first access
        to minimize import-time cost. Restore jobs are created automatically when
        restoring an index from a backup.

        Returns:
            RestoreJobs namespace instance.

        Example:
            >>> pc = Pinecone(api_key="your-api-key")
            >>>
            >>> # List all restore jobs
            >>> for job in pc.restore_jobs.list():
            ...     print(f"{job.restore_job_id}: {job.status} ({job.percent_complete}%)")
            >>>
            >>> # Monitor a specific job
            >>> job = pc.restore_jobs.describe("670e8400-e29b-41d4-a716-446655440001")
            >>> print(f"Restoring {job.target_index_name}: {job.percent_complete}%")
        """
        if self._restore_jobs is None:
            from pinecone.client.restore_jobs import RestoreJobs

            self._restore_jobs = RestoreJobs(
                http_client=self._http_client,
                base_url=self._base_url,
            )
        return self._restore_jobs

    def index(self, *, host: str | None = None, name: str | None = None) -> Index:
        """Get an Index client for data plane operations on a specific index.

        Creates an Index client that connects to the specified index host.
        Either ``host`` or ``name`` must be provided. Using ``host`` is
        recommended because it avoids a control plane call to resolve the host.

        The returned Index inherits configuration (api_key, timeout) from
        this Pinecone client.

        Args:
            host: Index host URL (recommended). Avoids a control plane call.
            name: Index name. If provided without ``host``, the host is
                resolved via a describe call to the control plane.

        Returns:
            Index client for vector operations (upsert, query, fetch, delete).

        Raises:
            ValueError: If neither ``host`` nor ``name`` is provided.

        Example:
            >>> pc = Pinecone(api_key="...")
            >>>
            >>> # Recommended: use host directly
            >>> index = pc.index(host="my-index-abc123.svc.pinecone.io")
            >>>
            >>> # Alternative: resolve host from index name (makes a control plane call)
            >>> index = pc.index(name="my-index")
        """
        if host is None and name is None:
            raise ValueError("Either 'host' or 'name' must be provided.")

        if host is None:
            # Note: name is guaranteed to be str here (not None) due to validation above
            info = self.indexes.describe(name)  # type: ignore[arg-type]
            host = info.host

        from pinecone.index import Index

        return Index(
            host=host,
            api_key=self._api_key,
            timeout=self._timeout,
        )

    def close(self) -> None:
        """Close the client and clean up resources.

        This closes the underlying HTTP client and releases connections.
        After calling close(), the client should not be used.

        Example:
            >>> pc = Pinecone(api_key="your-api-key")
            >>> try:
            ...     response = pc.inference.embed(model="...", texts=["..."])
            ... finally:
            ...     pc.close()
        """
        self._http_client.close()

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

        Example:
            >>> with Pinecone(api_key="your-api-key") as pc:
            ...     response = pc.inference.embed(model="...", texts=["..."])
        """
        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 of client."""
        return f"Pinecone(base_url='{self._base_url}')"

    def __dir__(self) -> list[str]:
        """Return public API, filtering out internal attributes."""
        attrs = set(super().__dir__())
        public = {name for name in attrs if not name.startswith("_")}
        return sorted(public)
