from flask_restx import marshal
import json
import io

try:
    from collections.abc import Callable
except ImportError:
    from collections import Callable

from flask import jsonify, make_response, send_file

from app import app
from enum import Enum

from flask_restx import fields, marshal
from Blueprints import api

# TODO add documentation link when there is one
"""
    Answers are used to encapsulate the response building.
    The status represents the normal HTTP status returned.
    Message is a code for a specific message that can be displayed in the app.
"""


class MessageCodes(Enum):
    # General Codes
    WORKED = 0
    SOMETHING_WENT_WRONG = 1
    BAD_REQUEST = 3

    # Project Codes
    PROJECT_CREATED = 1001
    PROJECT_DELETED = 1002
    PROJECT_JOINED_SUCCESS = 1003
    PROJECT_ALREADY_JOINED = 1004
    PROJECT_LEFT_SUCCESS = 1005
    PROJECT_ALREADY_LEFT = 1006
    PROJECT_BANNED_SUCCESSFULLY = 1007
    PROJECT_YOUR_BANNED = 1008
    PROJECT_ONLY_FOR_LEADERS = 1009
    PROJECT_IS_PRIVATE = 1010
    PROJECT_SENT_JOIN_REQUEST = 1011
    PROJECT_ALREADY_SENT_JOIN_REQUEST = 1012
    PROJECT_JR_WAS_DENIED = 1013
    PROJECT_JR_WAS_ACCEPTED = 1014
    PROJECT_DIDNT_FIND_ANY = 1015
    PROJECT_ONLY_FOR_ATTENDEES = 1016
    PROJECT_CANT_BAN_YOURSELF = 1017
    PROJECT_NO_SUCH_PROJECT = 1018
    PROJECT_NO_SUCH_JOIN_REQUEST = 1019
    PROJECT_NO_SUCH_INVITE = 1020
    PROJECT_USER_ALREADY_INVITED = 1021
    PROJECT_CANT_UNBAN_NOT_BANNED = 1022
    PROJECT_NO_SUCH_MEDIA = 1023
    PROJECT_MEDIA_ALREADY_LIKED = 1024
    PROJECT_MEDIA_NOT_LIKED_YET = 1025
    PROJECT_DONT_LIKE_YOUR_OWN_MEDIA = 1026
    PROJECT_MEDIA_LINK_IS_BANNED = 1027

    # Matching Codes
    MATCHING_NO_VALID_MATCHER = 2001
    MATCHING_NO_MATCHES = 2002
    MATCHING_ALREADY_MATCHED = 2003
    MATCHING_ALREADY_LIKED = 2004
    MATCHING_ALREADY_DISLIKED = 2005
    MATCHING_DISLIKE_REVERTED = 2006
    MATCHING_LIKE_REVERTED = 2007
    MATCHING_GROUP_ALREADY_REGISTERED = 2008
    MATCHING_GROUP_ALREADY_LEFT = 2009
    MATCHING_GROUP_NOT_REGISTERED = 2010

    # Channel Codes
    CHANNEL_NOT_AN_ATTENDEE = 3001
    CHANNEL_ONLY_FOR_PROJECT_LEADERS = 3002
    CHANNEL_POST_DELETED = 3003
    CHANNEL_USER_NOT_IN_PROJECT = 3004
    CHANNEL_NO_SUCH_POST = 3005
    CHANNEL_NO_SUCH_CHANNEL = 3006
    CHANNEL_ONLY_DELETE_OWN_POSTS = 3007
    CHANNEL_NOT_AN_EMOJI = 3008
    CHANNEL_ALREADY_REACTED = 3009
    CHANNEL_NOT_YET_REACTED = 3010
    CHANNEL_POST_ALREADY_BLOCKED = 3011
    CHANNEL_POST_NOT_BLOCKED = 3012

    # User Codes
    USER_NOT_A_MODERATOR = 4001
    USER_MUTED_SUCCESS = 4002
    USER_IS_NOT_MUTED = 4003
    USER_UNMUTED_SUCCESS = 4004
    USER_YOUR_MUTED = 4005
    USER_NO_RANDOM_CHATS = 4006
    USER_CHAT_ALREADY_EXISTS = 4007
    USER_CHAT_DOES_NOT_EXISTS = 4008
    USER_NO_BAN_RIGHTS = 4009
    USER_INVALID_USERNAME = 4010
    USER_INVALID_USER_TAG = 4011
    USER_INVALID_PROFILE_PIC = 4012
    USER_NO_SUCH_USER = 4013
    USER_WRONG_PASSWORD = 4014
    USER_INVALID_QRCODE = 4015
    USER_MATCH_ALREADY_EXISTS = 4016
    USER_CANT_MATCH_YOURSELF = 4017
    USER_NO_CARD = 4018
    USER_ALREADY_BLOCKED = 4019
    USER_NOT_BLOCKED = 4020
    USER_BLOCKED_YOU = 4021

    # Image Codes
    IMAGE_DOES_NOT_EXIST = 5001
    IMAGE_UPLOAD_SUCCESS = 5002
    IMAGE_DELETE_SUCCESS = 5003
    IMAGE_NOT_THE_UPLOADER = 5004

    # Device Codes
    DEVICE_ACTIVE = 6001
    DEVICE_DOES_NOT_EXIST = 6002
    DEVICE_USER_HAS_NO_DEVICES = 6003

    # Card Codes
    CARD_NO_IMAGE = 7001
    CARD_NO_QUESTION = 7002
    CARD_NO_ANSWER = 7003
    CARD_NO_TEXT = 7004
    CARD_NO_NUGGETS = 7005
    CARD_INVALID_CARD_TYPE = 7006


# mappings message code to http code
# 200
success = [MessageCodes.WORKED,
           MessageCodes.PROJECT_DIDNT_FIND_ANY, MessageCodes.DEVICE_USER_HAS_NO_DEVICES,
           MessageCodes.PROJECT_JOINED_SUCCESS, MessageCodes.PROJECT_LEFT_SUCCESS,
           MessageCodes.PROJECT_JR_WAS_DENIED, MessageCodes.PROJECT_JR_WAS_ACCEPTED,
           MessageCodes.MATCHING_DISLIKE_REVERTED, MessageCodes.MATCHING_LIKE_REVERTED,
           MessageCodes.IMAGE_DELETE_SUCCESS, MessageCodes.PROJECT_DELETED,
           MessageCodes.PROJECT_BANNED_SUCCESSFULLY, MessageCodes.CHANNEL_POST_DELETED,
           MessageCodes.USER_MUTED_SUCCESS, MessageCodes.USER_UNMUTED_SUCCESS,
           MessageCodes.MATCHING_GROUP_NOT_REGISTERED, MessageCodes.DEVICE_ACTIVE]
# 201
created = [MessageCodes.PROJECT_SENT_JOIN_REQUEST, MessageCodes.IMAGE_UPLOAD_SUCCESS, MessageCodes.PROJECT_CREATED]
# 400
bad_request = [MessageCodes.SOMETHING_WENT_WRONG, MessageCodes.MATCHING_NO_VALID_MATCHER,
               MessageCodes.CHANNEL_NOT_AN_EMOJI, MessageCodes.BAD_REQUEST, MessageCodes.CARD_NO_IMAGE,
               MessageCodes.CARD_NO_QUESTION, MessageCodes.CARD_NO_ANSWER, MessageCodes.CARD_NO_TEXT,
               MessageCodes.CARD_INVALID_CARD_TYPE, MessageCodes.CARD_NO_NUGGETS]
# 403
access_denied = [MessageCodes.PROJECT_IS_PRIVATE, MessageCodes.CHANNEL_NOT_AN_ATTENDEE,
                 MessageCodes.PROJECT_YOUR_BANNED, MessageCodes.CHANNEL_ONLY_FOR_PROJECT_LEADERS,
                 MessageCodes.USER_NOT_A_MODERATOR, MessageCodes.USER_YOUR_MUTED, MessageCodes.IMAGE_NOT_THE_UPLOADER,
                 MessageCodes.USER_NO_RANDOM_CHATS, MessageCodes.PROJECT_ONLY_FOR_ATTENDEES,
                 MessageCodes.CHANNEL_USER_NOT_IN_PROJECT, MessageCodes.PROJECT_CANT_BAN_YOURSELF,
                 MessageCodes.CHANNEL_ONLY_DELETE_OWN_POSTS, MessageCodes.USER_WRONG_PASSWORD,
                 MessageCodes.USER_INVALID_QRCODE, MessageCodes.PROJECT_DONT_LIKE_YOUR_OWN_MEDIA,
                 MessageCodes.PROJECT_MEDIA_LINK_IS_BANNED, MessageCodes.USER_BLOCKED_YOU]
# 404
does_not_exist = [MessageCodes.IMAGE_DOES_NOT_EXIST, MessageCodes.DEVICE_DOES_NOT_EXIST,
                  MessageCodes.USER_CHAT_DOES_NOT_EXISTS, MessageCodes.PROJECT_NO_SUCH_PROJECT,
                  MessageCodes.PROJECT_NO_SUCH_JOIN_REQUEST, MessageCodes.USER_NO_SUCH_USER,
                  MessageCodes.PROJECT_NO_SUCH_INVITE, MessageCodes.CHANNEL_NO_SUCH_POST,
                  MessageCodes.CHANNEL_NO_SUCH_CHANNEL, MessageCodes.PROJECT_NO_SUCH_MEDIA,
                  MessageCodes.USER_NO_CARD]
# 409
conflict = [MessageCodes.MATCHING_ALREADY_LIKED, MessageCodes.MATCHING_ALREADY_DISLIKED,
            MessageCodes.PROJECT_ALREADY_JOINED, MessageCodes.PROJECT_ALREADY_LEFT,
            MessageCodes.MATCHING_ALREADY_MATCHED, MessageCodes.USER_CHAT_ALREADY_EXISTS,
            MessageCodes.PROJECT_ALREADY_SENT_JOIN_REQUEST, MessageCodes.USER_IS_NOT_MUTED,
            MessageCodes.MATCHING_GROUP_ALREADY_LEFT, MessageCodes.MATCHING_GROUP_ALREADY_REGISTERED,
            MessageCodes.USER_INVALID_USERNAME, MessageCodes.USER_INVALID_USER_TAG,
            MessageCodes.USER_INVALID_PROFILE_PIC, MessageCodes.PROJECT_USER_ALREADY_INVITED,
            MessageCodes.USER_MATCH_ALREADY_EXISTS, MessageCodes.USER_CANT_MATCH_YOURSELF,
            MessageCodes.CHANNEL_ALREADY_REACTED, MessageCodes.CHANNEL_NOT_YET_REACTED,
            MessageCodes.PROJECT_MEDIA_ALREADY_LIKED, MessageCodes.PROJECT_MEDIA_NOT_LIKED_YET,
            MessageCodes.USER_ALREADY_BLOCKED, MessageCodes.USER_NOT_BLOCKED, MessageCodes.CHANNEL_POST_ALREADY_BLOCKED,
            MessageCodes.CHANNEL_POST_NOT_BLOCKED]


def get_http_code(message_code):
    if message_code in success:
        return 200
    elif message_code in created:
        return 201
    elif message_code in bad_request:
        return 400
    elif message_code in access_denied:
        return 403
    elif message_code in does_not_exist:
        return 404
    elif message_code in conflict:
        return 409
    return 404


def make_image_response(image_name, data):
    response = make_response(data)
    response.headers.set('Content-Type', 'image/png')
    response.headers.set(
        'Content-Disposition', 'attachment', filename='{}.png'.format(image_name))
    response.status = 200
    return send_file(io.BytesIO(data),
                     attachment_filename='{}.png'.format(image_name),
                     mimetype='image/png')


class Wrapper:

    def __init__(self, user, data, lang):
        self.user = user
        self.data = data
        self.lang = lang


class Answer:
    def __init__(self):
        self.status = get_http_code(MessageCodes.SOMETHING_WENT_WRONG)
        self.data = {}
        # are saved for test purposes
        self.result = None
        self.message = None
        self.image = None

    def add_result(self, result):
        self.result = result
        self.data["result"] = result
        return self

    def add_message(self, message_code):
        self.message = message_code.value
        self.status = get_http_code(message_code)
        self.data["message"] = message_code.value
        return self

    def add_image(self, image):
        data = image.data
        image_name = str(image.id)
        self.image = (image_name, data)
        return self

    def make_response(self):
        if self.image:
            return make_image_response(self.image[0], self.image[1])
        response = app.response_class(
            response=json.dumps(self.data),
            status=self.status,
            mimetype='application/json'
        )
        return response

    def make_marshaled_response(self, user, lang, model, mask=None, skip_none=True):
        if self.image:
            return make_image_response(self.image[0], self.image[1])
        if 200 <= self.status < 300:
            return marshal(Wrapper(user, self.get_json(), lang), model, skip_none=skip_none, mask=mask), self.status
        else:
            return marshal(Wrapper(user, self.get_json(), lang), generic_message_model, skip_none=True), self.status

    def get_json(self):
        return {"result": self.result, "message": self.message}


def try_catch_error_answer(original_class):
    def try_catch_decorator(function):
        def try_catch_wrapper(*args, **kws):
            try:
                return function(*args, **kws)
            except Exception as e:
                print(str(e))

                return Answer() \
                    .add_message(MessageCodes.SOMETHING_WENT_WRONG)

        return try_catch_wrapper

    def decorate(cls):
        for attr in cls.__dict__:
            if isinstance(getattr(cls, attr), Callable):
                original_function = getattr(cls, attr)
                setattr(cls, attr, try_catch_decorator(original_function))
        return cls

    return decorate(original_class)


def message_response_model(model, single=False):
    if single:
        return api.model("MessageResponseSingle" + model.name,
                         {'message': fields.Integer(example=0, attribute=lambda x: x.data["message"]),
                          "result": fields.Nested(model, attribute=lambda x: Wrapper(x.user, x.data["result"], x.lang),
                                                  skip_none=True)})
    return api.model("MessageResponse" + model.name,
                     {'message': fields.Integer(example=0, attribute=lambda x: x.data["message"]),
                      "result": fields.List(fields.Nested(model, skip_none=True),
                                            attribute=lambda x: [Wrapper(x.user, y, x.lang)
                                                                 for y in x.data["result"]])})


generic_message_model = api.model("Message",
                                  {'message': fields.Integer(
                                      example=0,
                                      attribute=lambda x: x.data["message"],
                                      description="A code indicating the success of the action, or referencing"
                                                  " a message to be displayed in the app")})
