import asyncio
import json
import os
from typing import Optional, Union, Tuple, List, Dict, Any

from PIL import Image, ImageDraw, ImageFont
from PIL.Image import Resampling
from emoji import is_emoji

from .PicGenerator import Color, PicGenerator
from ..utils import config
from ..utils.network import request
from ..utils.utils import open_url_image, timestamp_format, split_list, limit_str_length, \
    mask_round, mask_rounded_rectangle


class DynamicPicGenerator:
    """
    动态图片生成器
    """
    __resource_base_path = os.path.dirname(os.path.dirname(__file__))

    @classmethod
    async def generate(cls, param: Dict[str, Any]) -> str:
        """
        根据传入动态信息生成动态图片

        Args:
            param: 动态信息

        Returns:
            动态图片的 Base64 字符串
        """
        width = 740
        height = 100000
        text_margin = 25
        img_margin = 10
        generator = PicGenerator(width, height)
        pic = generator.set_pos(175, 60).draw_rounded_rectangle(0, 0, width, height, 35, Color.WHITE).copy_bottom(35)

        # 提取参数
        desc = param["desc"]
        dynamic_id = desc["dynamic_id"]
        origin_dynamic_id = desc["orig_dy_id"] if "orig_dy_id" in desc else None
        dynamic_type = desc["type"]

        user_profile = desc["user_profile"]
        card = json.loads(param["card"])
        display = param["display"]

        # 动态头部
        face, pendant = await asyncio.gather(open_url_image(user_profile["info"]["face"]),
                                             open_url_image(user_profile["pendant"]["image"]))
        official = user_profile["card"]["official_verify"]["type"]
        vip = user_profile["vip"]["nickname_color"] != ""
        uname = user_profile["info"]["uname"]
        timestamp = desc["timestamp"]

        await cls.__draw_header(pic, face, pendant, official, vip, uname, timestamp)

        # 动态主体
        pic.set_pos(x=text_margin)
        pic.set_row_space(10)

        await cls.__draw_by_type(pic, dynamic_type, card, dynamic_id, display,
                                 text_margin, img_margin, False, origin_dynamic_id)

        # 底部版权信息，请务必保留此处
        pic.move_pos(0, 15)
        pic.draw_text_right(25, "Designed By StarBot", Color.GRAY)
        pic.draw_text_right(25, "https://github.com/Starlwr/StarBot", Color.LINK)
        pic.crop_and_paste_bottom()

        return pic.base64()

    @classmethod
    def __remove_illegal_char(cls, s: str) -> str:
        """
        移除动态中的非法字符

        Args:
            s: 源字符串

        Returns:
            移除非法字符后的字符串
        """
        return s.replace(chr(8203), "").replace(chr(65039), "")

    @classmethod
    async def __draw_header(cls,
                            pic: PicGenerator,
                            face: Image.Image,
                            pendant: Image.Image,
                            official: int,
                            vip: bool,
                            uname: str,
                            timestamp: int) -> PicGenerator:
        """
        绘制动态头部

        Args:
            pic: 绘图器实例
            face: 头像图片
            pendant: 头像挂件图片
            official: 认证类型
            vip: 是否为大会员
            uname: 昵称
            timestamp: 动态时间戳
        """
        face_size = (100, 100)
        face = face.resize(face_size, Resampling.LANCZOS).convert("RGBA")
        face = mask_round(face)
        pic.draw_img_alpha(face, (50, 50))

        if pendant is not None:
            pendant_size = (170, 170)
            pendant = pendant.resize(pendant_size, Resampling.LANCZOS).convert('RGBA')
            pic.draw_img_alpha(pendant, (15, 15))
            pic.move_pos(15, 0)

        if official == 0:
            pic.draw_img_alpha(Image.open(f"{cls.__resource_base_path}/resource/personal.png"), (118, 118))
        elif official == 1:
            pic.draw_img_alpha(Image.open(f"{cls.__resource_base_path}/resource/business.png"), (118, 118))
        elif vip:
            pic.draw_img_alpha(Image.open(f"{cls.__resource_base_path}/resource/vip.png"), (118, 118))

        if vip:
            pic.draw_text(uname, Color.PINK)
        else:
            pic.draw_text(uname, Color.BLACK)
        pic.draw_tip(timestamp_format(timestamp, "%Y-%m-%d %H:%M"))

        pic.set_pos(y=200)

        return pic

    @classmethod
    async def __draw_by_type(cls,
                             pic: PicGenerator,
                             dynamic_type: int,
                             card: Dict[str, Any],
                             dynamic_id: int,
                             display: Dict[str, Any],
                             text_margin: int,
                             img_margin: int,
                             forward: bool,
                             origin_dynamic_id: Optional[int] = None):
        """
        根据动态类型绘制动态图片

        Args:
            pic: 绘图器实例
            dynamic_type: 动态类型
            card: 动态信息
            dynamic_id: 动态 ID
            display: 动态绘制附加信息
            text_margin: 文字外边距
            img_margin: 图片外边距
            forward: 当前是否为转发动态的源动态
        """

        async def download_img(mod: Dict[str, Any]):
            """
            下载表情图片

            Args:
                mod: 表情区块字典
            """
            mod["img"] = await open_url_image(mod["emoji"]["icon_url"])

        modules_url = f"https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?timezone_offset=-480&id={dynamic_id}"
        modules = (await request("GET", modules_url))["item"]["modules"]["module_dynamic"]["desc"]
        modules = modules["rich_text_nodes"] if modules else []

        # 下载表情
        download_picture_tasks = []
        for module in modules:
            if module["type"] == "RICH_TEXT_NODE_TYPE_EMOJI":
                download_picture_tasks.append(download_img(module))
        await asyncio.gather(*download_picture_tasks)

        if dynamic_type == 1:
            # 转发动态
            await cls.__draw_content(pic, modules, text_margin, forward)

            if "origin" in card:
                origin = json.loads(card["origin"])
                origin_type = card["item"]["orig_type"]
                origin_display = display["origin"]

                origin_name = card["origin_user"]["info"]["uname"]
                origin_name_at_param = [{"type": "RICH_TEXT_NODE_TYPE_AT", "text": f"@{origin_name}"}]
                await cls.__draw_content(pic, origin_name_at_param, text_margin, True)

                await cls.__draw_by_type(pic, origin_type, origin, origin_dynamic_id, origin_display,
                                         text_margin, img_margin, True)
        elif dynamic_type == 2:
            # 带图动态
            await cls.__draw_content(pic, modules, text_margin, forward)
            await cls.__draw_picture_area(pic, card["item"]["pictures"], img_margin, forward)
        elif dynamic_type == 4:
            # 纯文字动态
            await cls.__draw_content(pic, modules, text_margin, forward)
        elif dynamic_type == 8:
            # 视频
            await cls.__draw_content(pic, modules, text_margin, forward)
            await cls.__draw_video_cover(pic, card["pic"], card["duration"], img_margin, forward)
            title_param = [{"type": "RICH_TEXT_NODE_TYPE_TEXT", "text": card["title"]}]
            await cls.__draw_content(pic, title_param, img_margin, forward)
        elif dynamic_type == 64:
            # 专栏
            title_param = [{"type": "RICH_TEXT_NODE_TYPE_TEXT", "text": card["title"]}]
            await cls.__draw_content(pic, title_param, text_margin, forward)
            await cls.__draw_article_cover(pic, card["origin_image_urls"], img_margin, forward)
            summary = limit_str_length(card['summary'].replace("\n", " "), 60)
            summary_param = [{"type": "RICH_TEXT_NODE_TYPE_TEXT", "text": summary}]
            await cls.__draw_content(pic, summary_param, text_margin, forward)
        elif dynamic_type == 256:
            # 音频
            await cls.__draw_content(pic, modules, text_margin, forward)
            title = limit_str_length(card["title"], 15)
            await cls.__draw_audio_area(pic, card["cover"], title, card["typeInfo"], text_margin, forward)
        elif dynamic_type == 2048:
            # 分享
            await cls.__draw_content(pic, modules, text_margin, forward)
            title = limit_str_length(card["sketch"]["title"], 15)
            desc = limit_str_length(card["sketch"]["desc_text"], 18)
            await cls.__draw_share_area(pic, card["sketch"]["cover_url"], title, desc, text_margin, forward)
        elif dynamic_type == 4200:
            # 直播
            title = limit_str_length(card["title"], 14)
            desc = f"{card['area_v2_name']} · {card['watched_show']}"
            await cls.__draw_live_area(pic, card["cover"], title, desc, text_margin, forward)
        elif dynamic_type == 4308:
            # 直播
            base = card["live_play_info"]
            title = limit_str_length(base["title"], 14)
            desc = f"{base['area_name']} {base['online']}人气"
            await cls.__draw_live_area(pic, base["cover"], title, desc, text_margin, forward)
        else:
            notice_param = [{"type": "RICH_TEXT_NODE_TYPE_TEXT", "text": "暂不支持的动态类型"}]
            await cls.__draw_content(pic, notice_param, text_margin, forward)

        # 附加卡片
        add_on_card = display["add_on_card_info"] if "add_on_card_info" in display else []

        await cls.__draw_add_on_card(pic, add_on_card, text_margin, forward)

    @classmethod
    async def __get_content_line_imgs(cls, modules: List[Dict[str, Any]], width: int) -> List[Image.Image]:
        """
        逐行绘制动态内容主体

        Args:
            modules: 动态内容各区块信息列表
            width: 每行最大宽度

        Returns:
            绘制好的每行文字的透明背景图片列表
        """
        imgs = []

        if not modules:
            return imgs

        line_height = 40
        text_img_size = (40, 40)

        img = Image.new("RGBA", (width, line_height))
        draw = ImageDraw.Draw(img)
        normal_font = config.get("PAINTER_NORMAL_FONT")
        font = ImageFont.truetype(f"{cls.__resource_base_path}/resource/{normal_font}", 30)
        emoji_font = ImageFont.truetype(f"{cls.__resource_base_path}/resource/emoji.ttf", 109)
        x, y = 0, 0

        def next_line():
            """
            换行
            """
            nonlocal img, draw, x, y

            imgs.append(img)
            img = Image.new("RGBA", (width, line_height))
            draw = ImageDraw.Draw(img)
            x, y = 0, 0

        def auto_next_line(next_element_width: int):
            """
            超出画布宽度自动换行

            Args:
                next_element_width: 下一绘制元素宽度
            """
            if x + next_element_width > width:
                next_line()

        def draw_pic(pic: Image, size: Optional[Tuple[int, int]] = None):
            """
            绘制图片

            Args:
                pic: 要绘制的图片
                size: 图片尺寸
            """
            nonlocal img, draw, x, y

            if size is None:
                size = pic.size
            else:
                pic = pic.resize(size, Resampling.LANCZOS).convert('RGBA')

            auto_next_line(size[0])
            img.paste(pic, (x, y), pic)
            x = int(x + size[0])

        def draw_char(c: str, color: Union[Color, Tuple[int, int, int]] = Color.BLACK):
            """
            绘制字符

            Args:
                c: 要绘制的字符
                color: 字符颜色
            """
            nonlocal x

            if isinstance(color, Color):
                color = color.value

            if c == "\n":
                next_line()
            else:
                if is_emoji(c):
                    emoji_img = Image.new("RGBA", (130, 130))
                    emoji_draw = ImageDraw.Draw(emoji_img)
                    emoji_draw.text((0, 0), c, font=emoji_font, embedded_color=True)
                    emoji_img = emoji_img.resize((30, 30), Resampling.LANCZOS)
                    draw_pic(emoji_img)
                else:
                    text_width = draw.textlength(c, font)
                    auto_next_line(text_width)
                    draw.text((x, y), c, color, font)
                    x = int(x + text_width)

        for module in modules:
            module_type = module["type"]

            if module_type == "RICH_TEXT_NODE_TYPE_TEXT":
                for char in cls.__remove_illegal_char(module["text"]):
                    draw_char(char)
            elif module_type == "RICH_TEXT_NODE_TYPE_EMOJI":
                draw_pic(module["img"], text_img_size)
            elif module_type in ["RICH_TEXT_NODE_TYPE_AT", "RICH_TEXT_NODE_TYPE_WEB", "RICH_TEXT_NODE_TYPE_BV",
                                 "RICH_TEXT_NODE_TYPE_TOPIC", "RICH_TEXT_NODE_TYPE_LOTTERY",
                                 "RICH_TEXT_NODE_TYPE_VOTE", "RICH_TEXT_NODE_TYPE_GOODS"]:
                if module_type == "RICH_TEXT_NODE_TYPE_WEB":
                    draw_pic(Image.open(f"{cls.__resource_base_path}/resource/link.png"), text_img_size)
                elif module_type == "RICH_TEXT_NODE_TYPE_BV":
                    draw_pic(Image.open(f"{cls.__resource_base_path}/resource/video.png"), text_img_size)
                elif module_type == "RICH_TEXT_NODE_TYPE_LOTTERY":
                    draw_pic(Image.open(f"{cls.__resource_base_path}/resource/box.png"), text_img_size)
                elif module_type == "RICH_TEXT_NODE_TYPE_VOTE":
                    draw_pic(Image.open(f"{cls.__resource_base_path}/resource/tick.png"), text_img_size)
                elif module_type == "RICH_TEXT_NODE_TYPE_GOODS":
                    draw_pic(Image.open(f"{cls.__resource_base_path}/resource/tb.png"), text_img_size)
                for char in cls.__remove_illegal_char(module["text"]):
                    draw_char(char, Color.LINK)

        imgs.append(img)

        return imgs

    @classmethod
    async def __draw_content(cls,
                             pic: PicGenerator,
                             modules: List[Dict[str, Any]],
                             text_margin: int,
                             forward: bool) -> PicGenerator:
        """
        绘制动态主体内容

        Args:
            pic: 绘图器实例
            modules: @ 信息
            text_margin: 文字外边距
            forward: 当前是否为转发动态的源动态
        """
        pic.set_pos(x=text_margin)

        content_imgs = await cls.__get_content_line_imgs(modules, pic.width - (text_margin * 2))

        if forward and content_imgs:
            heights = (content_imgs[0].height + pic.row_space) * len(content_imgs)
            pic.draw_rectangle(0, pic.y, pic.width, heights, Color.LIGHTGRAY)

        for img in content_imgs:
            pic.draw_img_alpha(img)
        return pic

    @classmethod
    async def __draw_picture_area(cls,
                                  pic: PicGenerator,
                                  pictures: List[Dict[str, Any]],
                                  img_margin: int,
                                  forward: bool) -> PicGenerator:
        """
        绘制动态主体下方图片区域

        Args:
            pic: 绘图器实例
            pictures: 图片信息字典
            img_margin: 图片外边距
            forward: 当前是否为转发动态的源动态
        """
        pic.set_pos(x=img_margin)

        # 下载图片
        picture_count = len(pictures)
        if picture_count == 1:
            line_count = 1
            size = 0
        elif picture_count == 2 or picture_count == 4:
            line_count = 2
            size = int((pic.width - (img_margin * 3)) / 2)
        else:
            line_count = 3
            size = int((pic.width - (img_margin * 4)) / 3)

        download_picture_tasks = []
        for picture in pictures:
            if line_count == 1:
                download_picture_tasks.append(open_url_image(f"{picture['img_src']}@518w.webp"))
            elif line_count == 2:
                src = picture['img_src']
                if picture["img_height"] / picture["img_width"] >= 3:
                    download_picture_tasks.append(open_url_image(f"{src}@{size}w_{size}h_!header.webp"))
                else:
                    download_picture_tasks.append(open_url_image(f"{src}@{size}w_{size}h_1e_1c.webp"))
            else:
                src = picture['img_src']
                if picture["img_height"] / picture["img_width"] >= 3:
                    download_picture_tasks.append(open_url_image(f"{src}@{size}w_{size}h_!header.webp"))
                else:
                    download_picture_tasks.append(open_url_image(f"{src}@{size}w_{size}h_1e_1c.webp"))
        imgs = await asyncio.gather(*download_picture_tasks)

        img_list = []
        for i, img in enumerate(imgs):
            if size != 0 and (img.width != size or img.height != size):
                if img.width == img.height:
                    img_list.append(img.resize((size, size)))
                elif img.width > img.height:
                    img = img.resize((int(img.width * (size / img.height)), size))
                    crop_area = (int((img.width - size) / 2), 0, int((img.width - size) / 2) + size, size)
                    img_list.append(img.crop(crop_area))
                else:
                    img = img.resize((size, int(img.height * (size / img.width))))
                    crop_area = (0, int((img.height - size) / 2), size, int((img.height - size) / 2) + size)
                    img_list.append(img.crop(crop_area))
            else:
                img_list.append(img)
        imgs = tuple(img_list)

        if picture_count == 1:
            img = imgs[0]
            img = img.resize(((pic.width - (img_margin * 2)),
                              int((pic.width - (img_margin * 4)) / (img.size[0] / img.size[1]))),
                             Resampling.LANCZOS)
            imgs = [img]

        # 绘制图片
        imgs = split_list(imgs, line_count)

        if forward:
            heights = (imgs[0][0].height + pic.row_space) * len(imgs)
            pic.draw_rectangle(0, pic.y, pic.width, heights, Color.LIGHTGRAY)

        for line in imgs:
            for index, img in enumerate(line):
                if index == len(line) - 1:
                    pic.draw_img(img).set_pos(x=img_margin)
                else:
                    pic.draw_img(img, (pic.x, pic.y)).move_pos(img.width + img_margin, 0)
        return pic

    @classmethod
    async def __draw_video_cover(cls,
                                 pic: PicGenerator,
                                 url: str,
                                 duration: int,
                                 img_margin: int,
                                 forward: bool) -> PicGenerator:
        """
        绘制视频封面

        Args:
            pic: 绘图器实例
            url: 视频封面 URL
            duration: 视频时长
            img_margin: 图片外边距
            forward: 当前是否为转发动态的源动态
        """
        pic.set_pos(x=img_margin)

        cover = await open_url_image(f"{url}@480w.webp")
        cover = cover.resize(((pic.width - (img_margin * 2)),
                              int((pic.width - (img_margin * 4)) / (cover.size[0] / cover.size[1]))),
                             Resampling.LANCZOS)
        cover = mask_rounded_rectangle(cover)

        mask = Image.open(f"{cls.__resource_base_path}/resource/mask.png")
        mask = mask.crop((0, 0, cover.width, mask.height))
        time = Image.open(f"{cls.__resource_base_path}/resource/time.png")
        tv = Image.open(f"{cls.__resource_base_path}/resource/tv.png")

        cover.paste(mask, (0, cover.height - mask.height - 1), mask)
        cover.paste(time, (13, cover.height - time.height - 14), time)
        cover.paste(tv, (cover.width - tv.width - 16, cover.height - tv.height - 5), tv)

        cover_draw = ImageDraw.Draw(cover)
        normal_font = config.get("PAINTER_NORMAL_FONT")
        time_font = ImageFont.truetype(f"{cls.__resource_base_path}/resource/{normal_font}", 25)
        cover_draw.text((21, cover.height - time.height - 11),
                        timestamp_format(duration + 57600, "%H:%M:%S"), Color.WHITE.value, time_font)

        if forward:
            cover_height = cover.height + pic.row_space
            pic.draw_rectangle(0, pic.y, pic.width, cover_height, Color.LIGHTGRAY)

        pic.draw_img_alpha(cover)
        return pic

    @classmethod
    async def __draw_article_cover(cls,
                                   pic: PicGenerator,
                                   urls: List[str],
                                   img_margin: int,
                                   forward: bool) -> PicGenerator:
        """
        绘制专栏封面

        Args:
            pic: 绘图器实例
            urls: 专栏封面 URL 列表
            img_margin: 图片外边距
            forward: 当前是否为转发动态的源动态
        """
        pic.set_pos(x=img_margin)

        img_count = len(urls)

        download_picture_tasks = []
        for url in urls:
            if img_count == 1:
                download_picture_tasks.append(open_url_image(f"{url}@518w.webp"))
            else:
                size = int((pic.width - (img_margin * 4)) / 3)
                download_picture_tasks.append(open_url_image(f"{url}@{size}w_{size}h_1e_1c.webp"))
        imgs = await asyncio.gather(*download_picture_tasks)

        if img_count == 1:
            img = imgs[0]
            img = img.resize(((pic.width - (img_margin * 2)),
                              int((pic.width - (img_margin * 4)) / (img.size[0] / img.size[1]))),
                             Resampling.LANCZOS)
            imgs = [img]

        if forward:
            heights = imgs[0].height + pic.row_space
            pic.draw_rectangle(0, pic.y, pic.width, heights, Color.LIGHTGRAY)

        for index, img in enumerate(imgs):
            if index == len(imgs) - 1:
                pic.draw_img(img).set_pos(x=img_margin)
            else:
                pic.draw_img(img, (pic.x, pic.y)).move_pos(img.width + img_margin, 0)

        return pic

    @classmethod
    async def __draw_audio_area(cls,
                                pic: PicGenerator,
                                cover_url: str,
                                title: str,
                                audio_type: str,
                                margin: int,
                                forward: bool) -> PicGenerator:
        """
        绘制音频卡片

        Args:
            pic: 绘图器实例
            cover_url: 音频封面 URL
            title: 音频标题
            audio_type: 音频类型
            margin: 外边距
            forward: 当前是否为转发动态的源动态
        """
        return await cls.__draw_share_area(pic, cover_url, title, audio_type, margin, forward)

    @classmethod
    async def __draw_share_area(cls,
                                pic: PicGenerator,
                                cover_url: str,
                                title: str,
                                desc: str,
                                margin: int,
                                forward: bool) -> PicGenerator:
        """
        绘制分享卡片

        Args:
            pic: 绘图器实例
            cover_url: 封面 URL
            title: 标题
            desc: 描述
            margin: 外边距
            forward: 当前是否为转发动态的源动态
        """
        pic.set_pos(x=margin)

        cover = await open_url_image(cover_url)
        cover_size = int(pic.width / 4)
        cover = cover.resize((cover_size, cover_size), Resampling.LANCZOS)
        cover = mask_rounded_rectangle(cover)

        if forward:
            heights = cover.height + pic.row_space
            pic.draw_rectangle(0, pic.y, pic.width, heights, Color.LIGHTGRAY)

        border = Image.new("RGBA", (pic.width - (margin * 2) + 2, cover_size + 2))
        ImageDraw.Draw(border).rounded_rectangle((0, 0, border.width - 1, border.height - 1),
                                                 10, (0, 0, 0, 0), Color.GRAY.value, 1)
        pic.draw_img_alpha(border, (pic.x - 1, pic.y - 1))

        x, y = pic.xy
        pic.draw_img_alpha(cover)
        pic.draw_text(title, Color.BLACK, (x + cover_size + margin, y + int(cover_size / 5)))
        pic.draw_tip(desc, xy=(x + cover_size + margin, y + int(cover_size / 5 * 3)))

        return pic

    @classmethod
    async def __draw_live_area(cls,
                               pic: PicGenerator,
                               cover_url: str,
                               title: str,
                               area: str,
                               margin: int,
                               forward: bool) -> PicGenerator:
        """
        绘制直播卡片

        Args:
            pic: 绘图器实例
            cover_url: 直播封面 URL
            title: 直播标题
            area: 直播分区
            margin: 外边距
            forward: 当前是否为转发动态的源动态
        """
        pic.set_pos(x=margin)

        cover = await open_url_image(f"{cover_url}@203w_127h_1e_1c.webp")

        if forward:
            heights = cover.height + pic.row_space
            pic.draw_rectangle(0, pic.y, pic.width, heights, Color.LIGHTGRAY)

        back = Image.new("RGBA", (pic.width - (margin * 2), cover.height))
        ImageDraw.Draw(back).rectangle((0, 0, back.width, back.height), Color.WHITE.value)
        pic.draw_img_alpha(back, (pic.x, pic.y))

        x, y = pic.xy
        pic.draw_img(cover)
        pic.draw_text(title, Color.BLACK, (x + cover.width + margin, y + int(cover.height / 5)))
        pic.draw_tip(area, xy=(x + cover.width + margin, y + int(cover.height / 5 * 3)))

        return pic

    @classmethod
    async def __draw_add_on_card(cls,
                                 pic: PicGenerator,
                                 infos: List[Dict[str, Any]],
                                 margin: int,
                                 forward: bool) -> PicGenerator:
        """
        绘制附加卡片

        Args:
            pic: 绘图器实例
            infos: 附加卡片信息
            margin: 外边距
            forward: 当前是否为转发动态的源动态
        """
        pic.set_pos(x=margin)

        card_height = int(pic.width / 4)

        padding = 10
        img_height = card_height - padding * 2
        edge = pic.width - margin - padding

        for info in infos:
            # 绘制背景
            back_color = Color.LIGHTGRAY
            if forward:
                pic.draw_rectangle(0, pic.y, pic.width, card_height + pic.row_space + padding, Color.LIGHTGRAY)
                back_color = Color.WHITE

            pic.move_pos(0, padding)
            back = Image.new("RGBA", (pic.width - (margin * 2), card_height))
            ImageDraw.Draw(back).rectangle((0, 0, back.width, back.height), back_color.value)
            back = mask_rounded_rectangle(back)
            pic.draw_img_alpha(back, (pic.x, pic.y))
            pic.move_pos(padding, padding)

            # 根据类型绘制卡片
            card_type = info["add_on_card_show_type"]
            if card_type == 1:
                # 淘宝
                goods = json.loads(info["goods_card"])["list"]

                download_picture_tasks = []
                for good in goods:
                    url = good["img"]
                    download_picture_tasks.append(open_url_image(f"{url}@{img_height}w_{img_height}h_1e_1c.webp"))
                imgs = await asyncio.gather(*download_picture_tasks)

                if len(goods) == 1:
                    img = mask_rounded_rectangle(imgs[0])
                    x, y = pic.xy
                    pic.draw_img(img)

                    name = limit_str_length(goods[0]["name"], 15)
                    price = goods[0]["priceStr"]
                    price_length = pic.get_text_length(price)
                    pic.draw_text(name, Color.BLACK, (x + img.width + margin, y + int(img_height / 5)))
                    pic.draw_text(price, Color.LINK, (x + img.width + margin, y + int(img_height / 5 * 3)))
                    pic.draw_tip("起", xy=(x + img.width + margin + price_length, y + int(img_height / 5 * 3) + 5))
                else:
                    for index, img in enumerate(imgs):
                        overflow = False
                        img = mask_rounded_rectangle(img)
                        if pic.x + img.width > edge:
                            overflow = True
                            img = img.crop((0, 0, edge - pic.x, img.height))

                        if index == len(imgs) - 1 or overflow:
                            pic.draw_img(img).set_pos(x=margin)
                            if overflow:
                                break
                        else:
                            pic.draw_img(img, pic.xy).move_pos(img.height + margin, 0)
            elif card_type == 2:
                # 充电、相关装扮、相关游戏
                base = info["attach_card"]

                if base["type"] in ["decoration", "game"]:
                    url = base["cover_url"]
                    title = base["title"]
                    desc_first = base["desc_first"]
                    desc_second = base["desc_second"]

                    cover = await open_url_image(f"{url}@{img_height}w_{img_height}h_1e_1c.webp")
                    cover = mask_rounded_rectangle(cover)

                    x, y = pic.xy
                    pic.draw_img(cover)
                    pic.draw_text(title, Color.BLACK, (x + cover.width + margin, y + int(img_height / 10)))
                    pic.draw_tip(desc_first, xy=(x + cover.width + margin, y + int(img_height / 7 * 3)))
                    pic.draw_tip(desc_second, xy=(x + cover.width + margin, y + int(img_height / 7 * 5)))
                elif base["type"] == "lottery":
                    title = base["title"]
                    desc_first = limit_str_length(base["desc_first"], 24)
                    icon = Image.open(f"{cls.__resource_base_path}/resource/box.png")

                    x, y = pic.xy
                    pic.draw_text(title, Color.BLACK, (x, y + int(img_height / 5)))
                    pic.draw_img_alpha(icon, (x, y + int(img_height / 5 * 3)))
                    pic.draw_tip(desc_first, Color.LINK, (x + icon.width + padding, y + int(img_height / 5 * 3)))
                    pic.set_pos(x=margin).move_pos(0, img_height + pic.row_space)
            elif card_type == 3:
                # 投票
                vote = json.loads(info["vote_card"])
                desc = limit_str_length(vote["desc"], 15)
                join_num = vote["join_num"]
                icon = Image.open(f"{cls.__resource_base_path}/resource/tick_big.png")
                icon = mask_rounded_rectangle(icon.resize((img_height, img_height), Resampling.LANCZOS))

                x, y = pic.xy
                pic.draw_img_alpha(icon)
                pic.draw_text(desc, Color.BLACK, (x + img_height + margin, y + int(img_height / 5)))
                pic.draw_tip(f"{join_num}人参与", xy=(x + img_height + margin, y + int(img_height / 5 * 3)))
            elif card_type == 5:
                # 视频分享
                base = info["ugc_attach_card"]
                url = base["image_url"]
                title = limit_str_length(base["title"], 12)
                desc_second = base["desc_second"]
                cover = await open_url_image(f"{url}@480w.webp")
                cover = cover.resize((int(img_height / cover.height * cover.width), img_height), Resampling.LANCZOS)
                cover = mask_rounded_rectangle(cover)

                x, y = pic.xy
                pic.draw_img(cover)
                pic.draw_text(title, Color.BLACK, (x + cover.width + margin, y + int(img_height / 5)))
                pic.draw_tip(desc_second, xy=(x + cover.width + margin, y + int(img_height / 5 * 3)))
            elif card_type == 6:
                # 视频、直播预约
                base = info["reserve_attach_card"]
                title = cls.__remove_illegal_char(base["title"])
                desc_first = base["desc_first"]["text"]
                desc_second = base["desc_second"]
                desc = f"{desc_first}   {desc_second}"
                lottery = limit_str_length(base["reserve_lottery"]["text"], 24) if "reserve_lottery" in base else None

                if lottery:
                    pic.draw_text(title, Color.BLACK, (pic.x, pic.y + int(img_height / 10)))
                    pic.draw_tip(desc, xy=(pic.x, pic.y + int(img_height / 7 * 3)))
                    icon = Image.open(f"{cls.__resource_base_path}/resource/box.png")
                    pic.draw_img_alpha(icon, (pic.x, pic.y + int(img_height / 7 * 5)))
                    pic.draw_tip(lottery, Color.LINK, (pic.x + icon.width + padding, pic.y + int(img_height / 7 * 5)))
                else:
                    pic.draw_text(title, Color.BLACK, (pic.x, pic.y + int(img_height / 5)))
                    pic.draw_tip(desc, xy=(pic.x, pic.y + int(img_height / 5 * 3)))

                pic.set_pos(x=margin).move_pos(0, img_height + pic.row_space)

        return pic
