"""
The Docker Container "database" needs to run to execute this tests properly
"""

import unittest
from app import db
from Models.ProjectModel import ProjectModel
from Models.UserModel import UserModel
from Models.ChannelModel import ChannelModel
from Models.JoinRequestModel import JoinRequestModel

from Rest.ProjectsREST import ProjectsREST
from Rest.Answer import MessageCodes


def prepare_db():
    db.session.commit()
    db.drop_all()
    db.create_all()
    db.session.commit()


def setup_one():
    """
    One user, three projects (one, two, three) with own channels.
    The user attends two of the projects (project one and two).
    Project one and two are private, project three is public.
    """
    prepare_db()
    # create user
    user_one = UserModel.create("user_1", "randomemail@email.de", "randompassword")
    db.session.add(user_one)
    # create three channels
    channel_one, channel_two, channel_three = ChannelModel(), ChannelModel(), ChannelModel()
    db.session.add(channel_one)
    db.session.add(channel_two)
    db.session.add(channel_three)
    db.session.commit()
    # create three projects and assign user to project one and two as attendee
    project_one = ProjectModel(name="A_proj_one", channel_id=channel_one.id, has_exam=True)
    project_two = ProjectModel(name="B_proj_two", channel_id=channel_two.id, has_exam=False)
    project_three = ProjectModel(name="C_proj_three", channel_id=channel_three.id, has_exam=False, is_private=False)
    db.session.add(project_one)
    db.session.add(project_two)
    db.session.add(project_three)
    # let users attend
    project_two.attendees.append(user_one)
    project_one.attendees.append(user_one)
    db.session.commit()
    return {"project_one": project_one, "project_two": project_two, "project_three": project_three,
            "user_one": user_one}


def setup_two():
    """
    Two user, Two Projects (with own distinct channels), user one is leader of project one, user two is leader of project two.
    Both Projects are private. Non of the users attend any projects. A JoinProjectRequest exits: user two wants to join project one.
    """
    prepare_db()
    # create two user
    user_one = UserModel.create("user_1", "randomemail1@email.de", "randompassword1")
    user_two = UserModel.create("user_2", "randomemail2@email.de", "randompassword2")
    db.session.add(user_one)
    db.session.add(user_two)
    # create two channels
    channel_one, channel_two = ChannelModel(), ChannelModel()
    db.session.add(channel_one)
    db.session.add(channel_two)
    db.session.commit()
    # create two projects
    project_one = ProjectModel(name="A_proj_one", channel_id=channel_one.id, has_exam=True, project_leader=[user_one])
    project_two = ProjectModel(name="B_proj_two", channel_id=channel_two.id, has_exam=False, project_leader=[user_two])
    db.session.add(project_one)
    db.session.add(project_two)
    # create join project request
    join_project_request = JoinRequestModel(user=user_two, project=project_one)
    db.session.add(join_project_request)
    db.session.commit()
    return {"project_one": project_one, "project_two": project_two, "user_one": user_one, "user_two": user_two,
            "join_project_request": join_project_request}


def setup_three():
    """
    Three users, Two projects.
    User one is leader of project one. User one and two attend project one.
    User three is banned from project one.
    """
    prepare_db()
    # create users
    user_one = UserModel.create("user_1", "randomemail1@email.de", "randompassword1")
    user_two = UserModel.create("user_2", "randomemail2@email.de", "randompassword2")
    user_three = UserModel.create("user_3", "randomemail3@email.de", "randompassword3")
    # create two channels
    channel_one, channel_two = ChannelModel(), ChannelModel()
    [db.session.add(channel) for channel in [channel_one, channel_two]]
    db.session.commit()
    # create two projects
    project_one = ProjectModel(name="A_proj_one", channel_id=channel_one.id, has_exam=True, project_leader=[user_one])
    project_two = ProjectModel(name="B_proj_two", channel_id=channel_two.id, has_exam=False)
    [db.session.add(proj) for proj in [project_one, project_two]]
    db.session.commit()
    # let users attend / ban
    project_one.attendees.extend([user_one, user_two])
    project_one.black_list.append(user_three)
    db.session.commit()

    return {"user_one": user_one, "user_two": user_two, "user_three": user_three,
            "project_one": project_one, "project_two": project_two}


class TestProjectRest(unittest.TestCase):

    def compare_projects(self, project_after, created_project):
        # check id, name, has_exam
        self.assertEqual(project_after.id, created_project.id)
        self.assertEqual(project_after.name, created_project.name)
        self.assertEqual(project_after.has_exam, created_project.has_exam)

    def test_setup_three(self):
        setup = setup_three()
        user_one = setup["user_one"]
        user_two = setup["user_two"]
        user_three = setup["user_three"]
        project_one = setup["project_one"]
        project_two = setup["project_two"]
        # check attendance
        self.assertTrue([user_one] == project_one.project_leader)
        self.assertTrue(user_one in project_one.attendees)
        self.assertTrue(user_two in project_one.attendees)
        self.assertTrue(user_three not in project_one.attendees)
        [self.assertTrue(user not in project_two.attendees) for user in [user_one, user_two, user_three]]
        self.assertTrue(user_three in project_one.black_list)
        # check unique channels
        self.assertNotEqual(project_one.channel, project_two.channel)

    def test_setup_two(self):
        setup = setup_two()
        user_one = setup["user_one"]
        user_two = setup["user_two"]
        project_one = setup["project_one"]
        project_two = setup["project_two"]
        join_project_request = setup["join_project_request"]
        # check attendance
        self.assertTrue(user_one not in project_one.attendees)
        self.assertTrue(user_one not in project_two.attendees)
        self.assertTrue(user_two not in project_one.attendees)
        self.assertTrue(user_two not in project_two.attendees)
        # check unique channels
        self.assertNotEqual(project_one.channel, project_two.channel)
        # check private public
        self.assertTrue(project_one.is_private)
        self.assertTrue(project_two.is_private)
        # check join request
        self.assertTrue(join_project_request.project == project_one)
        self.assertTrue(join_project_request.user == user_two)

    def test_setup_one(self):
        setup = setup_one()
        user_one = setup["user_one"]
        project_one = setup["project_one"]
        project_two = setup["project_two"]
        project_three = setup["project_three"]
        # check attendance
        self.assertTrue(user_one in project_one.attendees)
        self.assertTrue(user_one in project_two.attendees)
        self.assertTrue(user_one not in project_three.attendees)
        # check unique channels
        self.assertNotEqual(project_one.channel, project_two.channel)
        self.assertNotEqual(project_one.channel, project_three.channel)
        self.assertNotEqual(project_two.channel, project_three.channel)
        # check private public
        self.assertTrue(project_one.is_private)
        self.assertTrue(project_two.is_private)
        self.assertFalse(project_three.is_private)

    def test_get_my_projects(self):
        # SETUP
        setup = setup_one()
        user_one = setup["user_one"]
        project_one = setup["project_one"]
        project_two = setup["project_two"]

        # ACTION
        get_projects_my_result = ProjectsREST.get_projects_my(user_id=user_one.id)

        # RESULT
        projects = get_projects_my_result.result
        # right message code
        self.assertEqual(MessageCodes.WORKED.value, get_projects_my_result.message)
        # two projects are found for the user
        self.assertEqual(len(projects), 2)
        # sort projects to simplify content checking
        projects.sort(key=lambda project: project.name)
        project_one_after = projects[0]
        project_two_after = projects[1]
        # check id, name, has_exam
        self.compare_projects(project_one_after, project_one)
        self.compare_projects(project_two_after, project_two)
        # check if same project is returned
        self.assertEqual(ProjectModel.query.get(project_one_after.id), project_one)
        self.assertEqual(ProjectModel.query.get(project_two_after.id), project_two)

    def test_get_all_projects(self):
        # SETUP
        setup = setup_one()
        project_one = setup["project_one"]
        project_two = setup["project_two"]
        project_three = setup["project_three"]

        # ACTION
        get_projects_all_result = ProjectsREST.get_projects_all()

        # RESULT
        projects = get_projects_all_result.result
        # right message code
        self.assertEqual(MessageCodes.WORKED.value, get_projects_all_result.message)
        # three projects are returned (all)
        self.assertEqual(len(projects), 3)
        # sort projects to simplify content checking
        projects.sort(key=lambda project: project.name)
        project_one_after = projects[0]
        project_two_after = projects[1]
        project_three_after = projects[2]
        self.compare_projects(project_one_after, project_one)
        self.compare_projects(project_two_after, project_two)
        self.compare_projects(project_three_after, project_three)
        # check if same project is returned
        self.assertEqual(ProjectModel.query.get(project_one_after.id), project_one)
        self.assertEqual(ProjectModel.query.get(project_two_after.id), project_two)
        self.assertEqual(ProjectModel.query.get(project_three_after.id), project_three)

    def test_join_project_success(self):
        # SETUP
        setup = setup_one()
        user_one = setup["user_one"]
        project_three = setup["project_three"]

        # ACTION
        join_project_result = ProjectsREST.post_projects_join(user_one.id, project_three.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_JOINED_SUCCESS.value, join_project_result.message)
        # user attends project three now
        user_one_after = UserModel.query.get(user_one.id)
        project_three_after = ProjectModel.query.get(project_three.id)
        self.assertTrue(user_one_after in project_three_after.attendees)

    def test_join_project_private_success(self):
        # SETUP
        setup = setup_two()
        user_one = setup["user_one"]
        project_two = setup["project_two"]

        # ACTION
        join_project_result = ProjectsREST.post_projects_join(user_one.id, project_two.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_SENT_JOIN_REQUEST.value, join_project_result.message)
        # user does not attend project now
        user_one_after = UserModel.query.get(user_one.id)
        project_two_after = ProjectModel.query.get(project_two.id)
        self.assertTrue(user_one_after not in project_two_after.attendees)
        # a join request for project two was created
        join_request = JoinRequestModel.query.filter(JoinRequestModel.project == project_two).first()
        self.assertTrue(join_request)
        self.assertTrue(join_request.user_id == user_one_after.id)

    def test_join_project_banned(self):
        setup = setup_three()
        user_three = setup["user_three"]
        project_one = setup["project_one"]

        # ACTION
        try_join_result = ProjectsREST.post_projects_join(user_three.id, project_one.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_YOUR_BANNED.value, try_join_result.message)
        # user did not join the project
        self.assertTrue(user_three not in project_one.attendees)

    def test_accept_join_project_request_success(self):
        # SETUP
        setup = setup_two()
        user_one = setup["user_one"]
        user_two = setup["user_two"]
        project_one = setup["project_one"]
        join_project_request = setup["join_project_request"]

        # ACTION
        accept_join_result = ProjectsREST.post_projects_join_request(user_one.id, join_project_request.id, False)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_JR_WAS_ACCEPTED.value, accept_join_result.message)
        # request is deleted
        join_request_after = JoinRequestModel.query.get(join_project_request.id)
        self.assertTrue(join_request_after is None)
        # user that sent the request is now in project
        user_two_after = UserModel.query.get(user_two.id)
        project_one_after = ProjectModel.query.get(project_one.id)
        self.assertTrue(user_two_after in project_one_after.attendees)

    def test_accept_join_project_request_fail(self):
        # SETUP
        setup = setup_two()
        user_one = setup["user_one"]
        user_two = setup["user_two"]
        project_one = setup["project_one"]
        join_project_request = setup["join_project_request"]

        # ACTION
        accept_join_result = ProjectsREST.post_projects_join_request(user_two.id, join_project_request.id, False)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_ONLY_FOR_LEADERS.value, accept_join_result.message)
        # user is not added to project
        user_two_after = UserModel.query.get(user_two.id)
        project_one_after = ProjectModel.query.get(project_one.id)
        self.assertTrue(user_two_after not in project_one_after.attendees)

    def test_get_project_join_requests(self):
        setup = setup_two()
        user_one = setup["user_one"]
        user_two = setup["user_two"]
        project_one = setup["project_one"]
        join_project_request = setup["join_project_request"]

        # ACTION
        get_join_request_result = ProjectsREST.get_projects_join_request(user_one.id, project_one.id)
        # right message code
        self.assertEqual(MessageCodes.WORKED.value, get_join_request_result.message)
        # check data
        jr_after = get_join_request_result.result[0]
        self.assertEqual(JoinRequestModel.query.get(jr_after.id), join_project_request)
        self.assertEqual(UserModel.query.get(jr_after.user_id), user_two)
        self.assertEqual(UserModel.query.get(jr_after.user_id).display_name, user_two.username)
        self.assertEqual(jr_after.denied, False)

    def test_double_join_project_private_fail(self):
        # SETUP
        setup = setup_two()
        user_one = setup["user_one"]
        user_two = setup["user_two"]
        project_one = setup["project_one"]
        project_two = setup["project_two"]
        join_project_request = setup["join_project_request"]

        # ACTION
        join_project_result = ProjectsREST.post_projects_join(user_two.id, project_one.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_ALREADY_SENT_JOIN_REQUEST.value, join_project_result.message)
        # user does not attend project now
        user_two_after = UserModel.query.get(user_two.id)
        project_one_after = ProjectModel.query.get(project_one.id)
        self.assertTrue(user_two_after not in project_one_after.attendees)
        # project still has one request
        self.assertEqual(len(JoinRequestModel.query.filter(JoinRequestModel.project == project_one_after).all()), 1)

    def test_leave_project_success(self):
        # SETUP
        setup = setup_one()
        user_one = setup["user_one"]
        project_one = setup["project_one"]

        # ACTION
        leave_project_result = ProjectsREST.post_projects_leave(user_one.id, project_one.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_LEFT_SUCCESS.value, leave_project_result.message)
        # user does not attend project one anymore
        user_one_after = UserModel.query.get(user_one.id)
        project_one_after = ProjectModel.query.get(project_one.id)
        self.assertTrue(user_one_after not in project_one_after.attendees)

    def test_ban_user_success(self):
        # SETUP
        setup = setup_three()
        user_one = setup['user_one']
        user_two = setup['user_two']
        project_one = setup['project_one']

        # ACTION
        ban_user_result = ProjectsREST.post_ban_user(user_one.id, project_one.id, user_two.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_BANNED_SUCCESSFULLY.value, ban_user_result.message)
        # user two is not attending proj_one
        self.assertTrue(user_two not in project_one.attendees)

    def test_ban_self_no_success(self):
        # SETUP
        setup = setup_three()
        user_one = setup['user_one']
        project_one = setup['project_one']

        # ACTION
        ban_self_result = ProjectsREST.post_ban_user(user_one.id, project_one.id, user_one.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_CANT_BAN_YOURSELF.value, ban_self_result.message)
        # user one is still attending proj_one
        self.assertTrue(user_one in project_one.attendees)


    def test_create_project_success(self):
        # SETUP
        setup = setup_three()
        user_three = setup['user_three']

        # ACTION
        create_project_result = ProjectsREST.put_projects_create(user_three.id, "New Project", True, True)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_CREATED.value, create_project_result.message)
        # new project exists
        project = ProjectModel.query.filter_by(name="New Project").first()
        self.assertTrue(project)
        # user that created project is leader and attending
        self.assertEqual(project.project_leader[0].id, user_three.id)
        self.assertTrue(user_three in project.attendees)

    def test_delete_project_success(self):
        # SETUP
        setup = setup_two()
        user_one = setup['user_one']
        project_one = setup['project_one']

        # ACTION
        delete_project_result = ProjectsREST.delete_projects(user_one.id, project_one.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_DELETED.value, delete_project_result.message)
        # project no longer exists
        project_one_after = ProjectModel.query.filter(ProjectModel.id==project_one.id).first()
        self.assertFalse(project_one_after)

    def test_delete_project_fail(self):
        # SETUP
        setup = setup_two()
        user_two = setup['user_two']
        project_one = setup['project_one']

        # ACTION
        delete_project_result = ProjectsREST.delete_projects(user_two.id, project_one.id)

        # RESULT
        # right message code
        self.assertEqual(MessageCodes.PROJECT_ONLY_FOR_LEADERS.value, delete_project_result.message)
        # project still exists
        project_one_after = ProjectModel.query.filter_by(id=project_one.id).first()
        self.assertTrue(project_one_after)

if __name__ == '__main__':
    unittest.main()
