import logging
import time
import threading
import emoji
from typing import TYPE_CHECKING, Set

import re
from fbchat.models import Thread, Message, TypingStatus, ThreadType, Mention, EmojiSize, MessageReaction
from ehforwarderbot import EFBMsg, MsgType
from ehforwarderbot.message import EFBMsgLinkAttribute, EFBMsgStatusAttribute, ChatType
from ehforwarderbot.exceptions import EFBMessageTypeNotSupported

from .utils import get_value

if TYPE_CHECKING:
    from . import FBMessengerChannel


class MasterMessageManager:

    logger = logging.getLogger("MasterMessageManager")

    def __init__(self, channel: 'FBMessengerChannel'):
        self.channel = channel
        channel.supported_message_types: Set[MsgType] = {MsgType.Text, MsgType.Image, MsgType.Sticker,
                                                         MsgType.Audio, MsgType.File, MsgType.Video,
                                                         MsgType.Status, MsgType.Unsupported}
        self.client = channel.client
        self.flag = channel.flag

    def send_message(self, msg: EFBMsg) -> EFBMsg:
        self.logger.debug("Received message from master: %s", msg)

        try:
            target_msg_offset = 0
            prefix = ""

            mentions = []

            # Send message reaction
            if msg.target and msg.text.startswith('r`') and getattr(MessageReaction, msg.text[2:], None)\
                    and msg.uid.startswith("mid.$"):
                self.logger.debug("[%s] Message is a reaction to another message: %s", msg.uid, msg.text)
                msg_id = ".".join(msg.target.uid.split(".", 2)[:2])
                self.client.reactToMessage(msg_id, getattr(MessageReaction, msg.text[2:]))
                msg.uid = "__reaction__"
                return msg

            # Target message
            if msg.target:
                self.logger.debug("[%s] Message replying to another message: %s", msg.uid, msg.target)
                if msg.target.chat.chat_type == ChatType.Group:
                    target_msg_name = msg.target.author.chat_alias or msg.target.author.chat_name
                    prefix = '@%s "%s"\n' % (target_msg_name, msg.target.text or msg.target.type)
                    mentions.append(Mention(msg.target.author.chat_uid, 1, len(target_msg_name)))
                else:
                    prefix = '"%s"\n' % msg.target.text or msg.target.type
                target_msg_offset = len(prefix)
                self.logger.debug("[%s] Converted to prefix: %s", msg.uid, prefix)

            # Message substitutions
            if msg.substitutions:
                self.logger.debug("[%s] Message has substitutions: %s", msg.uid, msg.substitutions)
                for i in msg.substitutions:
                    mentions.append(Mention(msg.substitutions[i].chat_uid,
                                            target_msg_offset + i[0], i[1] - i[0]))
                self.logger.debug("[%s] Translated to mentions: %s", msg.uid, mentions)

            fb_msg = Message(text=prefix + msg.text, mentions=mentions)
            thread: Thread = self.client.fetchThreadInfo(msg.chat.chat_uid)[str(msg.chat.chat_uid)]

            if msg.type in (MsgType.Text, MsgType.Unsupported):
                if msg.text[:-1] in emoji.UNICODE_EMOJI and msg.text[-1] in 'SML':
                    self.logger.debug("[%s] Message is an Emoji message: %s", msg.uid, msg.text)
                    if msg.text[-1] == 'S':
                        fb_msg.emoji_size = EmojiSize.SMALL
                    elif msg.text[-1] == 'M':
                        fb_msg.emoji_size = EmojiSize.MEDIUM
                    elif msg.text[-1] == 'L':
                        fb_msg.emoji_size = EmojiSize.LARGE
                    fb_msg.text = msg.text[:-1]
                msg.uid = self.client.send(fb_msg, thread_id=thread.uid, thread_type=thread.type)
            elif msg.type in (MsgType.Image, MsgType.Sticker):
                msg.uid = self.client.send_image_file(msg.path, msg.mime, message=fb_msg,
                                                      thread_id=thread.uid, thread_type=thread.type)
            elif msg.type == MsgType.Audio:
                file_id = self.upload_file(msg)
                msg.uid = self.client.send_audio(file_id=file_id, message=fb_msg,
                                                 thread_id=thread.uid, thread_type=thread.type)
            elif msg.type == MsgType.File:
                file_id = self.upload_file(msg)
                msg.uid = self.client.send_file(file_id=file_id, message=fb_msg,
                                                thread_id=thread.uid, thread_type=thread.type)
            elif msg.type == MsgType.Video:
                file_id = self.upload_file(msg)
                msg.uid = self.client.send_video(file_id=file_id, message=fb_msg,
                                                 thread_id=thread.uid, thread_type=thread.type)
            elif msg.type == MsgType.Status:
                attribute: EFBMsgStatusAttribute = msg.attributes
                if attribute.status_type in (EFBMsgStatusAttribute.Types.TYPING,
                                             EFBMsgStatusAttribute.Types.UPLOADING_AUDIO,
                                             EFBMsgStatusAttribute.Types.UPLOADING_VIDEO,
                                             EFBMsgStatusAttribute.Types.UPLOADING_IMAGE,
                                             EFBMsgStatusAttribute.Types.UPLOADING_FILE):
                    self.client.setTypingStatus(TypingStatus.TYPING, thread_id=thread.uid, thread_type=thread.type)
                    threading.Thread(target=self.stop_typing, args=(attribute.timeout, thread.uid, thread.type)).run()
            elif msg.type == MsgType.Link:
                attribute: EFBMsgLinkAttribute = msg.attributes
                if self.flag('send_link_with_description'):
                    info = (attribute.title,)
                    if attribute.description:
                        info += (attribute.description,)
                    info += (attribute.url,)
                    text = "\n".join(info)
                else:
                    text = attribute.url
                if fb_msg.text:
                    text = fb_msg.text + "\n" + text
                fb_msg.text = text
                msg.uid = self.client.send(fb_msg, thread_id=thread.uid, thread_type=thread.type)
            else:
                raise EFBMessageTypeNotSupported()
            return msg
        finally:
            if msg.file and not msg.file.closed:
                msg.file.close()
            self.client.markAsSeen()
            self.client.markAsRead(msg.chat.chat_uid)

    def stop_typing(self, timeout: int, thread_uid: str, thread_type: ThreadType):
        """Wait for a number of milliseconds, and stop typing."""
        time.sleep(timeout / 1000)
        self.client.setTypingStatus(TypingStatus.STOPPED, thread_id=thread_uid, thread_type=thread_type)

    def upload_file(self, msg: EFBMsg) -> int:
        """Upload media of a message as file, and return the file id."""
        response = self.client._postFile(self.client.req_url.UPLOAD, {
            'file': (msg.filename, msg.file, msg.mime)
        }, fix_request=True, as_json=True)
        return get_value(response, ('payload', 'metadata', 0, 'file_id')) or \
               get_value(response, ('payload', 'metadata', 0, 'audio_id')) or \
               get_value(response, ('payload', 'metadata', 0, 'video_id'))
