import json
from urllib.parse import quote, unquote
from typing import Optional, Union, List, Tuple, Any, Dict

from app.core.context import Context, MediaInfo
from app.log import logger
from app.modules import _ModuleBase, _MessageBase
from app.modules.vocechat.vocechat import VoceChat
from app.schemas import MessageChannel, CommingMessage, Notification
from app.schemas.types import ModuleType


class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
    _IMAGE_SUFFIXES = (
        ".png",
        ".jpg",
        ".jpeg",
        ".gif",
        ".webp",
        ".bmp",
        ".tiff",
        ".svg",
    )
    _AUDIO_SUFFIXES = (
        ".mp3",
        ".m4a",
        ".wav",
        ".ogg",
        ".oga",
        ".opus",
        ".aac",
        ".amr",
        ".flac",
        ".mpga",
        ".mpeg",
        ".webm",
    )

    def init_module(self) -> None:
        """
        初始化模块
        """
        super().init_service(service_name=VoceChat.__name__.lower(),
                             service_type=VoceChat)
        self._channel = MessageChannel.VoceChat

    @staticmethod
    def get_name() -> str:
        return "VoceChat"

    @staticmethod
    def get_type() -> ModuleType:
        """
        获取模块类型
        """
        return ModuleType.Notification

    @staticmethod
    def get_subtype() -> MessageChannel:
        """
        获取模块子类型
        """
        return MessageChannel.VoceChat

    @staticmethod
    def get_priority() -> int:
        """
        获取模块优先级，数字越小优先级越高，只有同一接口下优先级才生效
        """
        return 4

    def stop(self):
        pass

    def test(self) -> Optional[Tuple[bool, str]]:
        """
        测试模块连接性
        """
        if not self.get_instances():
            return None
        for name, client in self.get_instances().items():
            state = client.get_state()
            if not state:
                return False, f"VoceChat {name} 未就绪"
        return True, ""

    def init_setting(self) -> Tuple[str, Union[str, bool]]:
        pass

    @staticmethod
    def _get_admins(config: Optional[dict]) -> List[str]:
        """
        解析 VoceChat 管理员配置，兼容逗号分隔和首尾空白。
        """
        return [
            admin.strip()
            for admin in str((config or {}).get("VOCECHAT_ADMINS") or "").split(",")
            if admin.strip()
        ]

    @classmethod
    def _should_reject_admin_command(
            cls,
            config: Optional[dict],
            *user_ids: Optional[Union[str, int]],
    ) -> bool:
        """
        判断 VoceChat 斜杠命令是否应因非管理员身份被拒绝。
        """
        admins = cls._get_admins(config)
        if not admins:
            return False
        candidates = [
            str(user_id).strip()
            for user_id in user_ids
            if user_id is not None and str(user_id).strip()
        ]
        return not any(candidate in admins for candidate in candidates)

    @staticmethod
    def _send_admin_denied(
            client: Optional[VoceChat], userid: Optional[Union[str, int]]
    ) -> None:
        """
        向 VoceChat 非管理员用户发送命令拒绝提示。
        """
        if client and userid:
            client.send_msg(title="只有管理员才有权限执行此命令", userid=str(userid))

    def message_parser(self, source: str, body: Any, form: Any,
                       args: Any) -> Optional[CommingMessage]:
        """
        解析消息内容，返回字典，注意以下约定值：
        userid: 用户ID
        username: 用户名
        text: 内容
        :param source: 消息来源
        :param body: 请求体
        :param form: 表单
        :param args: 参数
        :return: 渠道、消息体
        """
        try:
            """
            {
              "created_at": 1672048481664, //消息创建的时间戳
              "detail": {
                "content": "hello this is my message to you", //消息内容
                "content_type": "text/plain", //消息类型，text/plain：纯文本消息，text/markdown：markdown消息，vocechat/file：文件类消息
                "expires_in": null, //消息过期时长，如果有大于0数字，说明该消息是个限时消息
                "properties": null, //一些有关消息的元数据，比如at信息，文件消息的具体类型信息，如果是个图片消息，还会有一些宽高，图片名称等元信息
                "type": "normal" //消息类型，normal代表是新消息
              },
              "from_uid": 7910, //来自于谁
              "mid": 2978, //消息ID
              "target": { "gid": 2 } //发送给谁，gid代表是发送给频道，uid代表是发送给个人，此时的数据结构举例：{"uid":1}
            }
            """
            # 获取服务配置
            client_config = self.get_config(source)
            if not client_config:
                return None
            client: VoceChat = self.get_instance(client_config.name)
            # 报文体
            msg_body = json.loads(body)
            # 类型
            msg_type = msg_body.get("detail", {}).get("type")
            if msg_type not in ("normal", "reply"):
                # 非新消息/回复
                return None
            logger.debug(f"收到VoceChat请求：{msg_body}")
            detail = msg_body.get("detail", {}) or {}
            content_type = detail.get("content_type") or ""
            content = detail.get("content")
            images = self._extract_images(detail)
            audio_refs = self._extract_audio_refs(detail)
            files = self._extract_files(detail)
            text = None
            if content_type in ("text/plain", "text/markdown") and isinstance(content, str):
                text = content
            # 用户ID
            gid = msg_body.get("target", {}).get("gid")
            channel_id = client_config.config.get("channel_id")
            if gid and str(gid) == str(channel_id):
                # 来自监听频道的消息
                userid = f"GID#{gid}"
            else:
                # 来自个人的消息
                userid = f"UID#{msg_body.get('from_uid')}"

            # 处理消息内容
            if (text or images or audio_refs or files) and userid:
                if text and text.startswith("/") and self._should_reject_admin_command(
                        client_config.config, msg_body.get("from_uid"), userid
                ):
                    self._send_admin_denied(client, userid)
                    return None
                logger.info(
                    f"收到来自 {client_config.name} 的VoceChat消息："
                    f"userid={userid}, text={text}, images={len(images) if images else 0}, "
                    f"audios={len(audio_refs) if audio_refs else 0}, files={len(files) if files else 0}"
                )
                return CommingMessage(channel=MessageChannel.VoceChat, source=client_config.name,
                                      userid=userid, username=userid, text=text or "",
                                      images=images, audio_refs=audio_refs, files=files)
        except Exception as err:
            logger.error(f"VoceChat消息处理发生错误：{str(err)}")
        return None

    @classmethod
    def _extract_images(
        cls, detail: dict
    ) -> Optional[List[CommingMessage.MessageImage]]:
        content_type = detail.get("content_type") or ""
        if content_type != "vocechat/file":
            return None
        properties = detail.get("properties") or {}
        mime_type = (
            properties.get("content_type")
            or properties.get("mime_type")
            or properties.get("contentType")
            or ""
        ).lower()
        file_path = (
            properties.get("path")
            or properties.get("file_path")
            or properties.get("storage_path")
            or detail.get("content")
        )
        direct_url = (
            properties.get("url")
            or properties.get("download_url")
            or properties.get("file_url")
        )
        file_name = (
            properties.get("name")
            or properties.get("filename")
            or (str(file_path).rsplit("/", 1)[-1] if file_path else "")
        ).lower()

        is_image = mime_type.startswith("image/") or file_name.endswith(cls._IMAGE_SUFFIXES)
        if not is_image:
            return None
        if isinstance(direct_url, str) and direct_url.startswith("http"):
            return [
                CommingMessage.MessageImage(
                    ref=direct_url,
                    name=properties.get("name") or properties.get("filename"),
                    mime_type=mime_type or None,
                    size=properties.get("size"),
                )
            ]
        if isinstance(file_path, str) and file_path:
            return [
                CommingMessage.MessageImage(
                    ref=f"vocechat://file/{quote(file_path, safe='')}",
                    name=properties.get("name") or properties.get("filename"),
                    mime_type=mime_type or None,
                    size=properties.get("size"),
                )
            ]
        return None

    @classmethod
    def _extract_audio_refs(cls, detail: dict) -> Optional[List[str]]:
        content_type = detail.get("content_type") or ""
        if content_type != "vocechat/file":
            return None
        properties = detail.get("properties") or {}
        mime_type = (
            properties.get("content_type")
            or properties.get("mime_type")
            or properties.get("contentType")
            or ""
        ).lower()
        file_path = (
            properties.get("path")
            or properties.get("file_path")
            or properties.get("storage_path")
            or detail.get("content")
        )
        file_name = (
            properties.get("name")
            or properties.get("filename")
            or (str(file_path).rsplit("/", 1)[-1] if file_path else "")
        ).lower()

        is_audio = mime_type.startswith("audio/") or file_name.endswith(cls._AUDIO_SUFFIXES)
        if not is_audio:
            return None
        if isinstance(file_path, str) and file_path:
            return [f"vocechat://file/{quote(file_path, safe='')}"]
        return None

    @classmethod
    def _extract_files(
        cls, detail: dict
    ) -> Optional[List[CommingMessage.MessageAttachment]]:
        content_type = detail.get("content_type") or ""
        if content_type != "vocechat/file":
            return None
        properties = detail.get("properties") or {}
        mime_type = (
            properties.get("content_type")
            or properties.get("mime_type")
            or properties.get("contentType")
            or ""
        ).lower()
        file_path = (
            properties.get("path")
            or properties.get("file_path")
            or properties.get("storage_path")
            or detail.get("content")
        )
        file_name = (
            properties.get("name")
            or properties.get("filename")
            or (str(file_path).rsplit("/", 1)[-1] if file_path else "")
        )
        lowered_name = str(file_name).lower()
        is_image = mime_type.startswith("image/") or lowered_name.endswith(
            cls._IMAGE_SUFFIXES
        )
        is_audio = mime_type.startswith("audio/") or lowered_name.endswith(
            cls._AUDIO_SUFFIXES
        )
        if is_image or is_audio or not isinstance(file_path, str) or not file_path:
            return None
        return [
            CommingMessage.MessageAttachment(
                ref=f"vocechat://file/{quote(file_path, safe='')}",
                name=file_name,
                mime_type=properties.get("content_type")
                or properties.get("mime_type")
                or properties.get("contentType"),
                size=properties.get("size"),
            )
        ]

    def post_message(self, message: Notification, **kwargs) -> None:
        """
        发送消息
        :param message: 消息内容
        :return: 成功或失败
        """
        for conf in self.get_configs().values():
            if not self.check_message(message, conf.name):
                continue
            targets = message.targets
            userid = message.userid
            if not message.userid and targets:
                userid = targets.get('vocechat_userid')
            client: VoceChat = self.get_instance(conf.name)
            if client:
                client.send_msg(title=message.title, text=message.text,
                                image=message.image, userid=userid, link=message.link)

    def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> None:
        """
        发送媒体信息选择列表
        :param message: 消息内容
        :param medias: 媒体列表
        :return: 成功或失败
        """
        for conf in self.get_configs().values():
            if not self.check_message(message, conf.name):
                continue
            client: VoceChat = self.get_instance(conf.name)
            if client:
                client.send_msg(title=message.title, userid=message.userid)
                client.send_medias_msg(title=message.title, medias=medias,
                                       userid=message.userid, link=message.link)

    def post_torrents_message(self, message: Notification, torrents: List[Context]) -> None:
        """
        发送种子信息选择列表
        :param message: 消息内容
        :param torrents: 种子列表
        :return: 成功或失败
        """
        for conf in self.get_configs().values():
            if not self.check_message(message, conf.name):
                continue
            targets = message.targets
            userid = message.userid
            if not userid and targets is not None:
                userid = targets.get('vocechat_userid')
                if not userid:
                    logger.warn(f"用户没有指定 VoceChat用户ID，消息无法发送")
                    return
            client: VoceChat = self.get_instance(conf.name)
            if client:
                client.send_torrents_msg(title=message.title, torrents=torrents,
                                         userid=userid, link=message.link)

    def register_commands(self, commands: Dict[str, dict]):
        pass

    def download_vocechat_image_to_data_url(self, image_ref: str, source: str) -> Optional[str]:
        """
        下载 VoceChat 图片并转换为 data URL
        """
        if not image_ref or not image_ref.startswith("vocechat://file/"):
            return None
        client_config = self.get_config(source)
        if not client_config:
            return None
        client: VoceChat = self.get_instance(client_config.name)
        if not client:
            return None
        file_path = unquote(image_ref.replace("vocechat://file/", "", 1))
        return client.download_file_to_data_url(file_path)

    def download_vocechat_file_bytes(self, file_ref: str, source: str) -> Optional[bytes]:
        """
        下载 VoceChat 文件并返回原始字节
        """
        if not file_ref or not file_ref.startswith("vocechat://file/"):
            return None
        client_config = self.get_config(source)
        if not client_config:
            return None
        client: VoceChat = self.get_instance(client_config.name)
        if not client:
            return None
        file_path = unquote(file_ref.replace("vocechat://file/", "", 1))
        file_data = client.download_file(file_path)
        if file_data:
            content, _ = file_data
            return content
        return None
