def login_user():
    """User verification and Login"""

    input_data = request.get_json(force=True)

    # validation check for required values
    if 'email' not in input_data.keys() or 'password' not in input_data.keys():
        return Validator.custom_response(400, 'Bad Request', "Request must contain 'email' and 'password' data")
    email = input_data['email'].strip()
    if len(email) == 0:
        return Validator.custom_response(400, 'Bad Request', "Provide an email address")
    password = input_data['password']
    if len(password.strip()) == 0:
        return Validator.custom_response(400, 'Bad Request', "Provide a password")

    # get all users and check if email and password match
    all_users = User.read_all(db.cur)
    if all_users is not None:
        for user in all_users:
            if email.strip().lower() == user.email.strip().lower() and sha256.verify(password, user.password):
                user_dict = user.obj_to_dict()
                # create token
                access_token = create_access_token(
                    identity=user.id, expires_delta=False)
                user_dict['access_token'] = access_token
                user_dict['msg'] = 'Login Successful!'
                return jsonify(user_dict), 200  # return user with access token
        # if the iterations do not find any matching user, return message:
        return Validator.custom_response(403, 'Forbidden', 'Invalid Login Credentials')
    else:
        return Validator.custom_response(200, "OK", "Request Successful BUT There are no users in store")
def api_add_answer(question_id):
    """Add an answer to a specific question"""

    # get id of user currently logged in (from authentication token)
    current_user_id = get_jwt_identity()
    input_data = request.get_json(force=True)
    if 'answer' not in input_data.keys():
        return Validator.custom_response(400, 'Bad Request', "Request must contain 'answer' data")
    answer = input_data['answer'].strip()
    if len(answer) == 0:
        return Validator.custom_response(400, 'Bad Request', "Provide a value for answer")
    all_answers = Answer.read_all(db.cur, question_id)

    # make sure the question for which the answer is to be posted is present
    question = Question.read_one(db.cur, question_id)
    if question is not None:
        for ans in all_answers:
            if ans.answer.strip().lower() == answer.strip().lower():  # check if value already exists
                return Validator.custom_response(409, 'Conflict', "Duplicate Value. Answer already exists")
        accepted = 'false'
        date_posted = datetime.datetime.now()
        new_id = Answer(0, question_id, current_user_id, answer,
                        0, accepted, date_posted).create(db.cur)
        return jsonify(Answer(new_id, question_id, current_user_id, answer, 0, accepted, date_posted).obj_to_dict()), 201
    else:
        return Validator.custom_response(404, 'Not Found', 'Question with id:' + str(question_id) + ' does not exist')
def api_update_answer(question_id, answer_id):
    """Update an answer to a specific question"""

    # get new answer values
    input_data = request.get_json(force=True)

    all_answers = Answer.read_all(db.cur, question_id)
    if all_answers is not None:
        for ans in all_answers:
            if ans.id == answer_id:  # check if answer exists
                # replace old values with new ones if available
                answer_edited = False
                if 'accepted' in input_data.keys():
                    new_accepted_value = input_data['accepted']  # boolean
                    # validate accepted value (must be a valid boolean value for postgres)
                    if isinstance(new_accepted_value, bool):
                        # make sure it's the user who posted the question accepting the answer
                        qn = Question.read_one(db.cur, question_id)
                        if qn.user_id != get_jwt_identity():
                            return Validator.custom_response(403, 'Forbidden',
                                                             "You do not have permission to accept this answer")
                        ans.accepted = new_accepted_value
                        answer_edited = True
                    else:
                        return Validator.custom_response(400, 'Bad Request',
                                                         "Provide 'accepted' data as a boolean (true OR false)")
                if 'answer' in input_data.keys():
                    new_answer_value = input_data['answer'].strip()
                    if len(new_answer_value) == 0:
                        return Validator.custom_response(400, 'Bad Request', "Provide a value for 'answer'")
                    if ans.user_id != get_jwt_identity():  # only the answer owner can edit it
                        return Validator.custom_response(403, 'Forbidden',
                                                         "You do not have permission to edit this answer")
                    ans.answer = new_answer_value
                    answer_edited = True

                if 'vote' in input_data.keys():
                    new_vote_value = input_data['vote']  # boolean
                    if isinstance(new_vote_value, bool):
                        if new_vote_value:
                            ans.votes += 1  # add a vote
                        else:
                            ans.votes -= 1  # remove a vote
                        answer_edited = True
                    else:
                        return Validator.custom_response(400, 'Bad Request',
                                                         "Provide 'vote' data as a boolean (true OR false)")

                if answer_edited:
                    ans.update(db.cur)
                    return jsonify(ans.obj_to_dict()), 202  # HTTP_202_ACCEPTED
                else:
                    return Validator.custom_response(400,
                                                     'Bad Request',
                                                     """Provide 'answer' data to edit answer, OR 'accepted' data (as a boolean) to edit accepted status OR 'vote' data (as a boolean) to up-vote or down-vote""")

        # Answer does not exist
        return Validator.custom_response(404, 'Not Found', 'No Answer found with id, ' + str(answer_id))
    else:  # Question does not exist
        return Validator.custom_response(404, 'Not Found', 'No answers for the question')
def api_delete_question(question_id):
    """Delete a specific question based on id"""

    # get object from database
    all_questions = Question.read_all(db.cur)
    if all_questions is not None:
        for qn in all_questions:
            if qn.id == question_id:
                # only the owner of the question can delete it
                if qn.user_id != get_jwt_identity():
                    return Validator.custom_response(403, 'Forbidden',
                                                     "You do not have permission to delete this question")
                qn.delete(db.cur)
                return Validator.custom_response(202, 'Accepted',
                                                 'Question with id, ' + str(question_id) + ' was deleted')
        return Validator.custom_response(404, 'Not Found', 'No question in store matching the id')
    else:
        return Validator.custom_response(200, 'OK', 'Request Successful BUT There are no Questions in store')
def api_add_question():
    """Post / Add a new question"""

    # get id of user currently logged in (from authentication token) and save it alongside the question
    current_user_id = get_jwt_identity()
    input_data = request.get_json(force=True)
    if 'question' not in input_data.keys():
        return Validator.custom_response(400, 'Bad Request', "Request must contain 'question' data")
    question = input_data['question'].strip()
    if len(question) == 0:
        return Validator.custom_response(400, 'Bad Request', "Provide a value for question")
    all_questions = Question.read_all(db.cur)
    if all_questions is not None:
        for qn in all_questions:
            if qn.question.strip().lower() == question.strip().lower():  # check if question already exists
                return Validator.custom_response(409, 'Conflict', "Duplicate Value. Question already exists")
    date_posted = datetime.datetime.now()
    new_id = Question(0, current_user_id, question, date_posted).create(db.cur)
    # 201 = created
    return jsonify(Question(new_id, current_user_id, question, date_posted).obj_to_dict()), 201
def api_get_one_question(question_id):
    """Get a specific question using its id. Also fetch all answers for the question"""

    questions_with_answers = dict()
    question_obj = Question.read_one(db.cur, question_id)
    if question_obj is None:
        return Validator.custom_response(404, 'Not Found', 'Question with id:' + str(question_id) + ' does not exist')

    # get all answers for the question
    questions_with_answers['question'] = question_obj.obj_to_dict()
    answers_list = Answer.read_all(db.cur, question_id)
    if answers_list is not None:
        list_of_answer_dicts = [answer_obj.obj_to_dict()
                                for answer_obj in answers_list]
        if len(list_of_answer_dicts) > 0:
            questions_with_answers['answers'] = list_of_answer_dicts

    return jsonify(questions_with_answers), 200
def bad_request(e):  # Bad request e.g. missing JSON post request data
    return Validator.custom_response(400, 'Bad request', 'Provide POST request data as valid JSON')
def page_not_found(e):  # Page not found
    return Validator.custom_response(404, 'Resource Not Found',
                                     'You are trying to access a resource that does not exist')
def register_user():
    """Post / Add a new user"""

    # validate presence of appropriate data
    input_data = request.get_json(force=True)
    if 'full_name' not in input_data.keys():
        return Validator.custom_response(400, 'Bad Request', "Request must contain 'full_name' data")
    if 'email' not in input_data.keys():
        return Validator.custom_response(400, 'Bad Request', "Request must contain 'email' data")
    if 'password' not in input_data.keys():
        return Validator.custom_response(400, 'Bad Request', "Request must contain 'password' data")
    if 'retype_password' not in input_data.keys():
        return Validator.custom_response(400, 'Bad Request', "Request must contain 'retype_password' data")

    password = input_data['password']
    if len(str(password)) < 6:
        return Validator.custom_response(400, 'Bad Request', "Password must be at least 6 characters long")
    full_name = input_data['full_name']
    if len(str(full_name).strip()) == 0:
        return Validator.custom_response(400, 'Bad Request', "Provide a name")
    email = input_data['email']
    if len(str(email).strip()) == 0:
        return Validator.custom_response(400, 'Bad Request', "Provide an email address")
    # email validator
    if not re.match(r"^[A-Za-z0-9.+_-]+@[A-Za-z0-9._-]+\.[a-zA-Z]+$", email):
        return Validator.custom_response(400, 'Bad Request', "Invalid email format")
    retype_password = input_data['retype_password']
    if len(str(retype_password)) == 0:
        return Validator.custom_response(400, 'Bad Request', "Retype password")

    # check if user already exists (compare emails)
    all_users = User.read_all(db.cur)
    if all_users is not None:
        for user in all_users:
            if user.email.strip().lower() == email.strip().lower():
                return Validator.custom_response(409, 'Conflict', "Duplicate Value. Email Address is already taken")

    # check if passwords match
    if password != retype_password:
        return Validator.custom_response(400, 'Bad Request', "Password mismatch")

    # if all is well; Add to database and return new id
    new_id = User(0, full_name, email, password).create(db.cur)
    user_dict = User(new_id, full_name, email,
                     sha256.hash(password)).obj_to_dict()

    # create token
    access_token = create_access_token(identity=new_id, expires_delta=False)
    user_dict['access_token'] = access_token
    user_dict['msg'] = 'User Registration Successful!'
    return jsonify(user_dict), 201