"""
author: ResetXD <resetwastakenwastaken@gmail.com>
license: MIT
"""

from io import BufferedIOBase, BytesIO, IOBase
from os import PathLike
from typing import Optional, Union

from aiohttp import ClientSession
from PIL import Image, ImageDraw, ImageFont
from .error import InvalidImageType, InvalidImageUrl
from pathlib import Path

from io import BufferedIOBase
from os import PathLike
from typing import Optional, Union

class CardSettings:
    """
    Represents the settings for the rank card

    Parameters
    ----------
    background: :class:`Optional[Union[PathLike, BufferedIOBase, str]]`
        The background image for the rank card. This can be a path to a file or a file-like object in `rb` mode or URL

    bar_color: :class:`Optional[str]`
        The color of the XP bar. This can be a hex code or a color name. Default is `white`
    
    text_color: :class:`Optional[str]`
        The color of the text. This can be a hex code or a color name. Default is `white`
    
    background_color: :class:`Optional[str]`
        The color of the background. This can be a hex code or a color name. Default is `#36393f`

    Attributes
    ----------
    - `background`
    - `bar_color`
    - `text_color`
    - `background_color`
    """

    __slots__ = ('background', 'bar_color', 'text_color', 'background_color')

    def __init__(
        self,
        background: Optional[Union[PathLike, BufferedIOBase, str]]=None,
        background_color: Optional[str]= "#36393f",
        bar_color: Optional[str] = 'white',
        text_color: Optional[str] = 'white'
    ) -> None:
        self.background = background
        self.bar_color = bar_color
        self.text_color = text_color
        self.background_color = background_color


class RankCard:
    """Class for creating a rank cards

    Parameters
    ----------
    CardSettings: :class:`CardSettings`
        The settings for the rank card

    avatar: :class:`str`
        The avatar image for the rank card. This must be a URL
    
    level: :class:`int`
        The level of the member
    
    username: :class:`str`
        The username of the member
    
    current_exp: :class:`int`
        The current amount of XP the member has
    
    max_exp: :class:`int`
        The amount of XP required for the member to level up
    
    rank: Optional[:class:`int`]
        The rank of the member.
    
    

    Attributes
    ----------
    - `settings`
    - `avatar`
    - `level`
    - `username`
    - `current_exp`
    - `max_exp`
    - `rank`

    Raises
    ------
    - `InvalidImageType`
        If the image type is not supported
    - `InvalidImageUrl`
        If the image url is invalid

    """

    __slots__ = ('background', 'rank', 'background_color', 'text_color', 'bar_color', 'settings', 'avatar', 'level', 'username', 'current_exp', 'max_exp')



    def __init__(
        self,
        settings: CardSettings,
        avatar: str,
        level:int,
        username:str,
        current_exp:int,
        max_exp:int,
        rank:Optional[int] = None,
    )-> None:
        self.background = settings.background
        self.background_color = settings.background_color
        self.avatar = avatar
        self.level = level
        self.rank = rank
        self.username = username
        self.current_exp = current_exp
        self.max_exp = max_exp
        self.bar_color = settings.bar_color
        self.text_color = settings.text_color

    @staticmethod
    def _convert_number(number: int) -> str:
        if number >= 1000000000:
            return f"{number / 1000000000:.1f}B"
        elif number >= 1000000:
            return f"{number / 1000000:.1f}M"
        elif number >= 1000:
            return f"{number / 1000:.1f}K"
        else:
            return str(number)

    @staticmethod
    async def _image(url:str):
        async with ClientSession()   as session:
            async with session.get(url) as response:
                if response.status != 200:
                    raise InvalidImageUrl(f"Invalid image url: {url}")
                data = await response.read()
                return Image.open(BytesIO(data))


    async def card1(self)-> Union[None, bytes]:
        """
        Creates the rank card and returns `bytes`
        
        ![card](https://cdn.discordapp.com/attachments/907213435358547968/1019966057294860328/final.png)
        """
        path = str(Path(__file__).parent)
        if isinstance(self.background, IOBase):
            if not (self.background.seekable() and self.background.readable() and self.background.mode == "rb"):
                raise InvalidImageType(f"File buffer {self.background!r} must be seekable and readable and in binary mode")
            self.background = Image.open(self.background)
        elif isinstance(self.background, str):
            if self.background.startswith("http"):
                self.background = await RankCard._image(self.background)
            else:
                self.background = Image.open(open(self.background, "rb"))
        else:
            raise InvalidImageType(f"background must be a path or url or a file buffer, not {type(self.background)}") 

        if isinstance(self.avatar, str):
            if self.avatar.startswith("http"):
                self.avatar = await RankCard._image(self.avatar)
        else:
            raise TypeError(f"avatar must be a url, not {type(self.avatar)}") 

        self.avatar = self.avatar.resize((170,170))

        overlay = Image.open(path + "/assets/overlay1.png")
        background = Image.new("RGBA", overlay.size)
        backgroundover = self.background.resize((638,159))
        background.paste(backgroundover,(0,0))
        
        self.background = background.resize(overlay.size)
        self.background.paste(overlay,(0,0),overlay)

        myFont = ImageFont.truetype(path + "/assets/levelfont.otf",40)
        draw = ImageDraw.Draw(self.background)

        draw.text((205,(327/2)+20), self.username,font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))
        bar_exp = (self.current_exp/self.max_exp)*420
        if bar_exp <= 50:
            bar_exp = 50    

        current_exp = RankCard._convert_number(self.current_exp)
        
        max_exp = RankCard._convert_number(self.max_exp)
        

        myFont = ImageFont.truetype(path + "/assets/levelfont.otf",30)
        draw.text((197,(327/2)+125), f"LEVEL - {RankCard._convert_number(self.level)}",font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))

        w,_ = draw.textsize(f"{current_exp}/{max_exp}", font=myFont)
        draw.text((638-w-50,(327/2)+125), f"{current_exp}/{max_exp}",font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))

        mask_im = Image.open(path + "/assets/mask_circle.jpg").convert('L').resize((170,170))
        new = Image.new("RGB", self.avatar.size, (0, 0, 0))
        try:
            new.paste(self.avatar, mask=self.avatar.convert("RGBA").split()[3])
        except Exception as e:
            print(e)
            new.paste(self.avatar, (0,0))
        self.background.paste(new, (13, 65), mask_im)

        im = Image.new("RGB", (490, 51), (0, 0, 0))
        draw = ImageDraw.Draw(im, "RGBA")
        draw.rounded_rectangle((0, 0, 420, 50), 30, fill=(255,255,255,50))
        draw.rounded_rectangle((0, 0, bar_exp, 50), 30, fill=self.bar_color)
        self.background.paste(im, (190, 235))
        new = Image.new("RGBA", self.background.size)
        new.paste(self.background,(0, 0), Image.open(path + "/assets/curvedoverlay.png").convert("L"))
        self.background = new.resize((505, 259))

        image = BytesIO()
        self.background.save(image, 'PNG')
        image.seek(0)
        return image


    async def card2(self)-> Union[None, bytes]:
        """
        Creates the rank card and returns `bytes`
        
        ![card](https://cdn.discordapp.com/attachments/907213435358547968/1020968412144480316/final.png)
        """
        path = str(Path(__file__).parent)

        if isinstance(self.avatar, str):
            if self.avatar.startswith("http"):
                self.avatar = await RankCard._image(self.avatar)
        else:
            raise TypeError(f"avatar must be a url or a file buffer, not {type(self.background)}") 

        background = Image.new("RGB", (1000, 333), self.background_color)
        background.paste(Image.new("RGB", (950, 333-50), "#2f3136"), (25, 25) )

        avatar = self.avatar.resize((260, 260))

        mask = Image.open(path + "/assets/curveborder.png").resize((260, 260))

        new = Image.new("RGBA", avatar.size, (0, 0, 0))
        try:
            new.paste(avatar, mask=avatar.convert("RGBA").split()[3])
        except:
            new.paste(avatar, (0,0))
        
        background.paste(new, (53, 73//2), mask.convert("L"))

        # background.paste(avatar, (53, 73//2), mask.convert("L"))

        myFont = ImageFont.truetype(path + "/assets/levelfont.otf",50)
        draw = ImageDraw.Draw(background)

        if self.rank is not None:
            combined = "LEVEL: " + self._convert_number(self.level) + "       " + "RANK: " + str(self.rank)
        else:
            combined = "LEVEL: " + self._convert_number(self.level)
        w = draw.textlength(combined, font=myFont)
        draw.text((950-w,40), combined,font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))
        draw.text((330,130), self.username,font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))

        exp = f"{self._convert_number(self.current_exp)}/{self._convert_number(self.max_exp)}"
        w = draw.textlength(exp, font=myFont)
        draw.text((950-w,130), exp,font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))

        bar_exp = (self.current_exp/self.max_exp)*619
        if bar_exp <= 50:
            bar_exp = 50  

        im = Image.new("RGB", (620, 51), "#2f3136")
        draw = ImageDraw.Draw(im, "RGBA")
        draw.rounded_rectangle((0, 0, 619, 50), 30, fill=(255,255,255,50))
        draw.rounded_rectangle((0, 0, bar_exp, 50), 30, fill=self.bar_color)
        background.paste(im, (330, 235))

        image = BytesIO()
        background.save(image, 'PNG')
        image.seek(0)
        return image

    async def card3(self)-> Union[None, bytes]:
        """
        Creates the rank card and returns `bytes`
        
        ![card](https://cdn.discordapp.com/attachments/1018936393659076668/1022149875544113172/rank.png)
        """
        path = str(Path(__file__).parent)

        if isinstance(self.background, IOBase):
            if not (self.background.seekable() and self.background.readable() and self.background.mode == "rb"):
                raise InvalidImageType(f"File buffer {self.background!r} must be seekable and readable and in binary mode")
            self.background = Image.open(self.background)
        elif isinstance(self.background, str):
            if self.background.startswith("http"):
                self.background = await RankCard._image(self.background)
            else:
                self.background = Image.open(open(self.background, "rb"))
        else:
            raise InvalidImageType(f"background must be a path or url or a file buffer, not {type(self.background)}") 

        if isinstance(self.avatar, str):
            if self.avatar.startswith("http"):
                self.avatar = await RankCard._image(self.avatar)
        else:
            raise TypeError(f"avatar must be a url or a file buffer, not {type(self.background)}") 

        background = self.background.resize((1000, 333))
        cut = Image.new("RGBA", (950, 333-50) , (0, 0, 0, 200))
        background.paste(cut, (25, 25) ,cut)

        avatar = self.avatar.resize((260, 260))

        mask = Image.open(path + "/assets/curveborder.png").resize((260, 260))

        new = Image.new("RGBA", avatar.size, (0, 0, 0))
        try:
            new.paste(avatar, mask=avatar.convert("RGBA").split()[3])
        except:
            new.paste(avatar, (0,0))
        
        background.paste(new, (53, 73//2), mask.convert("L"))
        myFont = ImageFont.truetype(path + "/assets/levelfont.otf",50)
        draw = ImageDraw.Draw(background)

        if self.rank is not None:
            combined = "LEVEL: " + self._convert_number(self.level) + "       " + "RANK: " + str(self.rank)
        else:
            combined = "LEVEL: " + self._convert_number(self.level)
        w = draw.textlength(combined, font=myFont)
        draw.text((950-w,40), combined,font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))
        draw.text((330,130), self.username,font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))

        exp = f"{self._convert_number(self.current_exp)}/{self._convert_number(self.max_exp)}"
        w = draw.textlength(exp, font=myFont)
        draw.text((950-w,130), exp,font=myFont, fill=self.text_color,stroke_width=1,stroke_fill=(0, 0, 0))

        bar_exp = (self.current_exp/self.max_exp)*619
        if bar_exp <= 50:
            bar_exp = 50  

        im = Image.new("RGBA", (620, 51))
        draw = ImageDraw.Draw(im, "RGBA")
        draw.rounded_rectangle((0, 0, 619, 50), 30, fill=(255,255,255,225))
        draw.rounded_rectangle((0, 0, bar_exp, 50), 30, fill=self.bar_color)
        background.paste(im, (330, 235))

        image = BytesIO()
        background.save(image, 'PNG')
        image.seek(0)
        return image