# By: Riasat Ullah
# This class syncs up live calls stored in db with that in cache.

from cache_queries import cache_live_calls, cache_task_instances
from data_syncers import syncer_organizations, syncer_services, syncer_task_instances
from dbqueries import db_events, db_live_call_routing
from modules.router import Router
from modules.workflow_manager import WorkflowManager
from objects.events import CallAnsweredEvent, CallEndedEvent, CallForwardingEvent, CallOutgoingEvent, \
    CallVoicemailPrompt, ResolveEvent
from objects.live_call_routing import LiveCallRouting
from objects.task_payload import TaskPayload
from taskcallrest import settings
from utils import constants, helpers, permissions
import configuration as configs
import json


def store_live_call(client, lcr_object):
    '''
    Store a live call routing instance.
    :param client:
    :param lcr_object:
    '''
    if settings.CACHE_ON:
        cache_live_calls.store_single_live_call(client, lcr_object)


def get_live_call(client, call_sid):
    '''
    Get a live call routing instance.
    :param client: cache client
    :param call_sid: Twilio CallSid
    :return: LiveCallRouting object
    '''
    if settings.CACHE_ON:
        return cache_live_calls.get_single_live_call(client, call_sid)
    return None


def log_live_call(conn, client, timestamp, request_data, lcr: LiveCallRouting, from_iso_code=None, service_id=None,
                  with_fwd_user=None, with_voicemail=False, with_call_end=False, with_task_id=None, with_assignee=None):
    '''
    Log an incoming live call that is being handled by call routing.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param request_data: request.data from the HttpRequest
    :param lcr: LiveCallRouting object
    :param from_iso_code: ISO code of the caller's phone number
    :param service_id: ID of the service the call is being assigned to
    :param with_fwd_user: forwarding user ID; if provided, a CallForwarding event is booked in the database
    :param with_voicemail: True if a CallVoicemailPrompt event should be booked afterward
    :param with_call_end: True if a CallEnd event should be booked afterward
    :param with_task_id: ID of the task that this live call is related to. This should be used when a return call is
                        being made to tie the details to the same instance.
    :param with_assignee: Policy ID of the user who is making an outgoing call. This will be used to assign the incident
                        to the user. It will only be needed when it is not a return call.
    :return: instance ID; None if no instance is created
    '''
    task_status = None
    if lcr.call_status == constants.blocked_state:
        task_status = constants.suppressed_state
    elif lcr.call_status == constants.outgoing and with_task_id is not None:
        task_status = constants.grouped_state

    # Only when outgoing return calls are made, an instance ID
    # should already be expected to be added to the live call routing
    if lcr.instance_id is None:
        if lcr.caller_details is not None:
            # It is important to process the request.data in through json.dumps -> json.loads to make the
            # stored trigger_info consistent. If this is not done, then the data gets stored as From: ['']
            # when the request.data object is treated as a dict and updated with new info.
            request_data = {**json.loads(json.dumps(request_data, default=helpers.jsonify_unserializable)),
                            **lcr.caller_details}

        payload = TaskPayload(
            timestamp, lcr.organization_id, timestamp.date(), lcr.get_incident_title(), configs.standard_timezone,
            timestamp.time(), text_msg=lcr.get_incident_description(), urgency_level=lcr.urgency_level,
            trigger_method=constants.live_call_routing, trigger_info=request_data, service_id=service_id,
            task_status=task_status, related_task_id=with_task_id, instantiate=True if task_status is None else False,
            alert=False, assignees=[with_assignee] if with_assignee is not None else None
        )
        lcr.instance_id = Router(conn, client, payload).run()

    # Change the call status here if no incidents were created. This is to account for cases where instances
    # are grouped or suppressed because of intelligent grouping.
    if lcr.instance_id is None and with_task_id is not None:
        lcr.call_status = constants.suppressed_state

    db_live_call_routing.log_live_call(
        conn, timestamp, lcr.organization_id, lcr.call_routing_id, constants.twilio, lcr.twilio_call_sid,
        lcr.caller, lcr.phone, instance_id=lcr.instance_id, from_iso=from_iso_code, to_iso=lcr.iso_country_code,
        call_status=lcr.call_status
    )

    if settings.CACHE_ON and lcr.call_status not in [constants.blocked_state, constants.suppressed_state]:
        cache_live_calls.store_single_live_call(client, lcr)

    if lcr.instance_id is not None:
        if lcr.call_status == constants.outgoing:
            outgoing_call(conn, timestamp, lcr)

        if with_fwd_user is not None:
            forward_call(conn, timestamp, lcr, with_fwd_user)
        elif with_voicemail:
            prompt_for_voicemail(conn, client, timestamp, lcr)
        elif with_call_end:
            end_call(conn, client, timestamp, lcr)

    return lcr.instance_id


def forward_call(conn, timestamp, lcr: LiveCallRouting, fwd_user: int):
    '''
    Forwards a live call.
    :param conn: db connection.
    :param timestamp: timestamp when this request is being made
    :param lcr: LiveCallRouting object
    :param fwd_user: ID of the user to forward to
    '''
    event = CallForwardingEvent(lcr.instance_id, timestamp, constants.internal, fwd_user, lcr.forwarding_count)
    db_events.book_call_forwarding_event(conn, event, lcr.organization_id)


def answer_call(conn, client, timestamp, lcr: LiveCallRouting):
    '''
    Answer a live call.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param lcr: LiveCallRouting object
    '''
    event = CallAnsweredEvent(lcr.instance_id, timestamp, constants.internal, lcr.last_on_call_user_id())
    updated_next_alert = db_events.book_call_answered_event(conn, event, lcr.organization_id)

    if settings.CACHE_ON:
        cache_live_calls.store_single_live_call(client, lcr)

        inst = syncer_task_instances.get_single_instance(conn, client, timestamp, event.instance_id, store_misses=False)
        if inst is not None:
            inst.acknowledge(updated_next_alert)
            cache_task_instances.store_single_instance(client, inst)

            org_perm = syncer_organizations.get_single_organization_permission(
                conn, client, timestamp, lcr.organization_id)
            syncer_services.run_integration_syncer(conn, client, timestamp, lcr.organization_id, event.instance_id,
                                                   new_status=constants.acknowledged_state)
            if permissions.has_org_permission(org_perm, permissions.ORG_WORKFLOWS_PERMISSION):
                WorkflowManager(conn, client, lcr.organization_id, org_perm, event.instance_id,
                                constants.acknowledge_event).execute_workflow()


def prompt_for_voicemail(conn, client, timestamp, lcr: LiveCallRouting):
    '''
    Prompt for a voicemail on the call.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param lcr: LiveCallRouting object
    '''
    event = CallVoicemailPrompt(lcr.instance_id, timestamp, constants.internal)
    db_events.book_call_voicemail_prompt_event(conn, event, lcr.organization_id, lcr.call_status, lcr.call_duration)

    if settings.CACHE_ON:
        cache_live_calls.remove_live_call(client, lcr.twilio_call_sid)


def end_call(conn, client, timestamp, lcr: LiveCallRouting, voicemail_url=None):
    '''
    End a live call.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param lcr: LiveCallRouting object
    :param voicemail_url: url where the voicemail can be retrieved from
    '''
    event = CallEndedEvent(lcr.instance_id, timestamp, constants.internal, lcr.call_status, lcr.call_duration,
                           voicemail_url=voicemail_url)
    db_events.book_call_ended_event(conn, event, lcr.organization_id)

    if (lcr.call_status == constants.answered_state and lcr.resolve_answered_calls)\
            or ((lcr.call_status in (constants.no_answer_state, constants.message_left_state)
                 or lcr.call_status is None) and lcr.resolve_unanswered_calls):

        org_perm = syncer_organizations.get_single_organization_permission(
            conn, client, timestamp, lcr.organization_id)
        event = ResolveEvent(lcr.instance_id, timestamp, constants.internal)
        syncer_task_instances.resolve(
            conn, client, event, org_id=lcr.organization_id, is_sys_action=True,
            voice_url=voicemail_url, from_num=lcr.caller, to_num=lcr.phone, org_perm=org_perm
        )
        if permissions.has_org_permission(org_perm, permissions.ORG_WORKFLOWS_PERMISSION):
            WorkflowManager(conn, client, lcr.organization_id, org_perm, event.instance_id,
                            constants.resolve_event).execute_workflow()
    elif lcr.to_alert:
        syncer_task_instances.enable_instance_alerting(conn, client, timestamp, lcr.organization_id, lcr.instance_id,
                                                       to_enable=True)
    if settings.CACHE_ON:
        cache_live_calls.remove_live_call(client, lcr.twilio_call_sid)


def outgoing_call(conn, timestamp, lcr: LiveCallRouting):
    '''
    Forwards a live call.
    :param conn: db connection.
    :param timestamp: timestamp when this request is being made
    :param lcr: LiveCallRouting object
    '''
    event = CallOutgoingEvent(lcr.instance_id, timestamp, constants.internal)
    db_events.book_call_outgoing_event(conn, event, lcr.organization_id)
