"""
Storage helpers for web services.

Provides convenience functions for common storage operations,
wrapping the storage backend with proper path handling using prefixes.
"""

from pathlib import Path
from typing import TYPE_CHECKING
from uuid import uuid4

from shared.storage import PREFIXES, get_storage_backend
from shared.storage.local import LocalStorageBackend

if TYPE_CHECKING:
    from shared.storage.base import StorageBackend


def get_default_storage() -> "StorageBackend":
    """Get the default storage backend.

    Returns:
        Configured StorageBackend instance.
    """
    return get_storage_backend()


class StorageHelper:
    """Helper class for storage operations with prefixes.

    Provides high-level operations for document storage, including
    upload, download, and URL generation with proper path prefixes.
    """

    def __init__(self, storage: "StorageBackend | None" = None) -> None:
        """Initialize storage helper.

        Args:
            storage: Storage backend to use. If None, creates default.
        """
        self._storage = storage or get_default_storage()

    @property
    def storage(self) -> "StorageBackend":
        """Get the underlying storage backend."""
        return self._storage

    # Document operations

    def upload_document(
        self,
        content: bytes,
        filename: str,
        document_id: str | None = None,
    ) -> tuple[str, str]:
        """Upload a document to storage.

        Args:
            content: Document content as bytes.
            filename: Original filename (used for extension).
            document_id: Optional document ID. Generated if not provided.

        Returns:
            Tuple of (document_id, storage_path).
        """
        if document_id is None:
            document_id = str(uuid4())

        ext = Path(filename).suffix.lower() or ".pdf"
        path = PREFIXES.document_path(document_id, ext)
        self._storage.upload_bytes(content, path, overwrite=True)

        return document_id, path

    def download_document(self, document_id: str, extension: str = ".pdf") -> bytes:
        """Download a document from storage.

        Args:
            document_id: Document identifier.
            extension: File extension.

        Returns:
            Document content as bytes.
        """
        path = PREFIXES.document_path(document_id, extension)
        return self._storage.download_bytes(path)

    def get_document_url(
        self,
        document_id: str,
        extension: str = ".pdf",
        expires_in_seconds: int = 3600,
    ) -> str:
        """Get presigned URL for a document.

        Args:
            document_id: Document identifier.
            extension: File extension.
            expires_in_seconds: URL expiration time.

        Returns:
            Presigned URL string.
        """
        path = PREFIXES.document_path(document_id, extension)
        return self._storage.get_presigned_url(path, expires_in_seconds)

    def document_exists(self, document_id: str, extension: str = ".pdf") -> bool:
        """Check if a document exists.

        Args:
            document_id: Document identifier.
            extension: File extension.

        Returns:
            True if document exists.
        """
        path = PREFIXES.document_path(document_id, extension)
        return self._storage.exists(path)

    def delete_document(self, document_id: str, extension: str = ".pdf") -> bool:
        """Delete a document.

        Args:
            document_id: Document identifier.
            extension: File extension.

        Returns:
            True if document was deleted.
        """
        path = PREFIXES.document_path(document_id, extension)
        return self._storage.delete(path)

    # Image operations

    def save_page_image(
        self,
        document_id: str,
        page_num: int,
        content: bytes,
    ) -> str:
        """Save a page image to storage.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).
            content: Image content as bytes.

        Returns:
            Storage path where image was saved.
        """
        path = PREFIXES.image_path(document_id, page_num)
        self._storage.upload_bytes(content, path, overwrite=True)
        return path

    def get_page_image(self, document_id: str, page_num: int) -> bytes:
        """Download a page image.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).

        Returns:
            Image content as bytes.
        """
        path = PREFIXES.image_path(document_id, page_num)
        return self._storage.download_bytes(path)

    def get_page_image_url(
        self,
        document_id: str,
        page_num: int,
        expires_in_seconds: int = 3600,
    ) -> str:
        """Get presigned URL for a page image.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).
            expires_in_seconds: URL expiration time.

        Returns:
            Presigned URL string.
        """
        path = PREFIXES.image_path(document_id, page_num)
        return self._storage.get_presigned_url(path, expires_in_seconds)

    def delete_document_images(self, document_id: str) -> int:
        """Delete all images for a document.

        Args:
            document_id: Document identifier.

        Returns:
            Number of images deleted.
        """
        prefix = f"{PREFIXES.IMAGES}/{document_id}/"
        images = self._storage.list_files(prefix)
        deleted = 0
        for img_path in images:
            if self._storage.delete(img_path):
                deleted += 1
        return deleted

    def list_document_images(self, document_id: str) -> list[str]:
        """List all images for a document.

        Args:
            document_id: Document identifier.

        Returns:
            List of image paths.
        """
        prefix = f"{PREFIXES.IMAGES}/{document_id}/"
        return self._storage.list_files(prefix)

    # Upload staging operations

    def save_upload(
        self,
        content: bytes,
        filename: str,
        subfolder: str | None = None,
    ) -> str:
        """Save a file to upload staging area.

        Args:
            content: File content as bytes.
            filename: Filename to save as.
            subfolder: Optional subfolder (e.g., "async").

        Returns:
            Storage path where file was saved.
        """
        path = PREFIXES.upload_path(filename, subfolder)
        self._storage.upload_bytes(content, path, overwrite=True)
        return path

    def get_upload(self, filename: str, subfolder: str | None = None) -> bytes:
        """Get a file from upload staging area.

        Args:
            filename: Filename to retrieve.
            subfolder: Optional subfolder.

        Returns:
            File content as bytes.
        """
        path = PREFIXES.upload_path(filename, subfolder)
        return self._storage.download_bytes(path)

    def delete_upload(self, filename: str, subfolder: str | None = None) -> bool:
        """Delete a file from upload staging area.

        Args:
            filename: Filename to delete.
            subfolder: Optional subfolder.

        Returns:
            True if file was deleted.
        """
        path = PREFIXES.upload_path(filename, subfolder)
        return self._storage.delete(path)

    # Result operations

    def save_result(self, content: bytes, filename: str) -> str:
        """Save a result file.

        Args:
            content: File content as bytes.
            filename: Filename to save as.

        Returns:
            Storage path where file was saved.
        """
        path = PREFIXES.result_path(filename)
        self._storage.upload_bytes(content, path, overwrite=True)
        return path

    def get_result(self, filename: str) -> bytes:
        """Get a result file.

        Args:
            filename: Filename to retrieve.

        Returns:
            File content as bytes.
        """
        path = PREFIXES.result_path(filename)
        return self._storage.download_bytes(path)

    def get_result_url(self, filename: str, expires_in_seconds: int = 3600) -> str:
        """Get presigned URL for a result file.

        Args:
            filename: Filename.
            expires_in_seconds: URL expiration time.

        Returns:
            Presigned URL string.
        """
        path = PREFIXES.result_path(filename)
        return self._storage.get_presigned_url(path, expires_in_seconds)

    def result_exists(self, filename: str) -> bool:
        """Check if a result file exists.

        Args:
            filename: Filename to check.

        Returns:
            True if file exists.
        """
        path = PREFIXES.result_path(filename)
        return self._storage.exists(path)

    def delete_result(self, filename: str) -> bool:
        """Delete a result file.

        Args:
            filename: Filename to delete.

        Returns:
            True if file was deleted.
        """
        path = PREFIXES.result_path(filename)
        return self._storage.delete(path)

    # Export operations

    def save_export(self, content: bytes, export_id: str, filename: str) -> str:
        """Save an export file.

        Args:
            content: File content as bytes.
            export_id: Export identifier.
            filename: Filename to save as.

        Returns:
            Storage path where file was saved.
        """
        path = PREFIXES.export_path(export_id, filename)
        self._storage.upload_bytes(content, path, overwrite=True)
        return path

    def get_export_url(
        self,
        export_id: str,
        filename: str,
        expires_in_seconds: int = 3600,
    ) -> str:
        """Get presigned URL for an export file.

        Args:
            export_id: Export identifier.
            filename: Filename.
            expires_in_seconds: URL expiration time.

        Returns:
            Presigned URL string.
        """
        path = PREFIXES.export_path(export_id, filename)
        return self._storage.get_presigned_url(path, expires_in_seconds)

    # Admin image operations

    def get_admin_image_path(self, document_id: str, page_num: int) -> str:
        """Get the storage path for an admin image.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).

        Returns:
            Storage path like "admin_images/doc123/page_1.png"
        """
        return f"{PREFIXES.ADMIN_IMAGES}/{document_id}/page_{page_num}.png"

    def save_admin_image(
        self,
        document_id: str,
        page_num: int,
        content: bytes,
    ) -> str:
        """Save an admin page image to storage.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).
            content: Image content as bytes.

        Returns:
            Storage path where image was saved.
        """
        path = self.get_admin_image_path(document_id, page_num)
        self._storage.upload_bytes(content, path, overwrite=True)
        return path

    def get_admin_image(self, document_id: str, page_num: int) -> bytes:
        """Download an admin page image.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).

        Returns:
            Image content as bytes.
        """
        path = self.get_admin_image_path(document_id, page_num)
        return self._storage.download_bytes(path)

    def get_admin_image_url(
        self,
        document_id: str,
        page_num: int,
        expires_in_seconds: int = 3600,
    ) -> str:
        """Get presigned URL for an admin page image.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).
            expires_in_seconds: URL expiration time.

        Returns:
            Presigned URL string.
        """
        path = self.get_admin_image_path(document_id, page_num)
        return self._storage.get_presigned_url(path, expires_in_seconds)

    def admin_image_exists(self, document_id: str, page_num: int) -> bool:
        """Check if an admin page image exists.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).

        Returns:
            True if image exists.
        """
        path = self.get_admin_image_path(document_id, page_num)
        return self._storage.exists(path)

    def list_admin_images(self, document_id: str) -> list[str]:
        """List all admin images for a document.

        Args:
            document_id: Document identifier.

        Returns:
            List of image paths.
        """
        prefix = f"{PREFIXES.ADMIN_IMAGES}/{document_id}/"
        return self._storage.list_files(prefix)

    def delete_admin_images(self, document_id: str) -> int:
        """Delete all admin images for a document.

        Args:
            document_id: Document identifier.

        Returns:
            Number of images deleted.
        """
        prefix = f"{PREFIXES.ADMIN_IMAGES}/{document_id}/"
        images = self._storage.list_files(prefix)
        deleted = 0
        for img_path in images:
            if self._storage.delete(img_path):
                deleted += 1
        return deleted

    def get_admin_image_local_path(
        self, document_id: str, page_num: int
    ) -> Path | None:
        """Get the local filesystem path for an admin image.

        This method is useful for serving files via FileResponse.
        Only works with LocalStorageBackend; returns None for cloud storage.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).

        Returns:
            Path object if using local storage and file exists, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            # Cloud storage - cannot get local path
            return None

        remote_path = self.get_admin_image_path(document_id, page_num)
        try:
            full_path = self._storage._get_full_path(remote_path)
            if full_path.exists():
                return full_path
            return None
        except Exception:
            return None

    def get_admin_image_dimensions(
        self, document_id: str, page_num: int
    ) -> tuple[int, int] | None:
        """Get the dimensions (width, height) of an admin image.

        This method is useful for normalizing bounding box coordinates.

        Args:
            document_id: Document identifier.
            page_num: Page number (1-indexed).

        Returns:
            Tuple of (width, height) if image exists, None otherwise.
        """
        from PIL import Image

        # Try local path first for efficiency
        local_path = self.get_admin_image_local_path(document_id, page_num)
        if local_path is not None:
            with Image.open(local_path) as img:
                return img.size

        # Fall back to downloading for cloud storage
        if not self.admin_image_exists(document_id, page_num):
            return None

        try:
            import io
            image_bytes = self.get_admin_image(document_id, page_num)
            with Image.open(io.BytesIO(image_bytes)) as img:
                return img.size
        except Exception:
            return None

    # Raw PDF operations (legacy compatibility)

    def save_raw_pdf(self, content: bytes, filename: str) -> str:
        """Save a raw PDF for auto-labeling pipeline.

        Args:
            content: PDF content as bytes.
            filename: Filename to save as.

        Returns:
            Storage path where file was saved.
        """
        path = f"{PREFIXES.RAW_PDFS}/{filename}"
        self._storage.upload_bytes(content, path, overwrite=True)
        return path

    def get_raw_pdf(self, filename: str) -> bytes:
        """Get a raw PDF from storage.

        Args:
            filename: Filename to retrieve.

        Returns:
            PDF content as bytes.
        """
        path = f"{PREFIXES.RAW_PDFS}/{filename}"
        return self._storage.download_bytes(path)

    def raw_pdf_exists(self, filename: str) -> bool:
        """Check if a raw PDF exists.

        Args:
            filename: Filename to check.

        Returns:
            True if file exists.
        """
        path = f"{PREFIXES.RAW_PDFS}/{filename}"
        return self._storage.exists(path)

    def get_raw_pdf_local_path(self, filename: str) -> Path | None:
        """Get the local filesystem path for a raw PDF.

        Only works with LocalStorageBackend; returns None for cloud storage.

        Args:
            filename: Filename to retrieve.

        Returns:
            Path object if using local storage and file exists, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        path = f"{PREFIXES.RAW_PDFS}/{filename}"
        try:
            full_path = self._storage._get_full_path(path)
            if full_path.exists():
                return full_path
            return None
        except Exception:
            return None

    def get_raw_pdf_path(self, filename: str) -> str:
        """Get the storage path for a raw PDF (not the local filesystem path).

        Args:
            filename: Filename.

        Returns:
            Storage path like "raw_pdfs/filename.pdf"
        """
        return f"{PREFIXES.RAW_PDFS}/{filename}"

    # Result local path operations

    def get_result_local_path(self, filename: str) -> Path | None:
        """Get the local filesystem path for a result file.

        Only works with LocalStorageBackend; returns None for cloud storage.

        Args:
            filename: Filename to retrieve.

        Returns:
            Path object if using local storage and file exists, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        path = PREFIXES.result_path(filename)
        try:
            full_path = self._storage._get_full_path(path)
            if full_path.exists():
                return full_path
            return None
        except Exception:
            return None

    def get_results_base_path(self) -> Path | None:
        """Get the base directory path for results (local storage only).

        Used for mounting static file directories.

        Returns:
            Path to results directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            base_path = self._storage._get_full_path(PREFIXES.RESULTS)
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None

    # Upload local path operations

    def get_upload_local_path(
        self, filename: str, subfolder: str | None = None
    ) -> Path | None:
        """Get the local filesystem path for an upload file.

        Only works with LocalStorageBackend; returns None for cloud storage.

        Args:
            filename: Filename to retrieve.
            subfolder: Optional subfolder.

        Returns:
            Path object if using local storage and file exists, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        path = PREFIXES.upload_path(filename, subfolder)
        try:
            full_path = self._storage._get_full_path(path)
            if full_path.exists():
                return full_path
            return None
        except Exception:
            return None

    def get_uploads_base_path(self, subfolder: str | None = None) -> Path | None:
        """Get the base directory path for uploads (local storage only).

        Args:
            subfolder: Optional subfolder (e.g., "async").

        Returns:
            Path to uploads directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            if subfolder:
                base_path = self._storage._get_full_path(f"{PREFIXES.UPLOADS}/{subfolder}")
            else:
                base_path = self._storage._get_full_path(PREFIXES.UPLOADS)
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None

    def upload_exists(self, filename: str, subfolder: str | None = None) -> bool:
        """Check if an upload file exists.

        Args:
            filename: Filename to check.
            subfolder: Optional subfolder.

        Returns:
            True if file exists.
        """
        path = PREFIXES.upload_path(filename, subfolder)
        return self._storage.exists(path)

    # Dataset operations

    def get_datasets_base_path(self) -> Path | None:
        """Get the base directory path for datasets (local storage only).

        Returns:
            Path to datasets directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            base_path = self._storage._get_full_path(PREFIXES.DATASETS)
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None

    def get_admin_images_base_path(self) -> Path | None:
        """Get the base directory path for admin images (local storage only).

        Returns:
            Path to admin_images directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            base_path = self._storage._get_full_path(PREFIXES.ADMIN_IMAGES)
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None

    def get_raw_pdfs_base_path(self) -> Path | None:
        """Get the base directory path for raw PDFs (local storage only).

        Returns:
            Path to raw_pdfs directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            base_path = self._storage._get_full_path(PREFIXES.RAW_PDFS)
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None

    def get_autolabel_output_path(self) -> Path | None:
        """Get the directory path for autolabel output (local storage only).

        Returns:
            Path to autolabel_output directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            # Use a subfolder under results for autolabel output
            base_path = self._storage._get_full_path("autolabel_output")
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None

    def get_training_data_path(self) -> Path | None:
        """Get the directory path for training data exports (local storage only).

        Returns:
            Path to training directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            base_path = self._storage._get_full_path("training")
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None

    def get_exports_base_path(self) -> Path | None:
        """Get the base directory path for exports (local storage only).

        Returns:
            Path to exports directory if using local storage, None otherwise.
        """
        if not isinstance(self._storage, LocalStorageBackend):
            return None

        try:
            base_path = self._storage._get_full_path(PREFIXES.EXPORTS)
            base_path.mkdir(parents=True, exist_ok=True)
            return base_path
        except Exception:
            return None


# Default instance for convenience
_default_helper: StorageHelper | None = None


def get_storage_helper() -> StorageHelper:
    """Get the default storage helper instance.

    Creates the helper on first call with default storage backend.

    Returns:
        Default StorageHelper instance.
    """
    global _default_helper
    if _default_helper is None:
        _default_helper = StorageHelper()
    return _default_helper
