"""Async backups namespace implementation for managing serverless index backups.

This module provides the async implementation for backup management operations,
enabling concurrent backup operations for async frameworks.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, cast

import msgspec
import orjson

from pinecone._internal.adapters.backups_adapter import BackupsAdapter
from pinecone._internal.constants import CONTROL_PLANE_API_VERSION
from pinecone._internal.introspection import InteractiveIntrospectionMixin
from pinecone._internal.logging import get_logger
from pinecone._internal.validation import require_non_empty
from pinecone.models.backups import CreateBackupRequest
from pinecone.models.pagination import AsyncPaginator, Page


if TYPE_CHECKING:
    from pinecone.__interface__.backups import BackupInfoProtocol
    from pinecone._internal.http import AsyncHTTPClient


__all__ = ["AsyncBackups"]

logger = get_logger(__name__)


class AsyncBackups(InteractiveIntrospectionMixin):
    """Async implementation of backup management operations.

    This class enables concurrent API calls for backup management, making it
    ideal for async frameworks like FastAPI or when managing multiple backups
    in parallel.

    Args:
        http_client: Async HTTP client for making API requests
        base_url: Base URL for the Control Plane API

    Example:
        >>> from pinecone import AsyncPinecone
        >>> import asyncio
        >>>
        >>> async def main():
        ...     async with AsyncPinecone(api_key="...") as pc:
        ...         # Create a backup
        ...         backup = await pc.backups.create(
        ...             index_name="my-index",
        ...             name="example-backup",
        ...             description="Monthly backup",
        ...         )
        ...         print(f"Backup ID: {backup.backup_id}")
        ...
        ...         # List all 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)
        >>>
        >>> asyncio.run(main())

    Example with concurrent operations:
        >>> async def backup_multiple_indexes(pc, index_names):
        ...     tasks = [
        ...         pc.backups.create(index_name=name, name=f"{name}-backup")
        ...         for name in index_names
        ...     ]
        ...     return await asyncio.gather(*tasks)
    """

    def __init__(
        self,
        http_client: AsyncHTTPClient,
        base_url: str = "https://api.pinecone.io",
    ):
        """Initialize async Backups namespace.

        Args:
            http_client: Async HTTP client for making API requests
            base_url: Base URL for the Control Plane API. Defaults to production endpoint.
        """
        self._http = http_client
        self._base_url = base_url
        self._adapter = BackupsAdapter()
        self._headers = {"X-Pinecone-Api-Version": CONTROL_PLANE_API_VERSION}

    async def create(
        self,
        index_name: str,
        *,
        name: str | None = None,
        description: str | None = None,
        timeout: float | None = None,
    ) -> BackupInfoProtocol:
        """Create a backup from a serverless index asynchronously.

        This operation creates a static copy of a serverless index that only consumes
        storage. The backup is non-queryable but can be used to restore an index later.

        Args:
            index_name: Name of the serverless index to back up.
            name: Optional user-defined display name for the backup. If not provided,
                a system-generated name will be used. Note: This is NOT the backup_id
                used to reference the backup. The backup_id is auto-generated and
                returned in the response.
            description: Optional description providing context for the backup.
            timeout: Optional timeout in seconds for the API request (not the backup
                creation itself, which happens asynchronously).

        Returns:
            BackupInfoProtocol with backup metadata including backup_id and status.
            The status will initially be "Initializing" as the backup happens
            asynchronously.

        Raises:
            ValueError: If index_name is empty or timeout is not positive.
            NotFoundError: If index not found.
            APIError: If backup creation fails.

        Example:
            >>> backup = await pc.backups.create(
            ...     index_name="my-index",
            ...     name="example-backup",
            ...     description="Monthly backup of production index",
            ... )
            >>> print(f"Backup ID: {backup.backup_id}")
            >>> print(f"Status: {backup.status}")
            >>>
            >>> # Poll until ready
            >>> import asyncio
            >>> while True:
            ...     info = await pc.backups.describe(backup.backup_id)
            ...     if info.status == "Ready":
            ...         break
            ...     await asyncio.sleep(10)
        """
        # Validate inputs
        require_non_empty("index_name", index_name)
        if timeout is not None and timeout <= 0:
            raise ValueError(f"timeout must be a positive number, received: {timeout}")

        logger.info(
            "Creating backup",
            index_name=index_name,
            backup_name=name,
            has_description=description is not None,
            timeout=timeout,
        )

        try:
            # Build request body - only include fields if provided
            if name is not None or description is not None:
                request = CreateBackupRequest(name=name, description=description)
                content = msgspec.json.encode(request)
            else:
                # Empty body for no optional params
                content = b"{}"

            # Make HTTP request
            response = await self._http.post(
                f"{self._base_url}/indexes/{index_name}/backups",
                content=content,
                headers={**self._headers, "Content-Type": "application/json"},
                timeout=timeout,
            )

            # Parse response (201 Created)
            data = orjson.loads(response.content)
            result: BackupInfoProtocol = self._adapter.to_backup_model(data)

            logger.info(
                "Backup created successfully",
                backup_id=result.backup_id,
                index_name=index_name,
                status=result.status,
            )

            return result  # noqa: TRY300

        except Exception:
            logger.exception("Failed to create backup", index_name=index_name, backup_name=name)
            raise

    def list(
        self,
        *,
        index_name: str | None = None,
        limit: int | None = None,
        pagination_token: str | None = None,
    ) -> AsyncPaginator[BackupInfoProtocol]:
        """List backups for a project or specific index asynchronously.

        Args:
            index_name: Optional index name to filter backups. If None, lists all
                backups in the project.
            limit: Maximum number of items to return (across all pages).
            pagination_token: Token to resume pagination from a previous call.

        Returns:
            AsyncPaginator[BackupInfoProtocol]: Iterator over BackupInfo objects.
            Iterate directly for items, or use .pages() for page-level access.

        Raises:
            ValueError: If index_name is an empty string or limit is not positive.
            APIError: If listing fails.

        Example:
            >>> # List all backups in project
            >>> async for backup in pc.backups.list():
            ...     print(f"{backup.backup_id}: {backup.status}")

        Example filtering by index:
            >>> # List backups for specific index
            >>> async for backup in pc.backups.list(index_name="my-index"):
            ...     print(f"{backup.name}: {backup.status}")

        Example with page-level access:
            >>> async for page in pc.backups.list().pages():
            ...     print(f"Got {len(page.items)} backups")
        """
        # Validate inputs
        if index_name is not None:
            require_non_empty("index_name", index_name)
        if limit is not None and limit <= 0:
            raise ValueError(f"limit must be a positive integer, received: {limit}")

        logger.info(
            "Listing backups",
            index_name=index_name,
            limit=limit,
            has_pagination_token=pagination_token is not None,
        )

        async def fetch_page(token: str | None) -> Page[BackupInfoProtocol]:
            """Fetch a page of backups asynchronously."""
            logger.debug("Fetching backups page", pagination_token=token, index_name=index_name)

            try:
                # Build URL - different endpoints for project-wide vs index-specific
                if index_name is not None:
                    url = f"{self._base_url}/indexes/{index_name}/backups"
                else:
                    url = f"{self._base_url}/backups"

                # Build query params
                params = {}
                if token:
                    params["paginationToken"] = token

                response = await self._http.get(
                    url,
                    headers=self._headers,
                    params=params,
                )

                data = orjson.loads(response.content)
                backups = self._adapter.to_backup_list(data)

                logger.debug(
                    "Successfully fetched backups",
                    backup_count=len(backups),
                    index_name=index_name,
                )

                # Extract next token from response
                pagination = data.get("pagination")
                next_token = pagination.get("next") if pagination else None
                return Page(
                    items=cast("list[BackupInfoProtocol]", backups), pagination_token=next_token
                )

            except Exception:
                logger.exception("Failed to list backups", index_name=index_name)
                raise

        return AsyncPaginator(
            fetch_page=fetch_page,
            initial_token=pagination_token,
            limit=limit,
            item_type="BackupModel",
        )

    async def describe(self, backup_id: str) -> BackupInfoProtocol:
        """Get backup details asynchronously.

        Args:
            backup_id: Unique identifier of the backup.

        Returns:
            BackupInfoProtocol with complete backup metadata including status,
            size, record count, and more.

        Raises:
            ValueError: If backup_id is empty.
            NotFoundError: If backup not found.
            APIError: If operation fails.

        Example:
            >>> info = await pc.backups.describe("8c85e612-ed1c-4f97-9f8c-8194e07bcf71")
            >>> print(f"Status: {info.status}")
            >>> print(f"Size: {info.size_bytes} bytes")
            >>> print(f"Records: {info.record_count}")
        """
        # Validate inputs
        require_non_empty("backup_id", backup_id)

        logger.info("Describing backup", backup_id=backup_id)

        try:
            # Make HTTP request
            response = await self._http.get(
                f"{self._base_url}/backups/{backup_id}",
                headers=self._headers,
            )

            # Transform response
            data = orjson.loads(response.content)
            result: BackupInfoProtocol = self._adapter.to_backup_model(data)

            logger.debug(
                "Successfully described backup",
                backup_id=backup_id,
                status=result.status,
                size_bytes=result.size_bytes,
            )

            return result  # noqa: TRY300

        except Exception:
            logger.exception("Failed to describe backup", backup_id=backup_id)
            raise

    async def delete(self, backup_id: str) -> None:
        """Delete a backup asynchronously.

        Args:
            backup_id: Unique identifier of the backup to delete.

        Raises:
            ValueError: If backup_id is empty.
            NotFoundError: If backup not found.
            PreconditionFailedError: If there's a pending restore job from this backup.
            APIError: If deletion fails.

        Example:
            >>> await pc.backups.delete("8c85e612-ed1c-4f97-9f8c-8194e07bcf71")
        """
        # Validate inputs
        require_non_empty("backup_id", backup_id)

        logger.info("Deleting backup", backup_id=backup_id)

        try:
            # Make HTTP request
            await self._http.delete(
                f"{self._base_url}/backups/{backup_id}",
                headers=self._headers,
            )

            logger.info("Backup deletion initiated", backup_id=backup_id)
            # Note: API returns 202 (accepted, async deletion), no response body

        except Exception:
            logger.exception("Failed to delete backup", backup_id=backup_id)
            raise

    def __repr__(self) -> str:
        """Return developer-friendly representation."""
        return f"AsyncBackups(base_url='{self._base_url}')"
