# By: Riasat Ullah
# This files contains the classes that represent the different
# events that can happen for an instance of a task.

from utils import constants, helpers, times, var_names
from validations import string_validator
import configuration
import datetime
import json
import operator


class Event(object):
    '''
    Represents an event. This acts as the super class
    for the different types of events.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by=None):
        assert isinstance(instance_id, int)
        assert isinstance(timestamp, datetime.datetime)
        assert event_method in [constants.app, constants.call, constants.check, constants.email,
                                constants.incidents_api, constants.integrations_api, constants.internal,
                                constants.live_call_routing, constants.rundeck, constants.text, constants.web]

        # Event by should be the (int) user_id of the user taking the action. However, when retrieving events
        # from the database for display purposes, the full name of the user is mapped out as event_by. Hence,
        # we have to make sure that the event_by attribute works for both (int) and (str).
        if event_by is not None:
            assert isinstance(event_by, int) or isinstance(event_by, str)

        self.instance_id = instance_id
        self.event_timestamp = timestamp
        self.event_method = event_method
        self.event_by = event_by
        self.event_date = self.event_timestamp.date()

    @staticmethod
    def create_events(inst_id, events_data):
        '''
        Creates a list of Event objects from a list of dictionary of events data.
        :param inst_id: ID of the instance the events are for
        :param events_data: (list) of dictionary of events data
        :return: (list) of Event objects
        '''
        all_events = []

        if events_data is not None:
            for item in events_data:
                event_timestamp = times.get_timestamp_from_string(item[var_names.event_timestamp])
                event_type = item[var_names.event_type]
                event_method = item[var_names.event_method]
                event_by = item[var_names.event_by]
                log = item[var_names.event_log]

                # This is needed because when we read events from the cache, the event log stays as json str.
                # It needs to be converted to a dict.
                if isinstance(log, str):
                    log = json.loads(log)

                if event_type == constants.acknowledge_event:
                    event = AcknowledgeEvent(inst_id, event_timestamp, event_method, event_by)

                elif event_type == constants.add_conference_bridge_event:
                    event = AddConferenceBridgeEvent(inst_id, event_timestamp, event_method,
                                                     log[var_names.conference_url], log[var_names.conference_phone],
                                                     log[var_names.additional_info], event_by)

                elif event_type == constants.add_impacted_business_service_event:
                    event = AddImpactedBusinessServiceEvent(inst_id, event_timestamp, event_method,
                                                            log[var_names.business_service_id], event_by)

                elif event_type == constants.add_responders_event:
                    event = AddRespondersEvent(inst_id, event_timestamp, event_method,
                                               log[var_names.new_responders], event_by)

                elif event_type == constants.add_subscribers_event:
                    event = AddSubscribersEvent(inst_id, event_timestamp, event_method,
                                                log[var_names.subscribers], event_by)

                elif event_type == constants.call_answered_event:
                    event = CallAnsweredEvent(inst_id, event_timestamp, event_method, event_by)

                elif event_type == constants.call_ended_event:
                    event = CallEndedEvent(inst_id, event_timestamp, event_method, log[var_names.call_status],
                                           log[var_names.call_duration], log[var_names.voicemail_url])

                elif event_type == constants.call_forwarding_event:
                    event = CallForwardingEvent(inst_id, event_timestamp, event_method, log[var_names.forward_to],
                                                log[var_names.forwarding_count], event_by=event_by)

                elif event_type == constants.call_outgoing_event:
                    event = CallOutgoingEvent(inst_id, event_timestamp, event_method)

                elif event_type == constants.call_voicemail_prompt_event:
                    event = CallVoicemailPrompt(inst_id, event_timestamp, event_method)

                elif event_type == constants.custom_action_event:
                    event = CustomActionEvent(inst_id, event_timestamp, event_method, log[var_names.integration_id],
                                              log[var_names.integration_type_id], log[var_names.vendor_id],
                                              log[var_names.vendor_url], log[var_names.additional_info], event_by,
                                              is_synced=log[var_names.is_synced],
                                              configuration_name=log[var_names.configuration_name])

                elif event_type == constants.dispatch_event:
                    event = DispatchEvent(inst_id, event_timestamp, event_method, log[var_names.dispatched_to])

                elif event_type == constants.edit_title_event:
                    event = EditTitleEvent(inst_id, event_timestamp, event_method, event_by, log[var_names.task_title])

                elif event_type == constants.escalate_event:
                    event = EscalateEvent(
                        inst_id, event_timestamp, event_method, event_by, log[var_names.escalate_to_level],
                        times.get_timestamp_from_string(log[var_names.next_alert_timestamp])
                        if var_names.next_alert_timestamp in log else times.get_timestamp_from_string(event_timestamp)
                    )

                elif event_type == constants.merge_event:
                    event = MergeEvent(inst_id, event_timestamp, event_method,
                                       log[var_names.related_instance_id], event_by)

                elif event_type == constants.notate_event:
                    event = NotateEvent(inst_id, event_timestamp, event_method, event_by, log[var_names.notes])

                elif event_type == constants.reassign_event:
                    event = ReassignEvent(inst_id, event_timestamp, event_method, event_by,
                                          log[var_names.reassign_to])

                elif event_type == constants.resolve_event:
                    event = ResolveEvent(inst_id, event_timestamp, event_method, event_by)

                elif event_type == constants.run_workflow_event:
                    event = RunWorkflowEvent(inst_id, event_timestamp, event_method,
                                             log[var_names.workflow_id], log[var_names.workflow_name],
                                             event_by)

                elif event_type == constants.send_chat_message_event:
                    event = SendChatMessageEvent(inst_id, event_timestamp, event_method, log[var_names.integration_id],
                                                 log[var_names.integration_type_id], event_by)

                elif event_type == constants.send_external_email_event:
                    event = SendExternalEmailEvent(inst_id, event_timestamp, event_method,
                                                   log[var_names.email_to], event_by)

                elif event_type == constants.send_external_sms_event:
                    event = SendExternalSmsEvent(inst_id, event_timestamp, event_method,
                                                 log[var_names.sms_to], event_by)

                elif event_type == constants.snooze_event:
                    event = SnoozeEvent(inst_id, event_timestamp, event_method, event_by,
                                        log[var_names.snooze_for])

                elif event_type == constants.status_update_event:
                    event = StatusUpdateEvent(inst_id, event_timestamp, event_method,
                                              log[var_names.status_update], event_by)

                elif event_type == constants.trigger_event:
                    event = TriggerEvent(inst_id, event_timestamp, event_method,
                                         times.get_timestamp_from_string(log[var_names.next_alert_timestamp]))

                elif event_type == constants.un_acknowledge_event:
                    event = UnAcknowledgeEvent(
                        inst_id, event_timestamp, event_method, event_by,
                        times.get_timestamp_from_string(log[var_names.next_alert_timestamp])
                        if var_names.next_alert_timestamp in log else times.get_timestamp_from_string(event_timestamp)
                    )

                elif event_type == constants.un_merge_event:
                    event = UnMergeEvent(inst_id, event_timestamp, event_method, log[var_names.task_id], event_by)

                elif event_type == constants.update_tags_event:
                    event = UpdateTagsEvent(inst_id, event_timestamp, event_method, log[var_names.tags], event_by)

                elif event_type == constants.urgency_amendment_event:
                    event = UrgencyAmendmentEvent(inst_id, event_timestamp, event_method,
                                                  log[var_names.urgency_level], event_by)

                else:
                    raise Exception('Unknown event type found - ' + event_type)

                all_events.append(event)

        if len(all_events) > 0:
            all_events.sort(key=operator.attrgetter(var_names.event_timestamp))
        return all_events

    def to_dict(self):
        '''
        Gets the serialized json for Event object
        :return: json of Event object
        '''
        data = {
            var_names.instance_id: self.instance_id,
            var_names.event_timestamp: self.event_timestamp,
            var_names.event_date: self.event_date,
            var_names.event_type: self.__str__(),
            var_names.event_method: self.event_method,
            var_names.event_by: self.event_by,
            var_names.event_log: self.db_log()
        }
        return data


class AcknowledgeEvent(Event):
    '''
    This class represents an acknowledgement event.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)

    def __str__(self):
        return constants.acknowledge_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by}
        return json.dumps(struct)


class AddConferenceBridgeEvent(Event):
    '''
    This class represents an event where a conference bridge is being added to an instance.
    '''
    def __init__(self, instance_id, timestamp, event_method, url=None, phone=None, details=None, event_by=None):
        if url is not None:
            assert string_validator.is_web_url(url)
        if phone is not None:
            assert string_validator.is_dial_in_number(phone)

        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.url = url
        self.phone = phone
        self.details = details

    def __str__(self):
        return constants.add_conference_bridge_event

    def db_log(self):
        struct = {var_names.conference_url: self.url,
                  var_names.conference_phone: self.phone,
                  var_names.additional_info: self.details}
        return json.dumps(struct)


class AddImpactedBusinessServiceEvent(Event):
    '''
    This class represents an event where a new impacted business service
    is added to the list of business services that are being impacted by the instance.
    '''
    def __init__(self, instance_id, timestamp, event_method, business_service_id, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        assert isinstance(business_service_id, int)
        self.business_service_id = business_service_id

    def __str__(self):
        return constants.add_impacted_business_service_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by, var_names.business_service_id: self.business_service_id}
        return json.dumps(struct)


class AddRespondersEvent(Event):
    '''
    This class represents an event where new responders are added to an instance.
    # new_responders is the list of policy id(s) of the new responders.
    # A new responder can be a user policy id or a group policy id.
    '''
    def __init__(self, instance_id, timestamp, event_method, new_responders, event_by=None):
        assert isinstance(new_responders, list)
        for policy_id in new_responders:
            assert isinstance(policy_id, int)

        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.new_responders = new_responders

    def __str__(self):
        return constants.add_responders_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.new_responders: self.new_responders}
        return json.dumps(struct)


class AddSubscribersEvent(Event):
    '''
    This class represents an event where new subscribers are added to an instance.
    subscribers: (list of int) user_ids of users to be added as subscribers
    '''
    def __init__(self, instance_id, timestamp, event_method, subscribers, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.subscribers = subscribers

    def __str__(self):
        return constants.add_subscribers_event

    def db_log(self):
        struct = {var_names.subscribers: self.subscribers,
                  var_names.event_by: self.event_by}
        return json.dumps(struct)


class CallAnsweredEvent(Event):
    '''
    This class represents an event where a live call being routed is answered by a user.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)

    def __str__(self):
        return constants.call_answered_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by}
        return json.dumps(struct)


class CallEndedEvent(Event):
    '''
    This class represents an event where a live call being routed ends.
    '''
    def __init__(self, instance_id, timestamp, event_method, call_status=None, call_duration=None, voicemail_url=None):
        Event.__init__(self, instance_id, timestamp, event_method)
        if call_status is not None:
            assert call_status in configuration.allowed_live_call_statuses
        if call_duration is not None:
            assert isinstance(call_duration, int)
        if voicemail_url is not None:
            assert string_validator.is_web_url(voicemail_url)

        self.call_status = call_status
        self.call_duration = call_duration
        self.voicemail_url = voicemail_url

    def __str__(self):
        return constants.call_ended_event

    def db_log(self):
        struct = {var_names.call_status: self.call_status,
                  var_names.call_duration: self.call_duration,
                  var_names.voicemail_url: self.voicemail_url}
        return json.dumps(struct)


class CallForwardingEvent(Event):
    '''
    This class represents an event where a live call being routed is forwarded.
    '''
    def __init__(self, instance_id, timestamp, event_method, forward_to, forwarding_count, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        assert isinstance(forward_to, int)
        assert isinstance(forwarding_count, int)
        self.forward_to = forward_to
        self.forwarding_count = forwarding_count

    def __str__(self):
        return constants.call_forwarding_event

    def db_log(self):
        struct = {var_names.forward_to: self.forward_to,
                  var_names.forwarding_count: self.forwarding_count}
        return json.dumps(struct)


class CallOutgoingEvent(Event):
    '''
    This class represents an event where an outgoing live call is being made.
    '''
    def __init__(self, instance_id, timestamp, event_method):
        Event.__init__(self, instance_id, timestamp, event_method)

    def __str__(self):
        return constants.call_outgoing_event

    def db_log(self):
        struct = dict()
        return json.dumps(struct)


class CallVoicemailPrompt(Event):
    '''
    This class represents an event where a live call is directed to voice mail.
    '''
    def __init__(self, instance_id, timestamp, event_method):
        Event.__init__(self, instance_id, timestamp, event_method)

    def __str__(self):
        return constants.call_voicemail_prompt_event

    def db_log(self):
        struct = dict()
        return json.dumps(struct)


class CustomActionEvent(Event):
    '''
    This class represents an event where a custom action is executed on an instance. This has been introduced
    after deprecating SyncIncidentEvent. Unlike its predecessor, the entry is stored in the instance_events table
    and is also process by the create_event function above.
    '''
    def __init__(self, instance_id, timestamp, event_method, integration_id, integration_type_id, vendor_id,
                 vendor_url=None, additional_info=None, event_by=None, is_synced=True, configuration_name=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        assert isinstance(integration_id, int)
        assert isinstance(integration_type_id, int)
        self.integration_id = integration_id
        self.integration_type_id = integration_type_id
        self.vendor_id = vendor_id
        self.vendor_url = vendor_url
        self.additional_info = additional_info
        self.is_synced = is_synced
        self.configuration_name = configuration_name

    def __str__(self):
        return constants.custom_action_event

    def db_log(self):
        struct = {var_names.integration_id: self.integration_id,
                  var_names.integration_type_id: self.integration_type_id, var_names.vendor_id: self.vendor_id,
                  var_names.vendor_url: self.vendor_url, var_names.additional_info: self.additional_info,
                  var_names.event_by: self.event_by, var_names.is_synced: self.is_synced,
                  var_names.configuration_name: self.configuration_name}
        return json.dumps(struct)


class DispatchEvent(Event):
    '''
    This class represents a dispatch event. The dispatch_to parameter is a tuple (user_policyid, for_policyid).
    '''
    def __init__(self, instance_id, timestamp, event_method, dispatched_to, assignee=None):
        DispatchEvent.validate_assignee_struct_format(dispatched_to)
        Event.__init__(self, instance_id, timestamp, event_method)
        self.dispatched_to = dispatched_to
        self.assignee = assignee

    def __str__(self):
        return constants.dispatch_event

    def has_been_dispatched_to(self, user_policy_id=None, for_policy_id=None):
        user_matches, group_matches = False, False
        if user_policy_id is None or user_policy_id == self.dispatched_to[0]:
            user_matches = True
        if for_policy_id is None or for_policy_id == self.dispatched_to[1]:
            group_matches = True

        if user_matches and group_matches:
            return True
        else:
            return False

    def user_policy_id(self):
        return self.dispatched_to[0]

    def for_policy_id(self):
        return self.dispatched_to[1]

    def db_log(self):
        struct = {var_names.dispatched_to: self.dispatched_to}
        return json.dumps(struct)

    @staticmethod
    def validate_assignee_struct_format(assignee):
        '''
        Validates the format of the dispatched_to attributes.
        :param assignee: (tuple) (user_id, policy id)
        '''
        assert isinstance(assignee, tuple)
        assert isinstance(assignee[0], int) and isinstance(assignee[1], int)


class EscalateEvent(Event):
    '''
    This class represents an event where the task is escalated to the next level of assignees.

    EscalateEvent does not have a timestamp because it only creates assignments and updates the
    instance status to OPEN. It should set the next_alert_timestamp to the timestamp of the event.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by, escalate_to_level, next_alert_timestamp,
                 escalate_for=None):
        assert isinstance(escalate_to_level, int)
        if escalate_for is not None:
            assert isinstance(escalate_for, list)
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.escalate_to_level = escalate_to_level
        self.next_alert_timestamp = next_alert_timestamp

        # This should be used if the escalation should happen for a particular policy only.
        # This will come handy when the instance does not have an escalation set up, but has group
        # assignees that have escalation layers. So, by using this we can restrict the escalation for
        # only the group assignees.
        self.escalate_for = escalate_for

    def __str__(self):
        return constants.escalate_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.escalate_to_level: self.escalate_to_level,
                  var_names.next_alert_timestamp: self.next_alert_timestamp}
        return json.dumps(struct, default=helpers.jsonify_unserializable)


class EditTitleEvent(Event):
    '''
    This class represents an event where the title of a task is edited (not for pre-scheduled alerts).
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by, new_title):
        assert isinstance(new_title, str)
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.task_title = new_title

    def __str__(self):
        return constants.edit_title_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.task_title: self.task_title}
        return json.dumps(struct)


class MergeEvent(Event):
    '''
    This class represents an event where an instance is merged with another instance.
    '''
    def __init__(self, instance_id, timestamp, event_method, related_instance_id, event_by=None):
        assert isinstance(related_instance_id, int)
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.related_instance_id = related_instance_id

    def __str__(self):
        return constants.merge_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.related_instance_id: self.related_instance_id}
        return json.dumps(struct)


class NotateEvent(Event):
    '''
    This class represents an event where a note is added to an instance.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by, notes):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.notes = notes

    def __str__(self):
        return constants.notate_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.notes: self.notes}
        return json.dumps(struct)


class ReassignEvent(Event):
    '''
    This class represents an event where the assignee has re-assigned
    the task to someone else. A task can be re-assigned to multiple
    policies at the same time.
    # reassign_to is the list of policy id(s) of the re-assignees.
    # A re-assignee can be a user policy id or a group policy id.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by, reassign_to):
        assert isinstance(reassign_to, list) and len(reassign_to) > 0
        for policy_id in reassign_to:
            assert isinstance(policy_id, int)

        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.reassign_to = reassign_to

    def __str__(self):
        return constants.reassign_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.reassign_to: self.reassign_to}
        return json.dumps(struct)


class RemoveImpactedBusinessServiceEvent(Event):
    '''
    This class represents an event where an impacted business service
    is removed from the list of impacted business services.
    '''
    def __init__(self, instance_id, timestamp, event_method, business_service_id, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        assert isinstance(business_service_id, int)
        self.business_service_id = business_service_id

    def __str__(self):
        return constants.remove_impacted_business_service_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by, var_names.business_service_id: self.business_service_id}
        return json.dumps(struct)


class RemoveSubscribersEvent(Event):
    '''
    This class represents an event where subscribers are remove from an instance.
    subscribers: (list of int) user_ids of the users to remove from the subscribers list
    '''
    def __init__(self, instance_id, timestamp, event_method, subscribers, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.subscribers = subscribers

    def __str__(self):
        return constants.remove_subscribers_event

    def db_log(self):
        struct = {var_names.subscribers: self.subscribers, var_names.event_by: self.event_by}
        return json.dumps(struct)


####################
# For non-repeating tasks, end the task after the instance of the task resolves
####################
class ResolveEvent(Event):
    '''
    This class represents an event where the task is resolved by the assignee.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)

    def __str__(self):
        return constants.resolve_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by}
        return json.dumps(struct)


class RunWorkflowEvent(Event):
    '''
    This class represents an event where a workflow is run.
    '''
    def __init__(self, instance_id, timestamp, event_method, workflow_id, workflow_name, event_by=None):
        assert isinstance(workflow_id, int)
        assert isinstance(workflow_name, str)
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.workflow_id = workflow_id
        self.workflow_name = workflow_name

    def __str__(self):
        return constants.run_workflow_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.workflow_id: self.workflow_id,
                  var_names.workflow_name: self.workflow_name}
        return json.dumps(struct)


class SendChatMessageEvent(Event):
    def __init__(self, instance_id, timestamp, event_method, integration_id, integration_type_id, event_by=None):
        assert isinstance(integration_id, int)
        assert isinstance(integration_type_id, int)

        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.integration_id = integration_id
        self.integration_type_id = integration_type_id

    def __str__(self):
        return constants.send_chat_message_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.integration_id: self.integration_id,
                  var_names.integration_type_id: self.integration_type_id}
        return json.dumps(struct)


class SendExternalEmailEvent(Event):
    def __init__(self, instance_id, timestamp, event_method, email_to, event_by=None):
        assert isinstance(email_to, list)
        for item in email_to:
            assert string_validator.is_email_address(item)

        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.email_to = email_to

    def __str__(self):
        return constants.send_external_email_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.email_to: self.email_to}
        return json.dumps(struct)


class SendExternalSmsEvent(Event):
    def __init__(self, instance_id, timestamp, event_method, sms_to, event_by=None):
        assert isinstance(sms_to, dict)
        for iso_code in sms_to:
            assert iso_code in list(constants.all_country_codes.keys())
            to_nums = sms_to[iso_code]
            assert isinstance(to_nums, list)
            for item in to_nums:
                assert string_validator.is_phone_number(item)

        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.sms_to = sms_to

    def __str__(self):
        return constants.send_external_sms_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.sms_to: self.sms_to}
        return json.dumps(struct)


class SnoozeEvent(Event):
    '''
    This class represents an event where the assignee
    has snoozed the task for a period of time.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by, snooze_for):
        assert isinstance(snooze_for, int)
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.snooze_for = snooze_for

    def __str__(self):
        return constants.snooze_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.snooze_for: self.snooze_for}
        return json.dumps(struct)


class StatusUpdateEvent(Event):
    '''
    This class represents an event where a status update is added to an instance.
    '''
    def __init__(self, instance_id, timestamp, event_method, update, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.update = update

    def __str__(self):
        return constants.status_update_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by, var_names.status_update: self.update}
        return json.dumps(struct)


class TriggerEvent(Event):
    '''
    This class represents an event where a new alert (not the same as the notification alert)
    is triggered for the instance. It essentially resets the status (to OPEN) and the base start for notifications.
    '''
    def __init__(self, instance_id, timestamp, event_method, next_alert_timestamp):
        assert isinstance(next_alert_timestamp, datetime.datetime)
        Event.__init__(self, instance_id, timestamp, event_method)
        self.next_alert_timestamp = next_alert_timestamp

    def __str__(self):
        return constants.trigger_event

    def db_log(self):
        struct = {var_names.next_alert_timestamp: self.next_alert_timestamp}
        return json.dumps(struct, default=helpers.jsonify_unserializable)


class UnAcknowledgeEvent(Event):
    '''
    This class represents an event where an already acknowledged instance is un-acknowledged.
    The act of un-acknowledging will change the status to OPEN and alert all prior and current assignees again.
    '''
    def __init__(self, instance_id, timestamp, event_method, event_by, next_alert_timestamp):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.next_alert_timestamp = next_alert_timestamp

    def __str__(self):
        return constants.un_acknowledge_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.next_alert_timestamp: self.next_alert_timestamp}
        return json.dumps(struct, default=helpers.jsonify_unserializable)


class UnMergeEvent(Event):
    '''
    This class represents an event where an instance is un-merged from another instance.
    '''
    def __init__(self, instance_id, timestamp, event_method, split_task_id, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.split_task_id = split_task_id

    def __str__(self):
        return constants.un_merge_event

    def db_log(self):
        struct = {var_names.task_id: self.split_task_id, var_names.event_by: self.event_by}
        return json.dumps(struct)


class UpdateTagsEvent(Event):
    '''
    This class represents an event where tags associated to an instance are updated.
    '''
    def __init__(self, instance_id, timestamp, event_method, tags, event_by=None):
        if tags is not None:
            assert isinstance(tags, list)
            for item in tags:
                assert isinstance(item, str)

        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.tags = tags

    def __str__(self):
        return constants.update_tags_event

    def db_log(self):
        struct = {var_names.tags: self.tags}
        return json.dumps(struct)


class UrgencyAmendmentEvent(Event):
    '''
    This class represents an event where the urgency level of an instance is updated
    '''
    def __init__(self, instance_id, timestamp, event_method, new_urgency, event_by=None):
        Event.__init__(self, instance_id, timestamp, event_method, event_by)
        self.new_urgency = new_urgency

    def __str__(self):
        return constants.urgency_amendment_event

    def db_log(self):
        struct = {var_names.event_by: self.event_by,
                  var_names.urgency_level: self.new_urgency}
        return json.dumps(struct)
