from typing import TYPE_CHECKING, Optional, Union

from naff.client.utils.attr_utils import define, field
from naff.client.utils.serializer import no_export_meta

if TYPE_CHECKING:
    from os import PathLike

    from naff.client import Client

__all__ = ("Asset",)


@define(kw_only=False)
class Asset:
    """
    Represents a discord asset.

    Attributes:
        BASE str: The `cdn` address for assets
        url str: The URL of this asset
        hash Optional[str]: The hash of this asset

    """

    BASE = "https://cdn.discordapp.com"

    _client: "Client" = field(metadata=no_export_meta)
    _url: str = field(repr=True)
    hash: Optional[str] = field(repr=True, default=None)

    @classmethod
    def from_path_hash(cls, client: "Client", path: str, asset_hash: str) -> "Asset":
        """
        Create an asset from a path and asset's hash.

        Args:
            client: The NAFF bot instance
            path: The CDN Endpoints for the type of asset.
            asset_hash: The hash representation of the target asset.

        Returns:
            A new Asset object

        """
        url = f"{cls.BASE}/{path.format(asset_hash)}"
        return cls(client=client, url=url, hash=asset_hash)

    @property
    def url(self) -> str:
        """The URL of this asset."""
        ext = ".gif" if self.animated else ".png"
        return f"{self._url}{ext}?size=4096"

    def as_url(self, *, extension: str | None = None, size: int = 4096) -> str:
        """
        Get the url of this asset.

        args:
            extension: The extension to override the assets default with
            size: The size of asset to return

        returns:
            A url for this asset with the given parameters
        """
        if not extension:
            extension = ".gif" if self.animated else ".png"
        elif not extension.startswith("."):
            extension = f".{extension}"

        return f"{self._url}{extension}?size={size}"

    @property
    def animated(self) -> bool:
        """True if this asset is animated."""
        return bool(self.hash) and self.hash.startswith("a_")

    async def fetch(self, extension: Optional[str] = None, size: Optional[int] = None) -> bytes:
        """
        Fetch the asset from the Discord CDN.

        Args:
            extension: File extension based on the target image format
            size: The image size, can be any power of two between 16 and 4096.

        Returns:
            Raw byte array of the file

        Raises:
            ValueError: Incorrect file size if not power of 2 between 16 and 4096

        """
        if not extension:
            extension = ".gif" if self.animated else ".png"

        url = self._url + extension

        if size:
            if not ((size != 0) and (size & (size - 1) == 0)):  # if not power of 2
                raise ValueError("Size should be a power of 2")
            if not 16 <= size <= 4096:
                raise ValueError("Size should be between 16 and 4096")

            url = f"{url}?size={size}"

        return await self._client.http.request_cdn(url, self)

    async def save(
        self, fd: Union[str, bytes, "PathLike", int], extension: Optional[str] = None, size: Optional[int] = None
    ) -> int:
        """
        Save the asset to a local file.

        Args:
            fd: Destination path to save the file to.
            extension: File extension based on the target image format.
            size: The image size, can be any power of two between 16 and 4096.

        Returns:
            Status code result of file write

        """
        content = await self.fetch(extension=extension, size=size)
        with open(fd, "wb") as f:
            return f.write(content)
