import asyncio
import math
import time
from datetime import datetime
from io import BytesIO
from typing import Any, Dict, List

from nonebot import logger
from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot_plugin_htmlrender import get_new_page
from PIL import Image, ImageFilter
from pil_utils import BuildImage, text2image
from playwright.async_api import ViewportSize

from .config import config
from .resource import RES_CALENDER_BANNER, RES_GRADIENT_BG
from .util import async_req, img_invert_rgba, parse_time_delta

PAGE_KWARGS = {
    "is_mobile": True,
    "viewport": ViewportSize(width=767, height=800),
}


async def schale_get(suffix, raw=False, **kwargs):
    return await async_req(f"{config.ba_schale_url}{suffix}", raw=raw, **kwargs)


async def schale_get_stu_data(loc: str = "cn") -> List[Dict[str, Any]]:
    return await schale_get(f"data/{loc}/students.min.json")


async def schale_get_common() -> Dict[str, Any]:
    return await schale_get("data/common.min.json")


async def schale_get_localization(loc: str = "cn") -> Dict[str, Any]:
    return await schale_get(f"data/{loc}/localization.min.json")


async def schale_get_raids(loc: str = "cn") -> Dict[str, Any]:
    return await schale_get(f"data/{loc}/raids.min.json")


async def schale_get_stu_dict(key="Name"):
    return {x[key]: x for x in await schale_get_stu_data()}


async def schale_get_stu_info(stu):
    async with get_new_page(**PAGE_KWARGS) as page:  # type:Page
        await page.goto(
            f"{config.ba_schale_mirror_url}?chara={stu}",
            timeout=60 * 1000,
            wait_until="networkidle",
        )

        # 进度条拉最大
        await page.add_script_tag(content="utilStuSetAllProgressMax();")

        return await page.screenshot(full_page=True)


async def schale_calender(server):
    return MessageSegment.image(
        await schale_get_calender(
            server,
            *(
                await asyncio.gather(
                    schale_get_stu_data(),
                    schale_get_common(),
                    schale_get_localization(),
                    schale_get_raids(),
                )
            ),
        )
    )


def find_current_event(ev, now=None):
    if not now:
        now = datetime.now()
    for _e in ev:
        _start = datetime.fromtimestamp(_e["start"])
        _end = datetime.fromtimestamp(_e["end"])
        if _start <= now < _end:
            _remain = _end - now
            return _e, _start, _end, _remain


async def schale_get_calender(server, students, common, localization, raids):
    students = {x["Id"]: x for x in students}

    region = common["regions"][server]
    now = datetime.now()

    pic_bg = BuildImage.new("RGBA", (1400, 640), (255, 255, 255, 70))

    def format_time(_start, _end, _remain):
        dd, hh, mm, ss = parse_time_delta(_remain)
        return (
            f"{_start} ~ {_end} | "
            f"剩余 [b][color=#fc6475]{dd}天 {hh:0>2d}:{mm:0>2d}:{ss:0>2d}[color=#fc6475][/b]"
        )

    async def draw_gacha():
        pic = pic_bg.copy().draw_text(
            (25, 25, 1375, 150), "特选招募", weight="bold", max_fontsize=80
        )
        c_gacha = region["current_gacha"]
        if r := find_current_event(c_gacha):
            g = r[0]
            t = format_time(*(r[1:]))
            pic = pic.paste(
                ti := text2image(
                    t, (255, 255, 255, 0), fontsize=45, max_width=1350, align="center"
                ),
                (int((1400 - ti.width) / 2), 150),
                True,
            )
            stu = [students[x] for x in g["characters"]]

            async def process_avatar(s):
                return (
                    BuildImage.open(
                        BytesIO(
                            await schale_get(
                                f'images/student/collection/{s["CollectionTexture"]}.webp',
                                raw=True,
                            )
                        )
                    )
                    .resize((300, 340))
                    .paste(
                        BuildImage.new("RGBA", (300, 65), (255, 255, 255, 120)),
                        (0, 275),
                        True,
                    )
                    .convert("RGB")
                    .circle_corner(25)
                    .draw_text((0, 275, 300, 340), s["Name"], max_fontsize=50)
                )

            avatars = await asyncio.gather(*[process_avatar(x) for x in stu])
            ava_len = len(avatars)
            x_index = int((1400 - (300 + 25) * ava_len + 25) / 2)

            for p in avatars:
                pic = pic.paste(p, (x_index, 250), True)
                x_index += p.width + 25

            return pic

    async def draw_event():
        pic = pic_bg.copy().draw_text(
            (25, 25, 1375, 150), "当前活动", weight="bold", max_fontsize=80
        )
        c_event = region["current_events"]
        if r := find_current_event(c_event):
            g = r[0]
            t = format_time(*(r[1:]))
            pic = pic.paste(
                ti := text2image(
                    t, (255, 255, 255, 0), fontsize=45, max_width=1350, align="center"
                ),
                (int((1400 - ti.width) / 2), 150),
                True,
            )
            ev = g["event"]
            ev_name = ""
            if ev >= 10000:
                ev_name = " (复刻)"
                ev %= 10000
            ev_name = localization["EventName"][str(ev)] + ev_name

            ev_bg, ev_img = await asyncio.gather(
                schale_get(f"images/campaign/Campaign_Event_{ev}_Normal.png", True),
                schale_get(
                    f"images/eventlogo/Event_{ev}_{'Tw' if server else 'Jp'}.png", True
                ),
            )

            ev_bg = (
                BuildImage.open(BytesIO(ev_bg))
                .convert("RGBA")
                .resize_height(340)
                .filter(ImageFilter.GaussianBlur(3))
            )
            ev_bg = (
                ev_bg.paste(
                    BuildImage.open(BytesIO(ev_img))
                    .convert("RGBA")
                    .resize(
                        (ev_bg.width, ev_bg.height - 65),
                        keep_ratio=True,
                        inside=True,
                        bg_color=(255, 255, 255, 0),
                    ),
                    alpha=True,
                )
                .paste(
                    BuildImage.new("RGBA", (ev_bg.width, 65), (255, 255, 255, 120)),
                    (0, ev_bg.height - 65),
                    True,
                )
                .convert("RGB")
                .circle_corner(25)
                .draw_text(
                    (0, ev_bg.height - 65, ev_bg.width, ev_bg.height),
                    ev_name,
                    max_fontsize=50,
                )
            )
            return pic.paste(ev_bg, (int((pic.width - ev_bg.width) / 2), 250), True)

    async def draw_raid():
        pic = pic_bg.copy()
        if r := find_current_event(region["current_raid"]):
            ri = r[0]
            t = format_time(*(r[1:]))
            pic = pic.paste(
                ti := text2image(
                    t, (255, 255, 255, 0), fontsize=45, max_width=1350, align="center"
                ),
                (int((1400 - ti.width) / 2), 150),
                True,
            )

            tp = "TimeAttack" if (time_atk := (ri["raid"] >= 1000)) else "Raid"
            raid = {x["Id"]: x for x in raids[tp]}
            c_ri = raid[ri["raid"]]
            pic = pic.draw_text(
                (25, 25, 1375, 150),
                localization["StageType"][tp],
                weight="bold",
                max_fontsize=80,
            )

            if time_atk:
                tk_bg = {
                    "Shooting": "TimeAttack_SlotBG_02",
                    "Defense": "TimeAttack_SlotBG_01",
                    "Destruction": "TimeAttack_SlotBG_03",
                }
                bg_url = f'images/timeattack/{tk_bg[c_ri["DungeonType"]]}'
                fg_url = f'images/enemy/{c_ri["Icon"]}.png'
            else:
                bg_url = f'images/raid/Boss_Portrait_{c_ri["PathName"]}_LobbyBG'
                if len(c_ri["Terrain"]) > 1 and ri["terrain"] == c_ri["Terrain"][1]:
                    bg_url += f'_{ri["terrain"]}'
                fg_url = f'images/raid/Boss_Portrait_{c_ri["PathName"]}_Lobby.png'
            bg_url += ".png"
            terrain = c_ri["Terrain"] if time_atk else ri["terrain"]

            color_map = {
                "LightArmor": (167, 12, 25),
                "Explosion": (167, 12, 25),
                "HeavyArmor": (178, 109, 31),
                "Pierce": (178, 109, 31),
                "Unarmed": (33, 111, 156),
                "Mystic": (33, 111, 156),
                "Normal": (115, 115, 115),
            }
            atk_color = color_map[
                c_ri["BulletType" if time_atk else "BulletTypeInsane"]
            ]
            def_color = color_map[c_ri["ArmorType"]]

            c_bg, c_fg, icon_def, icon_atk, icon_tr = await asyncio.gather(
                *[
                    schale_get(bg_url, True),
                    schale_get(fg_url, True),
                    schale_get("images/ui/Type_Defense_s.png", True),
                    schale_get("images/ui/Type_Attack_s.png", True),
                    schale_get(f"images/ui/Terrain_{terrain}.png", True),
                ]
            )

            icon_def = (
                BuildImage.new("RGBA", (64, 64), def_color)
                .paste(
                    BuildImage.open(BytesIO(icon_def))
                    .convert("RGBA")
                    .resize_height(48),
                    (8, 8),
                    True,
                )
                .circle()
            )
            icon_atk = (
                BuildImage.new("RGBA", (64, 64), atk_color)
                .paste(
                    BuildImage.open(BytesIO(icon_atk))
                    .convert("RGBA")
                    .resize_height(48),
                    (8, 8),
                    True,
                )
                .circle()
            )
            icon_tr = (
                BuildImage.new("RGBA", (64, 64), "#ffffff")
                .paste(
                    img_invert_rgba(Image.open(BytesIO(icon_tr)).convert("RGBA")),
                    (-2, -2),
                    True,
                )
                .circle()
            )

            c_bg = (
                BuildImage.open(BytesIO(c_bg))
                .convert("RGBA")
                .resize_height(340)
                .filter(ImageFilter.GaussianBlur(3))
            )
            c_fg = (
                BuildImage.open(BytesIO(c_fg))
                .convert("RGBA")
                .resize_height(c_bg.height)
            )
            c_bg = (
                c_bg.paste(
                    c_fg,
                    (int((c_bg.width - c_fg.width) / 2), 0),
                    True,
                )
                .paste(
                    BuildImage.new("RGBA", (c_bg.width, 65), (255, 255, 255, 120)),
                    (0, c_bg.height - 65),
                    True,
                )
                .paste(icon_atk, (10, 10), True)
                .paste(icon_def, (10, 79), True)
                .paste(icon_tr, (10, 147), True)
                .convert("RGB")
                .circle_corner(25)
                .draw_text(
                    (0, c_bg.height - 65, c_bg.width, c_bg.height),
                    (
                        localization["TimeAttackStage"][c_ri["DungeonType"]]
                        if time_atk
                        else (c_ri["Name"])
                    ),
                    max_fontsize=50,
                )
            )
            return pic.paste(c_bg, (int((pic.width - c_bg.width) / 2), 250), True)

    async def draw_birth():
        pic = pic_bg.copy().draw_text(
            (25, 25, 1375, 150), "学生生日", weight="bold", max_fontsize=80
        )
        now_t = time.mktime(now.date().timetuple())
        now_w = now.weekday()
        this_week_t = now_t - now_w * 86400
        next_week_t = now_t + (7 - now_w) * 86400
        next_next_week_t = next_week_t + 7 * 86400

        birth_this_week = []
        birth_next_week = []
        for s in [x for x in students.values() if x["IsReleased"][server]]:
            birth = time.mktime(
                time.strptime(f'{now.year}/{s["BirthDay"]}', "%Y/%m/%d")
            )
            if this_week_t <= birth < next_week_t:
                birth_this_week.append(s)
            elif next_week_t <= birth <= next_next_week_t:
                birth_next_week.append(s)

        sort_key = lambda x: x["BirthDay"].split("/")  # noqa: E731
        p_h = 0
        if birth_this_week:
            birth_this_week.sort(key=sort_key)
            p_h += 180

        if birth_next_week:
            birth_next_week.sort(key=sort_key)
            p_h += 180
            if birth_this_week:
                p_h += 10

        if p_h:
            stu_pics = [
                BuildImage.open(BytesIO(x)).convert("RGBA").resize_height(180).circle()
                for x in await asyncio.gather(
                    *[
                        schale_get(
                            f'images/student/icon/{x["CollectionTexture"]}.png', True
                        )
                        for x in birth_this_week + birth_next_week
                    ]
                )
            ]
            y_index = int((415 - p_h) / 2) + 125
            if birth_this_week:
                x_index = (
                    int((1400 - (len(birth_this_week) * (180 + 10) - 10)) / 2) + 75
                )
                pic = pic.draw_text(
                    (x_index - 165, y_index, x_index, y_index + 180),
                    "本周",
                    max_fontsize=50,
                )
                for s in birth_this_week:
                    pic.paste(stu_pics.pop(0), (x_index, y_index), True).draw_text(
                        (x_index, y_index + 180, x_index + 180, y_index + 220),
                        s["BirthDay"],
                    )
                    x_index += 180 + 10

            if birth_next_week:
                if birth_this_week:
                    y_index += 220 + 10
                x_index = (
                    int((1400 - (len(birth_next_week) * (180 + 10) - 10)) / 2) + 75
                )
                pic = pic.draw_text(
                    (x_index - 165, y_index, x_index, y_index + 180),
                    "下周",
                    max_fontsize=50,
                )
                for s in birth_next_week:
                    pic.paste(stu_pics.pop(0), (x_index, y_index), True).draw_text(
                        (x_index, y_index + 180, x_index + 180, y_index + 220),
                        s["BirthDay"],
                    )
                    x_index += 180 + 10

            return pic

    img = await asyncio.gather(  # type: ignore
        draw_gacha(), draw_event(), draw_raid(), draw_birth()
    )
    img: List[BuildImage] = [x for x in img if x]
    if not img:
        img.append(
            pic_bg.copy().draw_text((0, 0, 1400, 640), "没有获取到任何数据", max_fontsize=60)
        )

    bg_w = 1500
    bg_h = 200 + sum([x.height + 50 for x in img])
    bg = (
        BuildImage.new("RGBA", (bg_w, bg_h))
        .paste(RES_CALENDER_BANNER.copy().resize((1500, 150)))
        .draw_text(
            (50, 0, 1480, 150),
            f"SchaleDB丨活动日程丨{localization['ServerName'][str(server)]}",
            max_fontsize=100,
            weight="bold",
            fill="#ffffff",
            halign="left",
        )
        .paste(
            RES_GRADIENT_BG.copy().resize((1500, bg_h - 150), resample=Image.NEAREST),
            (0, 150),
        )
    )

    h_index = 200
    for im in img:
        bg.paste(im.circle_corner(10), (50, h_index), True)
        h_index += im.height + 50
    return bg.convert("RGB").save("png")


async def draw_fav_li(lvl):
    try:
        stu_li = [
            x
            for x in await schale_get_stu_data()
            if (x["MemoryLobby"] and x["MemoryLobby"][0] == lvl)
        ]
    except:
        logger.exception("获取schale db学生数据失败")
        return "获取SchaleDB学生数据失败，请检查后台输出"

    if not stu_li:
        return f"没有学生在羁绊等级{lvl}时解锁L2D"

    txt_h = 48
    pic_h = 144
    icon_w = 182
    icon_h = pic_h + txt_h
    line_max_icon = 6

    if (li_len := len(stu_li)) <= line_max_icon:
        line = 1
        length = li_len
    else:
        line = math.ceil(li_len / line_max_icon)
        length = line_max_icon

    img = RES_GRADIENT_BG.copy().resize(
        (icon_w * length, icon_h * line + 5), resample=Image.NEAREST
    )

    async def draw_stu(name_, dev_name_, line_, index_):
        left = index_ * icon_w
        top = line_ * icon_h + 5

        ret = await schale_get(
            f"images/student/lobby/Lobbyillust_Icon_{dev_name_}_01.png", True
        )
        icon_img = Image.open(BytesIO(ret)).convert("RGBA")
        img.paste(icon_img, (left, top), icon_img)
        img.draw_text(
            (left, top + pic_h, left + icon_w, top + icon_h),
            name_,
            max_fontsize=25,
            min_fontsize=1,
        )

    task_li = []
    line = 0
    i = 0
    for stu in stu_li:
        if i == line_max_icon:
            i = 0
            line += 1
        task_li.append(draw_stu(stu["Name"], stu["DevName"], line, i))
        i += 1
    await asyncio.gather(*task_li)

    return MessageSegment.text(f"羁绊等级 {lvl} 时解锁L2D的学生有以下这些：") + MessageSegment.image(
        img.save("png")
    )
