from sqlalchemy import or_

from Models import ChannelModel, InviteModel, ProjectTypeModel, ProjectModel, moderator_project, UserModel, \
    JoinRequestModel, GroupMatchingDeclarationModel, MediaModel
from app import db

from Rest.Answer import Answer, MessageCodes, try_catch_error_answer


def check_moderator(user):
    moderator = moderator_project()
    if user.moderation_flags.is_moderator:
        if user not in moderator.attendees:
            moderator.attendees.append(user)
            db.session.commit()
    else:
        if user in moderator.attendees:
            moderator.attendees.remove(user)
            db.session.commit()


@try_catch_error_answer
class ProjectsREST:
    @staticmethod
    def get_project(user, project_id):
        answer = Answer()
        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
        if not project.is_visible and user not in project.attendees and not user.moderation_flags.is_moderator:
            return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_ATTENDEES)
        return answer.add_message(MessageCodes.WORKED).add_result(project)

    @staticmethod
    def post_project(user, json_args):
        answer = Answer()
        keys = json_args.keys()
        if "id" in keys:
            project = ProjectModel.query.get(json_args["id"])
            if not project:
                return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
            if user not in project.project_leader and not user.moderation_flags.is_moderator:
                return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_LEADERS)
            if "project_leader_id" in keys:
                new_leader = []
                for leader_id in json_args["project_leader_id"]:
                    new_leader.append(UserModel.query.get(leader_id))
                if None in new_leader:
                    return answer.add_message(MessageCodes.USER_NO_SUCH_USER)
                for leader in new_leader:
                    if leader not in project.attendees:
                        return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_ATTENDEES)
                project.project_leader = new_leader
            if "name" in keys:
                project.name = json_args["name"]
            if "description" in keys:
                project.description = json_args["description"]
            if "is_private" in keys:
                project.is_private = json_args["is_private"]
            if "is_visible" in keys:
                project.is_visible = json_args["is_visible"]
            if "has_exam" in keys:
                project.has_exam = json_args["has_exam"]
            if "project_type" in keys:
                project.set_project_type(json_args["project_type"])
            if "show_only_reviewed_media" in keys:
                project.show_only_reviewed_media = json_args["show_only_reviewed_media"]
            db.session.commit()
            answer.add_message(MessageCodes.WORKED)
        else:
            if "name" in keys and json_args["name"]:
                name = json_args["name"]
            else:
                name = user.username + "'s Project"
            if "description" in keys:
                description = json_args["description"]
            else:
                description = None
            if "is_private" in keys:
                is_private = json_args["is_private"]
            else:
                is_private = True
            if "is_visible" in keys:
                is_visible = json_args["is_visible"]
            else:
                is_visible = False
            if "has_exam" in keys:
                has_exam = json_args["has_exam"]
            else:
                has_exam = False
            if "project_type" in keys and json_args["project_type"]:
                project_type = ProjectTypeModel.from_string(json_args["project_type"])
            else:
                project_type = ProjectTypeModel.get_default()
            if "show_only_reviewed_media" in keys:
                show_only_reviewed_media = json_args["show_only_reviewed_media"]
            else:
                show_only_reviewed_media = False
            project = ProjectModel(channel=ChannelModel(),
                                   project_leader=[user],
                                   is_private=is_private,
                                   is_visible=is_visible,
                                   has_exam=has_exam,
                                   name=name,
                                   description=description,
                                   show_only_reviewed_media=show_only_reviewed_media,
                                   project_type=project_type)
            # creator should be attending
            project.attendees.append(user)
            db.session.add(project)
            db.session.commit()
            answer.add_message(MessageCodes.PROJECT_CREATED)
        if "media" in keys:
            all_media = MediaModel.query.filter(MediaModel.project == project).order_by(MediaModel.order).all()
            media_list = json_args["media"]
            last_index = 0
            for i in range(len(media_list)):
                if "id" in media_list[i].keys():
                    media = MediaModel.query.get(media_list[i]["id"])
                    if not media:
                        return answer.add_message(MessageCodes.PROJECT_NO_SUCH_MEDIA)
                    if media not in all_media:
                        return answer.add_message(MessageCodes.PROJECT_NO_SUCH_MEDIA)
                    all_media.remove(media)
                    media.order = i
                    if "reviewed" in media_list[i].keys():
                        media.reviewed = media_list[i]["reviewed"]
                    if "title" in media_list[i].keys():
                        media.title = media_list[i]["title"]
                    if "description" in media_list[i].keys():
                        media.description = media_list[i]["description"]
                    if "link" in media_list[i].keys():
                        media.link = media_list[i]["link"]
                    if "icon" in media_list[i].keys():
                        media.icon = media_list[i]["icon"]
                else:
                    if "reviewed" in media_list[i].keys():
                        reviewed = media_list[i]["reviewed"]
                    else:
                        reviewed = False
                    if "title" in media_list[i].keys():
                        title = media_list[i]["title"]
                    else:
                        title = None
                    if "description" in media_list[i].keys():
                        description = media_list[i]["description"]
                    else:
                        description = None
                    if "link" in media_list[i].keys():
                        link = media_list[i]["link"]
                    else:
                        link = None
                    if "icon" in media_list[i].keys():
                        icon = media_list[i]["icon"]
                    else:
                        icon = None
                    media = MediaModel(
                        project=project,
                        order=i,
                        reviewed=reviewed,
                        title=title,
                        description=description,
                        link=link,
                        icon=icon,
                        submitter=user
                    )
                    db.session.add(media)
                last_index = i
            for media in all_media:
                last_index += 1
                media.order = last_index
            db.session.commit()
        if "delete_media" in keys:
            for media in json_args["delete_media"]:
                if "id" in media.keys():
                    media = MediaModel.query.get(media["id"])
                    if not media:
                        return answer.add_message(MessageCodes.PROJECT_NO_SUCH_MEDIA)
                    if media.project != project:
                        return answer.add_message(MessageCodes.PROJECT_NO_SUCH_MEDIA)
                    media.delete()
            db.session.commit()
        return answer.add_result(project)

    @staticmethod
    def delete_projects(user, project_id):
        answer = Answer()

        # check if user is leader
        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
        if user not in project.project_leader and not user.moderation_flags.can_delete:
            return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_LEADERS)

        project.delete()

        return answer.add_message(MessageCodes.PROJECT_DELETED)

    @staticmethod
    def get_projects_my(user):
        answer = Answer()

        check_moderator(user)
        projects = user.personal_information.projects

        # check if there are projects
        if len(projects) == 0:
            return answer.add_message(MessageCodes.PROJECT_DIDNT_FIND_ANY).add_result(projects)

        # return projects
        return answer \
            .add_message(MessageCodes.WORKED) \
            .add_result(projects)

    @staticmethod
    def get_projects_all(user):
        answer = Answer()

        check_moderator(user)
        projects = ProjectModel.query.filter(
            or_(ProjectModel.is_visible == True, ProjectModel.attendees.any(id=user.id))).order_by(
            ProjectModel.len_attendees.desc()).order_by(ProjectModel.name.asc()).order_by(ProjectModel.id.asc()).all()
        # check if there are projects
        if len(projects) == 0:
            return answer.add_message(MessageCodes.PROJECT_DIDNT_FIND_ANY).add_result([])

        return answer \
            .add_message(MessageCodes.WORKED) \
            .add_result(projects)

    @staticmethod
    def post_projects_join(user, project_id):
        answer = Answer()

        # get project and user
        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)

        # check if already joined
        if user in project.attendees:
            return answer.add_message(MessageCodes.PROJECT_ALREADY_JOINED)
        # check if user is blacklisted
        if user in project.black_list:
            return answer.add_message(MessageCodes.PROJECT_YOUR_BANNED)
        # check if user has an invite:
        invite = InviteModel.query.filter(
            InviteModel.invitee == user,
            InviteModel.project == project).first()
        if invite:
            db.session.delete(invite)
            project.attendees.append(user)
            db.session.commit()
            return answer.add_message(MessageCodes.PROJECT_JOINED_SUCCESS)
        # check if project is visible
        if not project.is_visible:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
        # send join request if project is private
        if project.is_private:
            # check if there already is one
            already_join_requests = JoinRequestModel.query.filter(
                JoinRequestModel.project == project,
                JoinRequestModel.user == user).all()
            if already_join_requests:
                return answer.add_message(MessageCodes.PROJECT_ALREADY_SENT_JOIN_REQUEST)
            # send request
            join_request = JoinRequestModel(user=user, project=project)
            db.session.add(join_request)
            db.session.commit()
            return answer.add_message(MessageCodes.PROJECT_SENT_JOIN_REQUEST)

        # let user join if project is public

        project.attendees.append(user)
        if project == moderator_project():
            user.moderation_flags.is_moderator = True
        db.session.commit()
        return answer.add_message(MessageCodes.PROJECT_JOINED_SUCCESS)

    @staticmethod
    def post_projects_leave(user, project_id):
        answer = Answer()

        # get project and user
        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)

        # check if user is still already out of the project
        if user not in project.attendees:
            return answer.add_message(MessageCodes.PROJECT_ALREADY_LEFT)

        # let user leave
        project.attendees.remove(user)
        if user in project.project_leader:
            project.project_leader.remove(user)
        declaration = GroupMatchingDeclarationModel.query.filter_by(declared_project=project).first()
        if declaration and user in declaration.matchable_users:
            declaration.matchable_users.remove(user)
        if project == moderator_project():
            user.moderation_flags.is_moderator = False
        db.session.commit()
        return answer.add_message(MessageCodes.PROJECT_LEFT_SUCCESS)

    @staticmethod
    def post_projects_join_request(user, request_id, denied):
        answer = Answer()
        request = JoinRequestModel.query.get(request_id)
        if not request:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_JOIN_REQUEST)
        project = request.project
        user_to_join = request.user

        # check leader status of user making the post
        if user not in project.project_leader:
            return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_LEADERS)
        # keep the request if denied
        if denied:
            request.denied = True
            db.session.commit()
            return answer.add_message(MessageCodes.PROJECT_JR_WAS_DENIED)
        else:
            db.session.delete(request)
            project.attendees.append(user_to_join)
            db.session.commit()
            return answer.add_message(MessageCodes.PROJECT_JR_WAS_ACCEPTED)

    @staticmethod
    def get_projects_join_request(user, project_id):
        answer = Answer()
        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
        # check for leader
        if user not in project.project_leader:
            return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_LEADERS)

        join_requests = project.join_requests

        return answer \
            .add_message(MessageCodes.WORKED) \
            .add_result(join_requests)

    @staticmethod
    def post_projects_invite(inviter, project_id, invitee_id):
        answer = Answer()
        invitee = UserModel.query.get(invitee_id)
        if not invitee:
            return answer.add_message(MessageCodes.USER_NO_SUCH_USER)
        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)

        if invitee in project.attendees:
            return answer.add_message(MessageCodes.PROJECT_ALREADY_JOINED)

        if inviter not in project.project_leader:
            return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_LEADERS)

        if inviter.moderation_flags.is_muted():
            return answer.add_message(MessageCodes.USER_YOUR_MUTED)

        if InviteModel.query.filter(
                InviteModel.invitee_id == invitee_id,
                InviteModel.project_id == project_id).first():
            return answer.add_message(MessageCodes.PROJECT_USER_ALREADY_INVITED)

        invite = InviteModel(inviter=inviter, invitee=invitee, project=project)
        db.session.add(invite)
        db.session.commit()
        return answer.add_message(MessageCodes.WORKED)

    @staticmethod
    def get_projects_invites(user, project_id):
        answer = Answer()

        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
        # check for leader
        if user not in project.project_leader:
            return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_LEADERS)

        invites = project.invites

        return answer \
            .add_message(MessageCodes.WORKED) \
            .add_result(invites)

    @staticmethod
    def get_user_invites(user, switch):
        answer = Answer()

        if switch == 'ignored':
            invites = InviteModel.query.filter(
                InviteModel.invitee == user,
                InviteModel.ignored == True).all()
        elif switch == 'not_ignored':
            invites = InviteModel.query.filter(
                InviteModel.invitee == user,
                InviteModel.ignored == False).all()
        else:
            invites = InviteModel.query.filter(InviteModel.invitee == user).all()

        return answer \
            .add_message(MessageCodes.WORKED) \
            .add_result(invites)

    @staticmethod
    def post_ignore_project_invite(user, invite_id, ignore):
        answer = Answer()

        invite = InviteModel.query.get(invite_id)
        if not invite:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_INVITE)
        if invite.invitee != user:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_INVITE)

        invite.ignored = ignore
        db.session.commit()
        return answer.add_message(MessageCodes.WORKED)

    @staticmethod
    def post_ban_user(user, project_id, user_to_ban_id):
        answer = Answer()

        project = ProjectModel.query.get(project_id)
        user_to_ban = UserModel.query.get(user_to_ban_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
        if not user_to_ban:
            return answer.add_message(MessageCodes.USER_NO_SUCH_USER)
        # check if user is project leader or can ban
        if user not in project.project_leader and not user.moderation_flags.can_ban:
            return answer.add_message(MessageCodes.CHANNEL_ONLY_FOR_PROJECT_LEADERS)
        # check if the user to be banned is not the leader itself
        elif user == user_to_ban:
            return answer.add_message(MessageCodes.PROJECT_CANT_BAN_YOURSELF)
        # check if user to ban is in project
        if user_to_ban not in project.attendees:
            return answer.add_message(MessageCodes.CHANNEL_USER_NOT_IN_PROJECT)
        project.attendees.remove(user_to_ban)
        project.black_list.append(user_to_ban)
        db.session.commit()
        return answer.add_message(MessageCodes.PROJECT_BANNED_SUCCESSFULLY)

    @staticmethod
    def post_unban_user(user, project_id, user_to_unban_id):
        answer = Answer()

        project = ProjectModel.query.get(project_id)
        user_to_unban = UserModel.query.get(user_to_unban_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)
        if not user_to_unban:
            return answer.add_message(MessageCodes.USER_NO_SUCH_USER)
        # check if user is project leader or can ban
        if user not in project.project_leader and not user.moderation_flags.can_ban:
            return answer.add_message(MessageCodes.CHANNEL_ONLY_FOR_PROJECT_LEADERS)
        # check if user to ban is in project
        if user_to_unban not in project.black_list:
            return answer.add_message(MessageCodes.PROJECT_CANT_UNBAN_NOT_BANNED)
        project.black_list.remove(user_to_unban)
        db.session.commit()
        return answer.add_message(MessageCodes.PROJECT_BANNED_SUCCESSFULLY)

    @staticmethod
    def get_project_users(user, project_id):
        answer = Answer()

        check_moderator(user)
        project = ProjectModel.query.get(project_id)
        if not project:
            return answer.add_message(MessageCodes.PROJECT_NO_SUCH_PROJECT)

        # check if user is attending
        if user not in project.attendees:
            return answer.add_message(MessageCodes.PROJECT_ONLY_FOR_ATTENDEES)
        # return attendees
        return answer.add_message(MessageCodes.WORKED).add_result(project.attendees)
