"""S3-compatible storage backend for backups."""

from __future__ import annotations

import logging
from pathlib import Path
from typing import TYPE_CHECKING, Any

from gaol.backup.exceptions import (
    BackupNotFoundError,
    StorageError,
    StorageNotConfiguredError,
)
from gaol.backup.storage.base import StorageBackend

if TYPE_CHECKING:
    from gaol.backup.models import StorageConfig


logger = logging.getLogger(__name__)


class S3StorageBackend(StorageBackend):
    """S3-compatible storage backend.

    Supports AWS S3 and S3-compatible services (MinIO, DigitalOcean Spaces, etc.).

    Example:
        >>> config = StorageConfig(backend="s3", bucket="my-backups", region="us-east-1")
        >>> storage = S3StorageBackend(config)
        >>> storage.upload(Path("/tmp/backup.tar.gz"), "mycontainer/backup.tar.gz")
        's3://my-backups/mycontainer/backup.tar.gz'

    Note:
        Requires boto3 to be installed: pip install boto3
    """

    def __init__(self, config: StorageConfig) -> None:
        """Initialize S3 storage backend.

        Args:
            config: Storage configuration with bucket, region, etc.
        """
        self._config = config
        self._client: Any = None

    @property
    def name(self) -> str:
        """Return the backend name."""
        return "s3"

    def is_configured(self) -> bool:
        """Check if S3 backend is properly configured."""
        return self._config.bucket is not None

    def _get_client(self) -> Any:
        """Get or create boto3 S3 client."""
        if self._client is not None:
            return self._client

        try:
            import boto3
        except ImportError as e:
            raise StorageNotConfiguredError(
                "s3 (boto3 not installed - run: pip install boto3)"
            ) from e

        if not self._config.bucket:
            raise StorageNotConfiguredError("s3 (no bucket configured)")

        # Build client kwargs
        kwargs: dict[str, Any] = {}
        if self._config.region:
            kwargs["region_name"] = self._config.region
        if self._config.endpoint_url:
            kwargs["endpoint_url"] = self._config.endpoint_url
        if self._config.access_key_id:
            kwargs["aws_access_key_id"] = self._config.access_key_id
        if self._config.secret_access_key:
            kwargs["aws_secret_access_key"] = self._config.secret_access_key

        self._client = boto3.client("s3", **kwargs)
        return self._client

    def _get_key(self, remote_path: str) -> str:
        """Get full S3 key with optional prefix."""
        if self._config.prefix:
            return f"{self._config.prefix.rstrip('/')}/{remote_path}"
        return remote_path

    def upload(
        self,
        local_path: Path,
        remote_path: str,
        metadata: dict[str, str] | None = None,
    ) -> str:
        """Upload a file to S3.

        Args:
            local_path: Source file path
            remote_path: Destination key (relative to prefix)
            metadata: Optional metadata to store with object

        Returns:
            S3 URI of the uploaded file
        """
        client = self._get_client()
        key = self._get_key(remote_path)

        try:
            extra_args: dict[str, Any] = {}
            if metadata:
                extra_args["Metadata"] = metadata

            client.upload_file(
                str(local_path),
                self._config.bucket,
                key,
                ExtraArgs=extra_args if extra_args else None,
            )

            return f"s3://{self._config.bucket}/{key}"

        except Exception as e:
            raise StorageError("s3", "upload", str(e)) from e

    def download(self, remote_path: str, local_path: Path) -> None:
        """Download a file from S3.

        Args:
            remote_path: Source key (relative to prefix)
            local_path: Destination file path
        """
        client = self._get_client()
        key = self._get_key(remote_path)

        try:
            local_path.parent.mkdir(parents=True, exist_ok=True)
            client.download_file(self._config.bucket, key, str(local_path))

        except client.exceptions.NoSuchKey:
            parts = remote_path.split("/", 1)
            container = parts[0] if parts else "unknown"
            backup = parts[1] if len(parts) > 1 else remote_path
            raise BackupNotFoundError(container, backup)
        except Exception as e:
            if "NoSuchKey" in str(e) or "404" in str(e):
                parts = remote_path.split("/", 1)
                container = parts[0] if parts else "unknown"
                backup = parts[1] if len(parts) > 1 else remote_path
                raise BackupNotFoundError(container, backup)
            raise StorageError("s3", "download", str(e)) from e

    def delete(self, remote_path: str) -> None:
        """Delete a file from S3.

        Args:
            remote_path: Key to delete (relative to prefix)
        """
        client = self._get_client()
        key = self._get_key(remote_path)

        try:
            client.delete_object(Bucket=self._config.bucket, Key=key)
        except Exception as e:
            raise StorageError("s3", "delete", str(e)) from e

    def exists(self, remote_path: str) -> bool:
        """Check if a file exists in S3."""
        client = self._get_client()
        key = self._get_key(remote_path)

        try:
            client.head_object(Bucket=self._config.bucket, Key=key)
            return True
        except Exception:
            return False

    def list(self, prefix: str) -> list[str]:
        """List files with the given prefix.

        Args:
            prefix: Key prefix to filter by

        Returns:
            List of keys matching the prefix (relative to configured prefix)
        """
        client = self._get_client()
        full_prefix = self._get_key(prefix)

        result = []
        try:
            paginator = client.get_paginator("list_objects_v2")
            for page in paginator.paginate(
                Bucket=self._config.bucket,
                Prefix=full_prefix,
            ):
                for obj in page.get("Contents", []):
                    key = obj["Key"]
                    # Remove configured prefix to get relative path
                    if self._config.prefix and key.startswith(self._config.prefix):
                        key = key[len(self._config.prefix) :].lstrip("/")
                    result.append(key)

        except Exception as e:
            logger.warning(f"Error listing S3 objects: {e}")

        return sorted(result)

    def get_size(self, remote_path: str) -> int:
        """Get the size of a file in S3."""
        client = self._get_client()
        key = self._get_key(remote_path)

        try:
            response = client.head_object(Bucket=self._config.bucket, Key=key)
            return response["ContentLength"]
        except Exception as e:
            if "NoSuchKey" in str(e) or "404" in str(e):
                parts = remote_path.split("/", 1)
                container = parts[0] if parts else "unknown"
                backup = parts[1] if len(parts) > 1 else remote_path
                raise BackupNotFoundError(container, backup)
            raise StorageError("s3", "get_size", str(e)) from e

    def get_total_size(self, prefix: str) -> int:
        """Get total size of all files with the given prefix."""
        client = self._get_client()
        full_prefix = self._get_key(prefix)

        total = 0
        try:
            paginator = client.get_paginator("list_objects_v2")
            for page in paginator.paginate(
                Bucket=self._config.bucket,
                Prefix=full_prefix,
            ):
                for obj in page.get("Contents", []):
                    total += obj.get("Size", 0)

        except Exception as e:
            logger.warning(f"Error getting total size from S3: {e}")

        return total
