import re
import typing as T
from datetime import timedelta
from pathlib import Path
from uuid import UUID
import json

from mirai.message.types import FriendMessage, GroupMessage, MessageTypes

from mirai.event import ExternalEvent, ExternalEvents
from mirai.friend import Friend
from mirai.group import Group, GroupSetting, Member, MemberChangeableSetting
from mirai.message.chain import MessageChain
from mirai.misc import ImageRegex, ImageType, assertOperatorSuccess, raiser, printer, getMatchedString
from mirai.network import fetch
from mirai.message.base import BaseMessageComponent
from mirai.message import components
import threading

class MiraiProtocol:
    qq: int
    baseurl: str
    session_key: str
    auth_key: str

    async def auth(self):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/auth", {
                "authKey": self.auth_key
            }
        ), raise_exception=True, return_as_is=True)

    async def verify(self):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/verify", {
                "sessionKey": self.session_key,
                "qq": self.qq
            }
        ), raise_exception=True, return_as_is=True)

    async def release(self):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/release", {
                "sessionKey": self.session_key,
                "qq": self.qq
            }
        ), raise_exception=True)

    async def sendFriendMessage(self,
            friend: T.Union[Friend, int],
            message: T.Union[
                MessageChain,
                BaseMessageComponent,
                T.List[BaseMessageComponent]
            ]
    ):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/sendFriendMessage", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsFriend(friend),
                "messageChain": self.handleMessageAsFriend(message)
            }
        ), raise_exception=True)
    
    async def sendGroupMessage(self, group: T.Union[Group, int], message: MessageChain):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/sendGroupMessage", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group),
                "messageChain": self.handleMessageAsGroup(message)
            }
        ), raise_exception=True)

    async def groupList(self) -> T.List[Group]:
        return [Group.parse_obj(group_info) \
            for group_info in await fetch.http_get(f"{self.baseurl}/groupList", {
                "sessionKey": self.session_key
            })
        ]

    async def friendList(self) -> T.List[Friend]:
        return [Friend.parse_obj(friend_info) \
            for friend_info in await fetch.http_get(f"{self.baseurl}/friendList", {
                "sessionKey": self.session_key
            })
        ]

    async def memberList(self, target: int) -> T.List[Member]:
        return [Member.parse_obj(member_info) \
            for member_info in await fetch.http_get(f"{self.baseurl}/memberList", {
                "sessionKey": self.session_key,
                "target": target
            })
        ]

    async def groupMemberNumber(self, target: int) -> int:
        return len(await self.memberList(target)) + 1

    async def uploadImage(self, type: T.Union[str, ImageType], imagePath: T.Union[Path, str]):
        if isinstance(imagePath, str):
            imagePath = Path(imagePath)

        if not imagePath.exists():
            raise FileNotFoundError("invaild image path.")

        regex = ImageRegex[type if isinstance(type, str) else type.value]
        post_result = await fetch.upload(f"{self.baseurl}/uploadImage", imagePath, {
            "sessionKey": self.session_key,
            "type": type if isinstance(type, str) else type.value
        })
        uuid_string = re.search(regex, post_result)
        if uuid_string:
            return components.Image(imageId=UUID(getMatchedString(uuid_string)))

    async def fetchMessage(self, count: int) -> T.List[T.Union[FriendMessage, GroupMessage, ExternalEvent]]:
        result = assertOperatorSuccess(
            await fetch.http_get(f"{self.baseurl}/fetchMessage", {
                "sessionKey": self.session_key,
                "count": count
            }
        ), raise_exception=True, return_as_is=True)
        for index in range(len(result)):
            if result[index]['type'] in MessageTypes:
                if 'messageChain' in result[index]:
                    result[index]['messageChain'] = MessageChain.custom_parse(result[index]['messageChain'])
                result[index] = \
                    MessageTypes[result[index]['type']].parse_obj(result[index])
            elif hasattr(ExternalEvents, result[index]['type']):
                result[index] = \
                    ExternalEvents[result[index]['type']].value.parse_obj(result[index])
        return result

    async def muteAll(self, group: T.Union[Group, int]) -> bool:
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/muteAll", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group)
            }
        ), raise_exception=True)
        
    async def unmuteAll(self, group: T.Union[Group, int]) -> bool:
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/unmuteAll", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group)
            }
        ), raise_exception=True)
    
    async def memberInfo(self,
        group: T.Union[Group, int],
        member: T.Union[Member, int],
    ):
        return assertOperatorSuccess(
            await fetch.http_get(f"{self.baseurl}/groupConfig", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group),
                "memberId": self.handleTargetAsMember(member)
            }
        ), raise_exception=True, return_as_is=True)

    async def botMemberInfo(self,
        group: T.Union[Group, int]
    ):
        return await self.memberInfo(group, self.qq)

    async def changeMemberInfo(self,
        group: T.Union[Group, int],
        member: T.Union[Member, int],
        setting: MemberChangeableSetting
    ) -> bool:
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/memberInfo", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group),
                "memberId": self.handleTargetAsMember(member),
                "info": json.loads(setting.json())
            }
        ), raise_exception=True)

    async def groupConfig(self, group: T.Union[Group, int]) -> GroupSetting:
        return GroupSetting.parse_obj(
            await fetch.http_get(f"{self.baseurl}/groupConfig", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group)
            })
        )

    async def changeGroupConfig(self,
        group: T.Union[Group, int],
        config: GroupSetting
    ) -> bool:
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/groupConfig", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group),
                "config": json.loads(json.loads(config.json()))
            }
        ), raise_exception=True)

    async def mute(self,
        group: T.Union[Group, int],
        member: T.Union[Member, int],
        time: T.Union[timedelta, int]
    ):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/mute", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group),
                "memberId": self.handleTargetAsMember(member),
                "time": time if isinstance(time, int) else \
                        int(time.total_seconds()) \
                            if timedelta(days=30) >= time >= timedelta(minutes=1) \
                        else \
                            int(timedelta(days=30).total_seconds()) \
                                if timedelta(days=30) >= time else \
                            int(timedelta(minutes=1).total_seconds()) \
                                if time <= timedelta(minutes=1) else \
                            raiser(ValueError("invaild time."))
                    if isinstance(time, timedelta) else raiser(ValueError("invaild time."))
            }
        ), raise_exception=True)

    async def unmute(self,
        group: T.Union[Group, int],
        member: T.Union[Member, int]
    ):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/unmute", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group),
                "memberId": self.handleTargetAsMember(member),
            }
        ), raise_exception=True)

    async def kick(self,
        group: T.Union[Group, int],
        member: T.Union[Member, int],
        kickMessage: T.Optional[str] = None
    ):
        return assertOperatorSuccess(
            await fetch.http_post(f"{self.baseurl}/kick", {
                "sessionKey": self.session_key,
                "target": self.handleTargetAsGroup(group),
                "memberId": self.handleTargetAsMember(member),
                **({
                    "msg": kickMessage
                } if kickMessage else {})
            }
        ), raise_exception=True)

    async def sendQuoteMessage(self,
        target: T.Union[components.Source, int],
        message: MessageChain
    ):
        return assertOperatorSuccess(await fetch.http_post(f"{self.baseurl}/sendQuoteMessage", {
            "sessionKey": self.session_key,
            "target": target if isinstance(target, int) else\
                target.id if isinstance(target, components.Source) else\
                    raiser(ValueError("invaild source id/uid")),
            "messageChain": self.handleMessageAsGroup(message)
        }), raise_exception=True)

    def handleMessageAsGroup(
        self,
        message: T.Union[
            MessageChain,
            BaseMessageComponent,
            T.List[BaseMessageComponent],
            str
        ]):
        return json.loads(message.json()) \
            if isinstance(message, MessageChain) else \
                [json.loads(message.json())] \
            if isinstance(message, BaseMessageComponent) else \
                [
                    json.loads(i.json()) \
                        # 对Image特殊处理
                        if type(i) != components.Image else \
                            {"type": "Image", "imageId": i.asGroupImage()} \
                    for i in message
                ] \
            if isinstance(message, (tuple, list)) else \
                message \
            if isinstance(message, str) else \
                raiser(ValueError("invaild message(s)."))

    def handleMessageAsFriend(
        self,
        message: T.Union[
            MessageChain,
            BaseMessageComponent,
            T.List[BaseMessageComponent],
            str
        ]):
        return json.loads(message.json()) \
            if isinstance(message, MessageChain) else \
                [json.loads(message.json())] \
            if isinstance(message, BaseMessageComponent) else \
                [
                    json.loads(i.json()) \
                        # 对Image特殊处理
                        if type(i) != components.Image else \
                            {"type": "Image", "imageId": i.asFriendImage()} \
                    for i in message
                ] \
            if isinstance(message, (tuple, list)) else \
                message \
            if isinstance(message, str) else \
                raiser(ValueError("invaild message(s)."))

    def handleTargetAsGroup(self, target: T.Union[Group, int]):
        return target if isinstance(target, int) else \
            target.id if isinstance(target, Group) else \
                raiser(ValueError("invaild target as group."))

    def handleTargetAsFriend(self, target: T.Union[Friend, int]):
        return target if isinstance(target, int) else \
            target.id if isinstance(target, Friend) else \
                raiser(ValueError("invaild target as a friend obj."))

    def handleTargetAsMember(self, target: T.Union[Member, int]):
        return target if isinstance(target, int) else \
            target.id if isinstance(target, Member) else \
                raiser(ValueError("invaild target as a member obj."))