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

This module provides the implementation for backup management operations including
creating, listing, describing, and deleting backups of serverless indexes.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, cast

import msgspec
import orjson

from pinecone.__interface__.backups import BackupNamespaceProtocol
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 Page, Paginator


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


__all__ = ["Backups"]

logger = get_logger(__name__)


class Backups(InteractiveIntrospectionMixin, BackupNamespaceProtocol):
    """Implementation of backup management operations.

    This class demonstrates the canonical patterns for SDK implementation:
    - Protocol satisfaction with runtime checking
    - HTTP client usage for all API calls (when implemented)
    - Comprehensive error handling with context
    - Structured logging at key operation points
    - Clear documentation with runnable examples

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

    Example:
        >>> from pinecone import Pinecone
        >>> pc = Pinecone(api_key="...")
        >>> backups = pc.backups
        >>>
        >>> # Create a backup
        >>> backup = backups.create(
        ...     index_name="my-index",
        ...     name="example-backup",
        ...     description="Monthly backup of production index",
        ... )
        >>> print(f"Backup ID: {backup.backup_id}")
        >>>
        >>> # List all backups
        >>> for backup in backups.list():
        ...     print(f"{backup.backup_id}: {backup.status}")
        >>>
        >>> # Describe a backup
        >>> info = backups.describe("8c85e612-ed1c-4f97-9f8c-8194e07bcf71")
        >>> print(f"Records: {info.record_count}")
        >>>
        >>> # Delete a backup
        >>> backups.delete("8c85e612-ed1c-4f97-9f8c-8194e07bcf71")
    """

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

        Args:
            http_client: 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}

    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.

        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 = 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 time
            >>> while True:
            ...     info = pc.backups.describe(backup.backup_id)
            ...     if info.status == "Ready":
            ...         break
            ...     time.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 = 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,
    ) -> Paginator[BackupInfoProtocol]:
        """List backups for a project or specific index.

        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:
            Paginator[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
            >>> for backup in pc.backups.list():
            ...     print(f"{backup.backup_id}: {backup.status}")

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

        Example with page-level access:
            >>> 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,
        )

        def fetch_page(token: str | None) -> Page[BackupInfoProtocol]:
            """Fetch a page of backups."""
            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 = 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 Paginator(
            fetch_page=fetch_page,
            initial_token=pagination_token,
            limit=limit,
            item_type="BackupModel",
        )

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

        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 = 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 = 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

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

        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:
            >>> 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
            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"Backups(base_url='{self._base_url}')"
