from datetime import datetime
from re import compile
from Notify.Firebase import notify_member
from Rest.Answer import Answer, MessageCodes, try_catch_error_answer
from Models import UserModel, ChannelModel, PostModel, ReactionModel, MainChatPostModel, LastReadMessageModel, \
    get_main_channel
from Rest.UserRegex import get_emoji_regex
from app import db

emoji_re = compile('^' + get_emoji_regex() + '$')


def get_post_from_channel(channel, limit, timestamp):
    # max limit
    if limit > 40:
        limit = 40
    return PostModel.query. \
        filter(PostModel.channel == channel, PostModel.date_created < timestamp). \
        order_by(PostModel.date_created.desc()). \
        limit(limit).all()


def valid_user(user, channel):
    if not user:
        return False
    # check if user is in project or chat of channel
    if channel == get_main_channel():
        pass
    elif channel.project:
        if user not in channel.project.attendees:
            return False
    elif channel.chat:
        if user not in channel.chat.users:
            return False
    else:
        return True
    return True


@try_catch_error_answer
class ChannelsREST:

    @staticmethod
    def get_channel_posts(user, channel_id, last_post_id, limit):
        answer = Answer()
        if not channel_id or channel_id == "":
            channel_id = get_main_channel().id
        channel = ChannelModel.query.get(channel_id)
        if not channel:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_CHANNEL)

        if not limit:
            limit = 20
        if limit > 40:
            limit = 40

        # check if user is in project or chat of channel
        if not valid_user(user, channel):
            return answer.add_message(MessageCodes.CHANNEL_NOT_AN_ATTENDEE)

        # get timestamp as start point, now or specified post
        if last_post_id:
            timestamp = PostModel.query.get(last_post_id).date_created
        else:
            timestamp = datetime.utcnow()

        returned_posts = get_post_from_channel(channel, limit, timestamp)

        return answer.add_message(MessageCodes.WORKED).add_result(returned_posts)

    @staticmethod
    def post_to_channel(user, channel_id, json_args):
        answer = Answer()
        channel = ChannelModel.query.get(channel_id)
        if not channel:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_CHANNEL)

        # check if user is in project or chat of channel
        if not valid_user(user, channel):
            return answer.add_message(MessageCodes.CHANNEL_NOT_AN_ATTENDEE)
        # check if user is muted
        if user.moderation_flags.is_muted():
            return answer.add_message(MessageCodes.USER_YOUR_MUTED)

        if channel.chat and user in channel.chat.get_other_user(user).blocked_user:
            return answer.add_message(MessageCodes.USER_BLOCKED_YOU)

        content = json_args["content"]
        image_id = json_args.get("image_id", None)
        anonymous = json_args.get("anonymous", False)
        if channel != get_main_channel():
            post = PostModel(channel=channel,
                             author=user,
                             content=content,
                             image_id=image_id,
                             date_created=datetime.utcnow(),
                             anonymous=anonymous)
        else:
            tags = json_args.get("tags", None)
            post = MainChatPostModel(author=user,
                                     tags=tags,
                                     content=content,
                                     image_id=image_id,
                                     date_created=datetime.utcnow(),
                                     anonymous=anonymous)
        db.session.add(post)
        db.session.commit()
        notify_member(post)
        return answer.add_message(MessageCodes.WORKED).add_result(post)

    @staticmethod
    def delete_channel_post(user, post_id):
        answer = Answer()

        post = PostModel.query.get(post_id)
        if not post:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_POST)
        if not valid_user(user, post.channel):
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_POST)

        if post.author != user and not user.moderation_flags.can_delete:
            return answer.add_message(MessageCodes.CHANNEL_ONLY_DELETE_OWN_POSTS)

        post.delete()
        return answer.add_message(MessageCodes.CHANNEL_POST_DELETED)

    @staticmethod
    def get_channels(user, json_args):
        answer = Answer()

        channel_ids = json_args['ids']

        channels = [ChannelModel.query.get(channel_id) for channel_id in channel_ids]
        for channel in channels:
            if not channel:
                return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_CHANNEL)

        returned_channels = []
        for channel in channels:
            if channel.project:
                if user not in channel.project.attendees:
                    continue
            if channel.chat:
                if user not in channel.chat.users:
                    continue

            returned_channels.append(channel)
        return answer.add_message(MessageCodes.WORKED).add_result(returned_channels)

    @staticmethod
    def get_post(user, post_id):
        answer = Answer()

        post = PostModel.query.get(post_id)
        if not post:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_POST)

        # check if user is allowed to read post
        if not valid_user(user, post.channel):
            return answer.add_message(MessageCodes.CHANNEL_NOT_AN_ATTENDEE)

        return answer.add_message(MessageCodes.WORKED).add_result(post)

    @staticmethod
    def post_message_read(user, post_id):
        answer = Answer()

        post = PostModel.query.get(post_id)
        if not post:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_POST)

        # check if user is allowed to read post
        if not valid_user(user, post.channel):
            return answer.add_message(MessageCodes.CHANNEL_NOT_AN_ATTENDEE)

        last_message = LastReadMessageModel.query.get((user.id, post.channel.id))
        if not last_message:
            last_message = LastReadMessageModel(user=user, channel=post.channel)
        last_message.post = post
        db.session.add(last_message)
        db.session.commit()
        return answer.add_message(MessageCodes.WORKED)

    @staticmethod
    def post_block_post(user, post_to_block_id):
        answer = Answer()

        post_to_block = PostModel.query.get(post_to_block_id)
        if not post_to_block:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_POST)
        if post_to_block in user.blocked_posts:
            return answer.add_message(MessageCodes.CHANNEL_POST_ALREADY_BLOCKED)

        user.blocked_posts.append(post_to_block)
        db.session.commit()
        return answer.add_message(MessageCodes.WORKED)

    @staticmethod
    def post_unblock_post(user, post_to_unblock_id):
        answer = Answer()

        post_to_unblock = PostModel.query.get(post_to_unblock_id)
        if not post_to_unblock:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_POST)
        if post_to_unblock not in user.blocked_posts:
            return answer.add_message(MessageCodes.CHANNEL_POST_NOT_BLOCKED)

        user.blocked_posts.remove(post_to_unblock)
        db.session.commit()
        return answer.add_message(MessageCodes.WORKED)

    @staticmethod
    def post_reaction(user, post_id, json_args):
        answer = Answer()

        emoji = json_args.get('emoji', '👍')
        remove = json_args.get('remove', False)

        if not bool(emoji_re.match(emoji)):
            return answer.add_message(MessageCodes.CHANNEL_NOT_AN_EMOJI)

        post = PostModel.query.get(post_id)
        if not post:
            return answer.add_message(MessageCodes.CHANNEL_NO_SUCH_POST)

        if not valid_user(user, post.channel):
            return answer.add_message(MessageCodes.CHANNEL_NOT_AN_ATTENDEE)

        existing = ReactionModel.query.filter(
            ReactionModel.user == user,
            ReactionModel.post == post,
            ReactionModel.emoji == emoji,
        ).first()

        if existing and not remove:
            return answer.add_message(MessageCodes.CHANNEL_ALREADY_REACTED)

        if not existing and remove:
            return answer.add_message(MessageCodes.CHANNEL_NOT_YET_REACTED)

        if not remove:
            reaction = ReactionModel(user=user, post=post, emoji=emoji)
            db.session.add(reaction)
        else:
            db.session.delete(existing)
        db.session.commit()
        return answer.add_message(MessageCodes.WORKED).add_result(post)
