"""
Azure Blob Storage backend.

Provides storage operations using Azure Blob Storage.
"""

from pathlib import Path

from azure.storage.blob import (
    BlobSasPermissions,
    BlobServiceClient,
    ContainerClient,
    generate_blob_sas,
)

from shared.storage.base import (
    FileNotFoundStorageError,
    StorageBackend,
    StorageError,
)


class AzureBlobStorageBackend(StorageBackend):
    """Storage backend using Azure Blob Storage.

    Files are stored as blobs in an Azure Blob Storage container.
    """

    def __init__(
        self,
        connection_string: str,
        container_name: str,
        create_container: bool = False,
    ) -> None:
        """Initialize Azure Blob Storage backend.

        Args:
            connection_string: Azure Storage connection string.
            container_name: Name of the blob container.
            create_container: If True, create the container if it doesn't exist.
        """
        self._connection_string = connection_string
        self._container_name = container_name

        self._blob_service = BlobServiceClient.from_connection_string(connection_string)
        self._container = self._blob_service.get_container_client(container_name)

        # Extract account key from connection string for SAS token generation
        self._account_key = self._extract_account_key(connection_string)

        if create_container and not self._container.exists():
            self._container.create_container()

    @staticmethod
    def _extract_account_key(connection_string: str) -> str | None:
        """Extract account key from connection string.

        Args:
            connection_string: Azure Storage connection string.

        Returns:
            Account key if found, None otherwise.
        """
        for part in connection_string.split(";"):
            if part.startswith("AccountKey="):
                return part[len("AccountKey=") :]
        return None

    @property
    def container_name(self) -> str:
        """Get the container name for this storage backend."""
        return self._container_name

    @property
    def container_client(self) -> ContainerClient:
        """Get the Azure container client."""
        return self._container

    def upload(
        self, local_path: Path, remote_path: str, overwrite: bool = False
    ) -> str:
        """Upload a file to Azure Blob Storage.

        Args:
            local_path: Path to the local file to upload.
            remote_path: Destination blob path.
            overwrite: If True, overwrite existing blob.

        Returns:
            The remote path where the file was stored.

        Raises:
            FileNotFoundStorageError: If local_path doesn't exist.
            StorageError: If blob exists and overwrite is False.
        """
        if not local_path.exists():
            raise FileNotFoundStorageError(str(local_path))

        blob_client = self._container.get_blob_client(remote_path)

        if blob_client.exists() and not overwrite:
            raise StorageError(f"File already exists: {remote_path}")

        with open(local_path, "rb") as f:
            blob_client.upload_blob(f, overwrite=overwrite)

        return remote_path

    def download(self, remote_path: str, local_path: Path) -> Path:
        """Download a blob from Azure Blob Storage.

        Args:
            remote_path: Blob path in storage.
            local_path: Local destination path.

        Returns:
            The local path where the file was downloaded.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        blob_client = self._container.get_blob_client(remote_path)

        if not blob_client.exists():
            raise FileNotFoundStorageError(remote_path)

        local_path.parent.mkdir(parents=True, exist_ok=True)

        stream = blob_client.download_blob()
        local_path.write_bytes(stream.readall())

        return local_path

    def exists(self, remote_path: str) -> bool:
        """Check if a blob exists in storage.

        Args:
            remote_path: Blob path to check.

        Returns:
            True if the blob exists, False otherwise.
        """
        blob_client = self._container.get_blob_client(remote_path)
        return blob_client.exists()

    def list_files(self, prefix: str) -> list[str]:
        """List blobs in storage with given prefix.

        Args:
            prefix: Blob path prefix to filter.

        Returns:
            List of blob paths matching the prefix.
        """
        if prefix:
            blobs = self._container.list_blobs(name_starts_with=prefix)
        else:
            blobs = self._container.list_blobs()

        return [blob.name for blob in blobs]

    def delete(self, remote_path: str) -> bool:
        """Delete a blob from storage.

        Args:
            remote_path: Blob path to delete.

        Returns:
            True if blob was deleted, False if it didn't exist.
        """
        blob_client = self._container.get_blob_client(remote_path)

        if not blob_client.exists():
            return False

        blob_client.delete_blob()
        return True

    def get_url(self, remote_path: str) -> str:
        """Get the URL for a blob.

        Args:
            remote_path: Blob path in storage.

        Returns:
            URL to access the blob.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        blob_client = self._container.get_blob_client(remote_path)

        if not blob_client.exists():
            raise FileNotFoundStorageError(remote_path)

        return blob_client.url

    def upload_bytes(
        self, data: bytes, remote_path: str, overwrite: bool = False
    ) -> str:
        """Upload bytes directly to Azure Blob Storage.

        Args:
            data: Bytes to upload.
            remote_path: Destination blob path.
            overwrite: If True, overwrite existing blob.

        Returns:
            The remote path where the data was stored.
        """
        blob_client = self._container.get_blob_client(remote_path)

        if blob_client.exists() and not overwrite:
            raise StorageError(f"File already exists: {remote_path}")

        blob_client.upload_blob(data, overwrite=overwrite)

        return remote_path

    def download_bytes(self, remote_path: str) -> bytes:
        """Download a blob as bytes.

        Args:
            remote_path: Blob path in storage.

        Returns:
            The blob contents as bytes.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
        """
        blob_client = self._container.get_blob_client(remote_path)

        if not blob_client.exists():
            raise FileNotFoundStorageError(remote_path)

        stream = blob_client.download_blob()
        return stream.readall()

    def upload_directory(
        self, local_dir: Path, remote_prefix: str, overwrite: bool = False
    ) -> list[str]:
        """Upload all files in a directory to Azure Blob Storage.

        Args:
            local_dir: Local directory to upload.
            remote_prefix: Prefix for remote blob paths.
            overwrite: If True, overwrite existing blobs.

        Returns:
            List of remote paths where files were stored.
        """
        uploaded: list[str] = []

        for file_path in local_dir.rglob("*"):
            if file_path.is_file():
                relative_path = file_path.relative_to(local_dir)
                remote_path = f"{remote_prefix}{relative_path}".replace("\\", "/")
                self.upload(file_path, remote_path, overwrite=overwrite)
                uploaded.append(remote_path)

        return uploaded

    def download_directory(
        self, remote_prefix: str, local_dir: Path
    ) -> list[Path]:
        """Download all blobs with a prefix to a local directory.

        Args:
            remote_prefix: Blob path prefix to download.
            local_dir: Local directory to download to.

        Returns:
            List of local paths where files were downloaded.
        """
        downloaded: list[Path] = []

        blobs = self.list_files(remote_prefix)

        for blob_path in blobs:
            # Remove prefix to get relative path
            if remote_prefix:
                relative_path = blob_path[len(remote_prefix):]
                if relative_path.startswith("/"):
                    relative_path = relative_path[1:]
            else:
                relative_path = blob_path

            local_path = local_dir / relative_path
            self.download(blob_path, local_path)
            downloaded.append(local_path)

        return downloaded

    def get_presigned_url(
        self,
        remote_path: str,
        expires_in_seconds: int = 3600,
    ) -> str:
        """Generate a SAS URL for temporary blob access.

        Args:
            remote_path: Blob path in storage.
            expires_in_seconds: SAS token validity duration (1 to 604800 seconds / 7 days).

        Returns:
            Blob URL with SAS token.

        Raises:
            FileNotFoundStorageError: If remote_path doesn't exist.
            ValueError: If expires_in_seconds is out of valid range.
        """
        if expires_in_seconds < 1 or expires_in_seconds > 604800:
            raise ValueError(
                "expires_in_seconds must be between 1 and 604800 (7 days)"
            )

        from datetime import datetime, timedelta, timezone

        blob_client = self._container.get_blob_client(remote_path)

        if not blob_client.exists():
            raise FileNotFoundStorageError(remote_path)

        # Generate SAS token
        sas_token = generate_blob_sas(
            account_name=self._blob_service.account_name,
            container_name=self._container_name,
            blob_name=remote_path,
            account_key=self._account_key,
            permission=BlobSasPermissions(read=True),
            expiry=datetime.now(timezone.utc) + timedelta(seconds=expires_in_seconds),
        )

        return f"{blob_client.url}?{sas_token}"
