from typing import Optional

from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext as _

from zerver.decorator import REQ, has_request_variables
from zerver.lib.actions import do_add_reaction, do_remove_reaction
from zerver.lib.emoji import check_emoji_request, emoji_name_to_emoji_code
from zerver.lib.message import access_message
from zerver.lib.request import JsonableError
from zerver.lib.response import json_success
from zerver.models import Message, Reaction, UserMessage, UserProfile


def create_historical_message(user_profile: UserProfile, message: Message) -> None:
    # Users can see and react to messages sent to streams they
    # were not a subscriber to; in order to receive events for
    # those, we give the user a `historical` UserMessage objects
    # for the message.  This is the same trick we use for starring
    # messages.
    UserMessage.objects.create(user_profile=user_profile,
                               message=message,
                               flags=UserMessage.flags.historical | UserMessage.flags.read)

@has_request_variables
def add_reaction(request: HttpRequest, user_profile: UserProfile, message_id: int,
                 emoji_name: str=REQ(),
                 emoji_code: Optional[str]=REQ(default=None),
                 reaction_type: Optional[str]=REQ(default=None)) -> HttpResponse:
    message, user_message = access_message(user_profile, message_id)

    if emoji_code is None:
        # The emoji_code argument is only required for rare corner
        # cases discussed in the long block comment below.  For simple
        # API clients, we allow specifying just the name, and just
        # look up the code using the current name->code mapping.
        emoji_code = emoji_name_to_emoji_code(message.sender.realm,
                                              emoji_name)[0]

    if reaction_type is None:
        reaction_type = emoji_name_to_emoji_code(message.sender.realm,
                                                 emoji_name)[1]

    if Reaction.objects.filter(user_profile=user_profile,
                               message=message,
                               emoji_code=emoji_code,
                               reaction_type=reaction_type).exists():
        raise JsonableError(_("Reaction already exists."))

    query = Reaction.objects.filter(message=message,
                                    emoji_code=emoji_code,
                                    reaction_type=reaction_type)
    if query.exists():
        # If another user has already reacted to this message with
        # same emoji code, we treat the new reaction as a vote for the
        # existing reaction.  So the emoji name used by that earlier
        # reaction takes precedence over whatever was passed in this
        # request.  This is necessary to avoid a message having 2
        # "different" emoji reactions with the same emoji code (and
        # thus same image) on the same message, which looks ugly.
        #
        # In this "voting for an existing reaction" case, we shouldn't
        # check whether the emoji code and emoji name match, since
        # it's possible that the (emoji_type, emoji_name, emoji_code)
        # triple for this existing rection xmay not pass validation
        # now (e.g. because it is for a realm emoji that has been
        # since deactivated).  We still want to allow users to add a
        # vote any old reaction they see in the UI even if that is a
        # deactivated custom emoji, so we just use the emoji name from
        # the existing reaction with no further validation.
        emoji_name = query.first().emoji_name
    else:
        # Otherwise, use the name provided in this request, but verify
        # it is valid in the user's realm (e.g. not a deactivated
        # realm emoji).
        check_emoji_request(message.sender.realm, emoji_name,
                            emoji_code, reaction_type)

    if user_message is None:
        create_historical_message(user_profile, message)

    do_add_reaction(user_profile, message, emoji_name, emoji_code, reaction_type)

    return json_success()

@has_request_variables
def remove_reaction(request: HttpRequest, user_profile: UserProfile, message_id: int,
                    emoji_name: Optional[str]=REQ(default=None),
                    emoji_code: Optional[str]=REQ(default=None),
                    reaction_type: str=REQ(default="unicode_emoji")) -> HttpResponse:
    message, user_message = access_message(user_profile, message_id)

    if emoji_code is None:
        if emoji_name is None:
            raise JsonableError(_('At least one of the following arguments '
                                  'must be present: emoji_name, emoji_code'))
        # A correct full Zulip client implementation should always
        # pass an emoji_code, because of the corner cases discussed in
        # the long block comments elsewhere in this file.  However, to
        # make it easy for simple API clients to use the reactions API
        # without needing the mapping between emoji names and codes,
        # we allow instead passing the emoji_name and looking up the
        # corresponding code using the current data.
        emoji_code = emoji_name_to_emoji_code(message.sender.realm, emoji_name)[0]

    if not Reaction.objects.filter(user_profile=user_profile,
                                   message=message,
                                   emoji_code=emoji_code,
                                   reaction_type=reaction_type).exists():
        raise JsonableError(_("Reaction doesn't exist."))

    # Unlike adding reactions, while deleting a reaction, we don't
    # check whether the provided (emoji_type, emoji_code) pair is
    # valid in this realm.  Since there's a row in the database, we
    # know it was valid when the user added their reaction in the
    # first place, so it is safe to just remove the reaction if it
    # exists.  And the (reaction_type, emoji_code) pair may no longer be
    # valid in legitimate situations (e.g. if a realm emoji was
    # deactivated by an administrator in the meantime).
    do_remove_reaction(user_profile, message, emoji_code, reaction_type)

    return json_success()
