"""Async Pinecone client implementation.

This module implements the async Pinecone client for non-blocking operations,
enabling concurrent API calls for agent workflows and async frameworks.
"""

from __future__ import annotations

import os
import warnings

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    from types import TracebackType

    from pinecone.async_client.backups import AsyncBackups
    from pinecone.async_client.collections import AsyncCollections
    from pinecone.async_client.indexes import AsyncIndexes
    from pinecone.async_client.indexes_old import AsyncIndexNamespace as AsyncIndexNamespaceOld
    from pinecone.async_client.inference import AsyncInference
    from pinecone.async_client.restore_jobs import AsyncRestoreJobs
    from pinecone.async_index import AsyncIndex


class AsyncPinecone:
    """Async Pinecone client for non-blocking operations.

    This client enables concurrent API calls using asyncio, making it ideal for
    agent workflows that need to process many requests in parallel or integrate
    with async frameworks like FastAPI, aiohttp, or Quart.

    The client is safe to instantiate in any context and will properly initialize
    async resources when first used. Always use ``async with`` or manually call
    ``await client.close()`` to ensure proper cleanup.

    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 AsyncPinecone
        >>> import asyncio
        >>>
        >>> async def main():
        ...     # With explicit api_key
        ...     async with AsyncPinecone(api_key="your-api-key") as pc:
        ...         response = await pc.inference.embed(
        ...             model="multilingual-e5-large",
        ...             texts=["Hello world"],
        ...             parameters={"input_type": "passage", "truncate": "END"},
        ...         )
        ...         print(f"Generated {len(response.embeddings)} embeddings")
        >>>
        >>> asyncio.run(main())

    Example with custom timeout:
        >>> async def main():
        ...     # With custom timeout for long-running operations
        ...     async with AsyncPinecone(api_key="your-api-key", timeout=120) as pc:
        ...         response = await pc.inference.embed(
        ...             model="multilingual-e5-large",
        ...             texts=["Hello world"],
        ...         )
        >>>
        >>> asyncio.run(main())

    Example with concurrent operations:
        >>> async def embed_documents(documents: list[str]) -> list[list[float]]:
        ...     async with AsyncPinecone(api_key="...") as pc:
        ...         # Split into batches
        ...         batches = [documents[i : i + 100] for i in range(0, len(documents), 100)]
        ...
        ...         # Process batches concurrently
        ...         tasks = [
        ...             pc.inference.embed(model="multilingual-e5-large", texts=batch)
        ...             for batch in batches
        ...         ]
        ...         results = await asyncio.gather(*tasks)
        ...
        ...         # Flatten results
        ...         return [emb for result in results for emb in result.embeddings]

    Example with environment variable:
        >>> async with AsyncPinecone() as pc:  # Uses PINECONE_API_KEY
        ...     response = await pc.inference.embed(
        ...         model="multilingual-e5-large",
        ...         texts=["Hello"],
        ...     )

    Example with FastAPI:
        >>> from fastapi import FastAPI
        >>> from pinecone import AsyncPinecone
        >>>
        >>> app = FastAPI()
        >>> pc = AsyncPinecone(api_key="your-api-key")  # Safe to create at module level
        >>>
        >>> @app.get("/embed")
        >>> async def embed_text(text: str):
        ...     response = await pc.inference.embed(
        ...         model="multilingual-e5-large",
        ...         texts=[text],
        ...     )
        ...     return {"embedding": response.embeddings[0]}
        >>>
        >>> @app.on_event("shutdown")
        >>> async def shutdown():
        ...     await pc.close()  # Clean up on app shutdown

    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 async 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 async HTTP client with custom timeout (imports httpx at this point)
        from pinecone._internal.http import AsyncHTTPClient, HTTPConfig

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

        # Lazy-initialize namespace objects (created on first access)
        self._backups: AsyncBackups | None = None
        self._collections: AsyncCollections | None = None
        self._indexes: AsyncIndexes | None = None
        self._indexes_old: AsyncIndexNamespaceOld | None = None
        self._inference: AsyncInference | None = None
        self._restore_jobs: AsyncRestoreJobs | None = None

    @property
    def indexes_old(self) -> AsyncIndexNamespaceOld:
        """Access the async 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 AsyncIndexNamespace is lazily imported and instantiated on first access
        to minimize import-time cost. Index management operations are fully async.

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

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

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

    @property
    def indexes(self) -> AsyncIndexes:
        """Access the async 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:
            AsyncIndexes instance for managing indexes asynchronously (2026-01.alpha API).

        Example:
            >>> async with AsyncPinecone(api_key="your-api-key") as pc:
            ...     # Create index with new schema API
            ...     await 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 self._indexes is None:
            from pinecone.async_client.indexes import AsyncIndexes

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

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

        The AsyncBackups 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:
            AsyncBackups namespace instance.

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

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

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

        The AsyncCollections 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:
            AsyncCollections namespace instance.

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

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

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

        The AsyncInference 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:
            AsyncInference namespace instance.

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

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

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

        The AsyncRestoreJobs 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:
            AsyncRestoreJobs namespace instance.

        Example:
            >>> async with AsyncPinecone(api_key="your-api-key") as pc:
            ...     # List all restore jobs
            ...     async for job in pc.restore_jobs.list():
            ...         print(f"{job.restore_job_id}: {job.status} ({job.percent_complete}%)")
            ...
            ...     # Monitor a specific job
            ...     job = await 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.async_client.restore_jobs import AsyncRestoreJobs

            self._restore_jobs = AsyncRestoreJobs(
                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) -> AsyncIndex:
        """Get an AsyncIndex client for data plane operations on a specific index.

        Creates an AsyncIndex 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 AsyncIndex inherits configuration (api_key, timeout)
        from this AsyncPinecone client.

        Note:
            Unlike most methods on AsyncPinecone, this method is synchronous
            because it only constructs the client object. No I/O occurs until
            you call a method on the returned AsyncIndex.

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

        Returns:
            AsyncIndex client for vector operations.

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

        Example:
            >>> async with AsyncPinecone(api_key="...") as pc:
            ...     index = pc.index(host="my-index-abc123.svc.pinecone.io")
            ...     results = await index.query(vector=[0.1, 0.2], top_k=10)
        """
        if host is None and name is None:
            raise ValueError("Either 'host' or 'name' must be provided.")

        if host is None:
            raise ValueError(
                "Name-based host resolution requires an async describe call. "
                "Please provide 'host' directly, or use "
                "'await pc.indexes.describe(name)' to resolve the host first."
            )

        from pinecone.async_index import AsyncIndex

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

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

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

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

    async def __aenter__(self) -> AsyncPinecone:
        """Enable async context manager usage.

        Example:
            >>> async with AsyncPinecone(api_key="your-api-key") as pc:
            ...     response = await pc.inference.embed(model="...", texts=["..."])
        """
        return self

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

    def __repr__(self) -> str:
        """Return string representation of client."""
        return f"AsyncPinecone(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)

    def __del__(self) -> None:
        """Warn if client wasn't properly closed."""
        try:
            if self._http_client._client is not None and not self._http_client._client.is_closed:
                warnings.warn(
                    f"Unclosed {self.__class__.__name__}. Use 'async with' or call 'await client.close()'",
                    ResourceWarning,
                    stacklevel=2,
                )
        except Exception:  # noqa: BLE001, S110
            # Ignore errors during cleanup (e.g., during interpreter shutdown)
            pass
