HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/mobile/mobile_inbound_call.py
"""
Governs the logic of the incoming lead calls and lead/agent interations.

At any given moment the call is in one of the following states:

* 'NEW': New inbound call
* 'CALLBACK': Callback call, agent called first
* 'LEAD_ONHOLD': Lead selected a routing, placed on hold until agent answers
* 'AGENT_ONHOLD': In a scheduled callback, the agent is called first and placed
  on hold.
* 'CALLBACK_PROMPT': Lead has been waiting for a while and is asked if he wants
  to be called back.
* 'ONGOING': Agent answered/call ongoing.
* 'MISSED': Call ended/lead missed.
* 'CAPTURED': Call ended/lead captured.
"""
import logging
import traceback
from flask import (
    Blueprint,
    request,
    current_app as app,
    url_for,
    jsonify,
    make_response
)
from buyercall.lib.util_twilio import (
    bw_client,
    CallStorage,
    InboundCallState as State
)
from buyercall.lib.bandwidth_bxml import create_xml_response, Response
import json
from buyercall.lib.util_webhooks import WebhookUtil
import redis
from dateutil import parser
from sqlalchemy import and_
from sqlalchemy.orm import load_only
from buyercall.blueprints.leads.models import Lead
from buyercall.blueprints.mobile.models import Endpoint
from buyercall.blueprints.phonenumbers.models import Phone, Audio
from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.block_numbers.models import Block
from buyercall.blueprints.contacts.models import Contact
from buyercall.blueprints.filters import format_phone_number_bracket, format_phone_number
from buyercall.extensions import csrf, db
from buyercall.lib.bandwidth import (
    BandwidthException,
)

log = logging.getLogger(__name__)
webhooker = WebhookUtil()

mobile_call_inbound = Blueprint(
    'mobile_call_inbound', __name__, template_folder='templates'
)
provider = "bandwidth"

TRANSFER_CALL_TIMEOUT = "40"
""" The call timeout period in seconds for transfer """


@mobile_call_inbound.route('/bw/mobile/voice', methods=['GET', 'POST'])
@csrf.exempt
def mobile_voice_call():
    """ Entry point for the incoming and outbound mobile sip phone call. This is specifically for Bandwidth API V2.
    This is for SIP Registrar calls.
    """
    from buyercall.lib.util_bandwidth import authenticate_bw_request
    authenticated = authenticate_bw_request(request.headers)
    if authenticated:
        # Fetch the request data. This includes message data for incoming sms/mms
        args = request.json or request.args
        log.info('The initiated mobile call callback args: {}'.format(args))
        return handle_mobile_call(args)
    else:
        print('Authentication failed')
        status_code = 401
        message = jsonify(message='Authentication failed.')
        response = make_response(message, status_code)
        return response


def handle_mobile_call(*args):
    """
    This function handles sip calling once a call is initiated. This
    function will check if agent is available and then determine if its a inbound
    or outbound call and transfer accordingly.
    """
    args = request.json or request.args
    c_from = args.get('from', '')
    c_to = args.get('to', '')
    call_id = args.get('callId', '')

    # Declare BXML response
    bxml = Response()

    outbound_call = Endpoint.api_username_check(c_from)
    inbound_call = False
    if outbound_call:
        bc_sip = Endpoint.query.filter(Endpoint.sip_username == c_from).first()
        bc_sip_tn = Phone.query.filter(Phone.id == bc_sip.inbound_id).first()
        lead_phone_number = c_to
    else:
        inbound_call = Endpoint.api_username_check(c_to)
        if inbound_call:
            bc_sip = Endpoint.query.filter(Endpoint.sip_username == c_to).first()
            bc_sip_tn = Phone.query.filter(Phone.id == bc_sip.inbound_id).first()
        else:
            log.info('The to number is: {}'.format(c_to))
            bc_sip_tn = Phone.query.filter(and_(Phone.phonenumber == c_to, Phone.is_deactivated.is_(False))).first()
            log.info('The returned phone id is: {}'.format(bc_sip_tn.id))
            if bc_sip_tn.id:
                inbound_call = True
                bc_sip = Endpoint.query.filter(Endpoint.inbound_id == bc_sip_tn.id).first()
            else:
                bxml.hangup()
                log.error('Unable to find bc sip or number for to: {} and from: {}'.format(c_to, c_from))
                return create_xml_response(bxml)
        lead_phone_number = c_from
    if not bc_sip:
        bxml.hangup()
        log.error('Unable to find bc sip or number for to: {} and from: {}'.format(c_to, c_from))
        return create_xml_response(bxml)

    # Check to see if a lead exist already
    lead = Lead.query.filter(Lead.call_sid == call_id).first()
    # Set the lead or create it if it doesn't exist yet
    if lead:
        lead_id = lead.id
    else:
        from .tasks import mobile_lead_create
        lead_new = mobile_lead_create(bc_sip_tn.id, call_id, lead_phone_number, outbound_call)
        lead_id = lead_new
    # Ping webhook to say a new call has started
    webhooker.trigger_generic_webhook('mobile_start_call', lead_id)

    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
    # Activate and set redis storage and add lead
    storage = CallStorage(redis_db, lead_id)

    # Set the storage routing and sip id
    storage.routing_config = bc_sip_tn.routing_config
    storage.state = State.NEW
    storage.sip_endpoint_id = bc_sip.id
    storage.agent_id = bc_sip.agent_id
    if outbound_call:
        storage.sip_call_direction = 'outbound'
    else:
        storage.sip_call_direction = 'inbound'

    if inbound_call:
        # Retrieve the agent that's associated with the endpoint
        agent = Agent.query.filter(Agent.id == bc_sip.agent_id).first()
        # Check if the incoming call is labeled as a blocked number
        is_number_blocked = Block.blocked(bc_sip_tn.id, lead_phone_number)
        if is_number_blocked:
            storage.state = State.BLOCKED
            storage.cause_description = 'Incoming phone number on blocked list for incoming mobile call.'
            bxml.hangup()
            return create_xml_response(bxml)
        if agent.available_now is False:
            storage.cause_description = 'The agent is unavailable.'
            return missed_call(storage, lead_id, bc_sip_tn.id)

        # Inbound transfer
        return mobile_inbound_transfer(lead_id, args)
    else:
        # outbound transfer
        return mobile_outbound_transfer(lead_id, args)


def mobile_inbound_transfer(lead_id, args):
    """
    This function handles inbound transfers for mobile
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
    # Activate and set redis storage and add lead
    storage = CallStorage(redis_db, lead_id)
    # Declare the bandwidth response xml tag. This will be used for the xml responses
    bxml = Response()

    # Return the lead information
    lead = Lead.query.filter(Lead.id == lead_id).first()

    # Return all variables from the Routing saved in Redis Storage
    greeting_enabled = storage.routing_config.get('greetingMessage', '')
    greeting_type = storage.routing_config.get('whisperMessageType', '')
    greeting = storage.routing_config.get('whisperMessage', '')
    language = storage.routing_config.get('language', '')

    # This is the web-hook that will be called if a transfer is answered
    transfer_answer_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_answer_bw_webhook', lead_id=lead.id,
        _external=True, _scheme='https')
    # This is the fallback web-hook that will be called if a transfer is answered
    transfer_answer_fallback_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_answer_fallback_bw_webhook', lead_id=lead.id,
        _external=True, _scheme='https')
    # This is the web-hook that will be called if a transfer is disconnected
    transfer_disconnect_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_disconnect_bw_webhook', lead_id=lead.id,
        _external=True, _scheme='https')

    # This is the web-hook that will be called if a transfer is completed
    transfer_complete_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_complete_bw_webhook', inbound_id=lead.inbound_id, lead_id=lead.id,
        _external=True, _scheme='https')
    # This is the fallback web-hook that will be called if a transfer is completed
    transfer_complete_fallback_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_complete_fallback_bw_webhook', inbound_id=lead.inbound_id, lead_id=lead.id,
        _external=True, _scheme='https')

    try:
        # If greeting is enabled and its the very first agent in sequence and not a retry call
        # we play a greeting message to the caller
        if greeting_enabled:
            if greeting_type == 'audio':
                greeting_audio = Audio.query \
                    .filter(and_(Audio.whisper_message_type == 'whisperMessage',
                                 Audio.inbound_id == lead.inbound_id)).first()
                if greeting_audio:
                    greeting_audio_link = greeting_audio.audio_url
                    bxml.tag('pre-transfer')
                    bxml.play_audio(greeting_audio_link)
                else:
                    log.error('There is no whisperMessage audio link for inbound id: {}'.format(inbound_id))
            else:
                if language == 'es':
                    bxml.tag('pre-transfer')
                    bxml.say(greeting, 'female', 'es_MX', 'esperanza')
                else:
                    bxml.tag('pre-transfer')
                    bxml.say(greeting)
        call_time_out = TRANSFER_CALL_TIMEOUT
        log.info(f"storage call sid id before saving : {storage.call_sid} {type(storage.call_sid)}")
        # Check if the caller id needs to be the tracking number or the caller's number
        caller_id = lead.phonenumber
        # Perform the transfer to the agent mobile sip
        transfer = bxml.transfer(caller_id, call_time_out, transfer_complete_webhook_url,
                                 transfer_complete_fallback_webhook_url, tag='transfer-initiated')
        # Lookup to see if the agent number is associated with a BuyerCall sip endpoint
        bc_sip_lookup = Phone.mobile_sip_uri(lead.my_phone)
        if bc_sip_lookup:
            agent_call_number = bc_sip_lookup
            transfer.sip_uri(agent_call_number, transfer_answer_webhook_url, transfer_answer_fallback_webhook_url,
                             transfer_disconnect_webhook_url)
        else:
            agent_call_number = format_phone_number(lead.my_phone)
            transfer.phone_number(agent_call_number, transfer_answer_webhook_url, transfer_answer_fallback_webhook_url,
                                  transfer_disconnect_webhook_url)
        return create_xml_response(bxml)
    except BandwidthException:
        log.error(traceback.format_exc())
        log.error('Error calling call id {}...'.format(lead.call_sid))
    return ''


def mobile_outbound_transfer(lead_id, args):
    """
    This function handles inbound transfers for mobile
    """
    # Declare the bandwidth response xml tag. This will be used for the xml responses
    bxml = Response()

    # Get the to number that is being called by agent
    c_to = args.get('to', '')

    # Return the lead information
    lead = Lead.query.filter(Lead.id == lead_id).first()

    # This is the web-hook that will be called if a transfer is answered
    transfer_answer_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_answer_bw_webhook', lead_id=lead.id,
        _external=True, _scheme='https')
    # This is the fallback web-hook that will be called if a transfer is answered
    transfer_answer_fallback_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_answer_fallback_bw_webhook', lead_id=lead.id,
        _external=True, _scheme='https')
    # This is the web-hook that will be called if a transfer is disconnected
    transfer_disconnect_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_disconnect_bw_webhook', lead_id=lead.id,
        _external=True, _scheme='https')

    # This is the web-hook that will be called if a transfer is completed
    transfer_complete_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_complete_bw_webhook', inbound_id=lead.inbound_id, lead_id=lead.id,
        _external=True, _scheme='https')
    # This is the fallback web-hook that will be called if a transfer is completed
    transfer_complete_fallback_webhook_url = url_for(
        'mobile_call_inbound.mobile_transfer_complete_fallback_bw_webhook', inbound_id=lead.inbound_id, lead_id=lead.id,
        _external=True, _scheme='https')

    try:
        caller_id = lead.my_phone
        call_time_out = "65"
        # Perform the transfer to the agent mobile sip
        transfer = bxml.transfer(caller_id, call_time_out, transfer_complete_webhook_url,
                                 transfer_complete_fallback_webhook_url, tag='transfer-initiated')

        transfer.phone_number(c_to, transfer_answer_webhook_url, transfer_answer_fallback_webhook_url,
                              transfer_disconnect_webhook_url)
        return create_xml_response(bxml)
    except BandwidthException:
        log.error(traceback.format_exc())
        log.error('Error calling call id {}...'.format(lead.call_sid))
    return ''


@mobile_call_inbound.route('/bw/mobile/voice/status', methods=['GET', 'POST'])
@csrf.exempt
def mobile_voice_status_callback(*args):
    """ Entry point for the incoming voice status callback. This is specifically for Bandwidth API
    V2. A Location exist with 2 application attach to it. One is for SMS/MMS and the other for Voice. This call-back url
    is specified on the Voice application.
    """
    from buyercall.lib.util_bandwidth import authenticate_bw_request
    authenticated = authenticate_bw_request(request.headers)
    if authenticated:
        # Declare redis config url
        redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

        # Fetch the request data. This includes message data for incoming sms/mms
        args = request.json or request.args
        # Fetch args values
        call_id = args.get('callId', '')
        event_type = args.get('eventType', '')
        req_start_time = parser.parse(args.get('startTime', ''))
        if req_start_time:
            start_time = req_start_time
        else:
            start_time = parser.parse(args.get('enqueuedTime', ''))
        end_time = parser.parse(args.get('endTime', ''))
        cause = args.get('cause', '')
        tag = args.get('tag', '')
        error_message = args.get('errorMessage', '')
        # Look up lead in db based on the provider call sid
        call_lead = Lead.query.filter(Lead.call_sid == call_id).first()
        log.info('The status callback args: {}'.format(args))
        try:
            # Fetch storage from Redis based on call lead id
            storage = CallStorage(redis_db, call_lead.id)
            cause_description = storage.cause_description
            if error_message:
                storage.state = State.ERROR
                storage.cause_description = error_message
            log.info('Storage state is: {}'.format(storage.state))
            log.info('The tag is: {}'.format(tag))
            if storage.state not in ('ANSWERED', 'CAPTURED'):
                if event_type == 'disconnect' and storage.state == 'NEW':
                    storage.state = 'MISSED'
                    cause_description = 'Call ended by caller before transfer can occurred.'
                if storage.state == 'MISSED' and tag == 'transfer-complete':
                    cause_description = 'Call ended by callee.'
                duration, response_time_seconds = 0, 0
                if storage.connect_time:
                    connect_time = parser.parse(storage.connect_time)
                    response_time_seconds = (connect_time - start_time).total_seconds()
                duration = (end_time - start_time).total_seconds()
                agent = Agent.query.filter(Agent.id == storage.agent_id).first()
                lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time,
                                   cause, cause_description, tag, agent=agent)
            else:
                if tag == 'transfer-initiated':
                    call_lead.cause_description = 'Call ended by caller.'
                    db.session.commit()
                elif tag == 'transfer-complete':
                    call_lead.cause_description = 'Call ended by callee.'
                    db.session.commit()
            return ''
        except Exception as e:
            log.error('Unable to find call id; {} in the db. error: {}'.format(call_id, e))
            return ''
    else:
        print('Authentication failed')
        status_code = 401
        message = jsonify(message='Authentication failed.')
        response = make_response(message, status_code)
        return response


@mobile_call_inbound.route('/bw/mobile/voice/status/fallback', methods=['GET', 'POST'])
@csrf.exempt
def voice_fallback_status_callback():
    """ Entry point for the incoming voice fallback callback. This is specifically for Bandwidth API
    V2. A Location exist with 2 application attach to it. One is for SMS/MMS and the other for Voice. This call-back url
    is specified on the Voice application. This function will call the status callback endpoint.
    """
    from buyercall.lib.util_bandwidth import authenticate_bw_request
    authenticated = authenticate_bw_request(request.headers)
    if authenticated:
        # Fetch the request data. This includes message data for incoming sms/mms
        args = request.json or request.args
        return mobile_voice_status_callback(args)
    else:
        print('Authentication failed')
        status_code = 401
        message = jsonify(message='Authentication failed.')
        response = make_response(message, status_code)
        return response


@mobile_call_inbound.route('/api/bw/mobile-transfer-answer/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def mobile_transfer_answer_bw_webhook(lead_id, *args):
    """ This endpoint should be hit when a transfer is answered sending over an event. Additional bXML can be
        invoked when this endpoint is hit. The only event to be sent in this
        call back is: TransferAnswered
        https://dev.bandwidth.com/voice/bxml/callbacks/transferAnswer.html
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
    # Declare the redis db model
    storage = CallStorage(redis_db, lead_id)

    record_call = storage.routing_config.get('recordCalls', '')
    transcribe_call = storage.routing_config.get('transcribeAnsweredCall', '')

    # Get the params of the Request
    args = request.json or request.args
    log.info('The transfer answer callback response: {}'.format(args))
    log.info('THE AGENT CALL ID IS: {}'.format(storage.agent_call_id))
    transfer_call_id = args.get('callId')
    connect_time = args.get('answerTime', '')
    to_number = str(args.get('to', ''))
    log.info('THE TO number is: {}'.format(to_number))

    from .tasks import mobile_update_lead
    mobile_update_lead.delay(lead_id, to_number=to_number, transfer_call_id=transfer_call_id,
                             connect_time=connect_time)

    # Declare the bandwidth response xml tag. This will be used for the xml responses
    bxml = Response()
    # Set the answer tag
    bxml.tag('transfer-answer')
    if storage.sip_call_direction == 'inbound':
        # Play a custom message to the agent before connecting with the lead
        hidden_information = storage.routing_config.get('hiddenInformation', '')
        if hidden_information:
            bxml.say('.....')
            bxml.say(hidden_information)
    if record_call:
        if storage.sip_call_direction == 'outbound':
            bxml.say('...Please note that this call might be recorded.')
        record_call_back_url = url_for(
            'bw_operational.operational_call_record_bw_webhook', lead_id=lead_id,
            _external=True, _scheme='https')
        if transcribe_call:
            transcribe = "true"
            transcribe_call_back_url = url_for(
                'bw_operational.operational_call_transcribe_bw_webhook', lead_id=lead_id,
                _external=True, _scheme='https')
        else:
            transcribe = "false"
            transcribe_call_back_url = ""

        bxml.start_record(recording_available_url=record_call_back_url, transcribe=transcribe,
                          transcribe_available_url=transcribe_call_back_url, tag='start-recording')
    if storage.sip_call_direction == 'inbound':
        bxml.play_audio(app.config['BEEP_SOUND'])
    return create_xml_response(bxml)


@mobile_call_inbound.route('/api/bw/mobile-transfer-answer-fallback/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def mobile_transfer_answer_fallback_bw_webhook(lead_id):
    """ This is the fallback callback url for the transfer answer callback event
        https://dev.bandwidth.com/voice/bxml/callbacks/transferAnswer.html
    """
    # Get the params of the Request
    args = request.json or request.args
    log.info('The transfer answer fallback callback response: {}'.format(args))
    return mobile_transfer_answer_bw_webhook(lead_id, args)


@mobile_call_inbound.route('/api/bw/mobile-transfer-disconnect/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def mobile_transfer_disconnect_bw_webhook(lead_id, *args):
    """ This endpoint should be hit when a transfer is disconnected sending over an event. Additional bXML can not be
        invoked when this endpoint is hit. The only event to be sent in this
        call back is: TransferDisconnect
        https://dev.bandwidth.com/voice/bxml/callbacks/transferDisconnect.html
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
    # Declare the redis db model
    storage = CallStorage(redis_db, lead_id)

    # Get the params of the Request
    args = request.json or request.args
    call_id = args.get('callId', '')
    req_start_time = parser.parse(args.get('startTime', ''))
    if req_start_time:
        start_time = req_start_time
    else:
        start_time = parser.parse(args.get('enqueuedTime', ''))
    end_time = parser.parse(args.get('endTime', ''))
    cause = args.get('cause', '')
    error_message = args.get('errorMessage', '')
    tag = args.get('tag', '')
    # Lookup the Call information from the DB
    call_lead = Lead.query.filter(Lead.id == lead_id).first()
    duration, response_time_seconds, cause_description = None, None, None
    if tag == 'transfer-answer' or tag == 'start-recording':
        storage.state = State.CAPTURED
        if storage.connect_time:
            connect_time = parser.parse(storage.connect_time)
            response_time_seconds = (connect_time - start_time).total_seconds()
        duration = (end_time - start_time).total_seconds()
        # Label call as complete
        lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time,
                           cause, cause_description, tag)
    # This following lines need to replace the else statement. This is a bug with BW not reporting the cancel
    # cause properly. Once fix the cancel cause and error message should be included in the callback.
    elif cause in ['cancel', 'rejected', 'timeout']:
        storage.state = State.MISSED
        storage.cause_description = error_message
    else:
        storage.state = State.MISSED
        if not storage.cause_description:
            storage.cause_description = 'Call ended by caller before it was answered by callee.'

    if cause in ['error', 'node-capacity-exceeded', 'unknown', 'callback-error',
                 'invalid-bxml', 'application-error', 'account-limit']:

        storage.state = State.ERROR
        cause_description = error_message
        # Label call as complete
        lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time,
                           cause, cause_description, tag)

    log.info('The transfer disconnect callback response: {}'.format(args))

    return ''


@mobile_call_inbound.route('/api/bw/mobile-transfer-complete/webhook/<int:inbound_id>/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def mobile_transfer_complete_bw_webhook(inbound_id, lead_id, *args):
    """ This endpoint should be hit when a transfer is complete sending over an event. Additional bXML can be
        invoked when this endpoint is hit after a transfer hangs-up. The only event to be sent in this
        call back is: TransferComplete
        https://dev.bandwidth.com/voice/bxml/callbacks/transferComplete.html
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
    # Declare the redis db model
    storage = CallStorage(redis_db, lead_id)

    # BXML Response
    bxml = Response()

    # Get the params of the Request
    args = request.json or request.args
    log.info('The transfer complete callback response: {}'.format(args))
    cause = args.get('cause', '')
    error_message = args.get('errorMessage', '')

    if cause in ['timeout', 'reject', 'cancel']:
        storage.call_cause = cause
        storage.cause_description = error_message
        if storage.sip_call_direction == 'inbound':
            return missed_call(storage, lead_id, inbound_id)
        else:
            bxml.tag('transfer-no-answer')
    else:
        bxml.tag('transfer-complete')
    return create_xml_response(bxml)


@mobile_call_inbound.route('/api/bw/mobile-transfer-complete-fallback/webhook/<int:inbound_id>/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def mobile_transfer_complete_fallback_bw_webhook(inbound_id, lead_id):
    """ This is the fallback callback url for the transfer complete callback event
        https://dev.bandwidth.com/voice/bxml/callbacks/transferComplete.html
    """
    # Get the params of the Request
    args = request.json or request.args
    log.info('The transfer complete fallback callback response: {}'.format(args))
    return mobile_transfer_complete_bw_webhook(lead_id, inbound_id, args)


@mobile_call_inbound.route('/mobile/inbound/call/<int:inbound_id>', methods=['GET', 'POST'])
@csrf.exempt
def mobile_inbound_call(inbound_id):
    # This function is a call back url for SIP incoming phone calls.
    # Whenever a call is made by a SIP account this call back url will be called.

    # Retrieve mobile number configuration
    mobile_pn = Phone.query.filter(Phone.id == inbound_id).first()
    # import partnership information to get partnership id
    from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
    from buyercall.blueprints.mobile.utils import send_agent_push_notification
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == mobile_pn.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    # Initiate Bandwidth API
    client = bw_client(partner.id)

    # Get the params of the Request
    args = request.json or request.args
    print(args)

    # Determine some request data
    event_type = args.get('eventType')
    call_status = args.get('callState')
    call_id = args.get('callId')
    to_number = args.get('to')
    from_number = args.get('from')
    log.info('The event type is: {}'.format(event_type))

    # Recording of calls setting
    recording_enabled = mobile_pn.routing_config['recordCalls']

    # Transcription settings for calls
    try:
        transcribe_call_enabled = mobile_pn.routing_config['transcribeAnsweredCall']
        if transcribe_call_enabled:
            transcribe_call_enabled_str = str(True)
        else:
            transcribe_call_enabled_str = str(False)
    except Exception as e:
        transcribe_call_enabled = False
        transcribe_call_enabled_str = str(False)
    try:
        transcribe_voicemail_enabled = mobile_pn.routing_config['transcribeVoiceMail']
        if transcribe_voicemail_enabled:
            transcribe_voicemail_enabled_str = str(True)
        else:
            transcribe_voicemail_enabled_str = str(False)
    except Exception as e:
        transcribe_voicemail_enabled = False
        transcribe_voicemail_enabled_str = str(False)

    log.info('The transcribe call value is {}'.format(transcribe_call_enabled))
    log.info('The transcribe voicemail value is {}'.format(transcribe_voicemail_enabled))

    # Greeting whisper message to the inbound caller settings
    greeting_enabled = mobile_pn.routing_config['greetingMessage']
    greeting_type = mobile_pn.routing_config['whisperMessageType']
    greeting_text = mobile_pn.routing_config['whisperMessage']
    # Voicemail settings
    voicemail_enabled = mobile_pn.routing_config['voicemail']
    voicemail_type = mobile_pn.routing_config['voicemailMessageType']
    voicemail_text = mobile_pn.routing_config['voicemailMessage']

    # Get the sip account info
    sip_endpoint = Endpoint.query.filter(Endpoint.inbound_id == inbound_id).first()

    # This determines if the call is originated from a sip outbound call or lead inbound call
    # Based off what the to number is. If the to number is a BuyerCall number we see it as incoming call
    if sip_endpoint is not None:
        if from_number == sip_endpoint.sip_uri:
            lead_phone_number = to_number
            # This determines if the call is originated from a sip outbound call or lead inbound call
            sip_outbound = True
        else:
            lead_phone_number = from_number
            # This determines if the call is originated from a sip outbound call or lead inbound call
            sip_outbound = False

        try:
            # Listen for the incoming call event and create the lead and ping the webhook for call starting
            if event_type == 'incomingcall':
                log.info('The mobile callId is {} and the from number is {} and the to number is {}'
                         .format(call_id, from_number, to_number))

                # Check to see if a lead exist already
                lead = Lead.query.filter(Lead.call_sid == call_id).first()
                # Lets set the lead or create it if it doesn't exist yet
                if lead:
                    lead_id = lead.id
                else:
                    from .tasks import mobile_lead_create
                    lead_new = mobile_lead_create(inbound_id, call_id, lead_phone_number, sip_outbound)
                    lead_id = lead_new

                # Ping webhook to say a new call has started
                webhooker.trigger_generic_webhook('mobile_start_call', lead_id)

            # Listen for the answer event type. The BXML will only be used on answer event as per Bandwidth API doc
            elif event_type == 'answer':
                # Get the callback url for recording
                record_callback = json.dumps('https://' + app.config.get('SERVER_DOMAIN') +
                                             url_for('.mobile_inbound_call_record'))
                log.info('The recording callback url is {}'.format(record_callback))
                # Get the callback url for transcription
                transcribe_callback = json.dumps('https://' + app.config.get('SERVER_DOMAIN') +
                                                 url_for('.mobile_inbound_call_transcribe'))
                # Get the callback url for transfer
                transfer_callback = json.dumps('https://' + app.config.get('SERVER_DOMAIN') +
                                               url_for('.mobile_inbound_call_transfer', inbound_id=inbound_id))
                # Set the call id used as tag in the transfer
                call_id_tag = json.dumps(call_id)
                # Check to see if it's outbound or inbound call and perform transfer
                if sip_outbound:
                    # If it's an outbound call to the SIP then perform the below
                    outbound_transfer_to = json.dumps(lead_phone_number)
                    # Find the Bandwidth phone number associated with a sip endpoint and use it as Caller Id
                    bw_number = Phone.query.filter(Phone.id == inbound_id).first()
                    outbound_caller_id = json.dumps(bw_number.phonenumber)
                    if recording_enabled:
                        # create XML
                        outbound_xml_response = f'<Response><Transfer transferCallerId={outbound_caller_id}' \
                                                f' transferTo={outbound_transfer_to} tag={call_id_tag}>' \
                                                f'<Record requestUrl= {record_callback} fileFormat="wav" transcribe=' \
                                                f'{json.dumps(transcribe_call_enabled_str)} transcribeCallbackUrl=' \
                                                f'{transcribe_callback}/></Transfer></Response>'
                        log.info('The xml response looks like: {}'.format(outbound_xml_response))
                        return outbound_xml_response
                    else:
                        # create XML
                        outbound_xml_response = '<Response><Transfer transferCallerId={} transferTo={} tag= {}> ' \
                                                '</Transfer></Response>'.\
                            format(outbound_caller_id, outbound_transfer_to, call_id_tag)
                        log.info('The xml response looks like: {}'.format(outbound_xml_response))
                        return outbound_xml_response
                else:
                    is_number_blocked = Block.blocked(inbound_id, args.get('from'))
                    if is_number_blocked:
                        log.info('This call was not processed because phone number: {} was blocked'.format(
                            args.get('from')
                        ))
                        blocked_hangup_xml = '<Response><Hangup></Hangup></Response>'
                        return blocked_hangup_xml

                    # Retrieve the agent that's associated with the endpoint
                    agent = Agent.query.filter(Agent.id == sip_endpoint.agent_id).first()
                    if agent.available_now is False:
                        log.info('Agent, name: {} {} and id: {} is not available according to their schedule'
                                 .format(agent.firstname, agent.lastname, agent.id))
                        if voicemail_enabled:
                            unavail_vm_end_xml = '<Record requestUrl={} fileFormat="wav" transcribe={} ' \
                                                 'transcribeCallbackUrl={} tag="voicemail" />'\
                                .format(record_callback, json.dumps(transcribe_voicemail_enabled_str),
                                        transcribe_callback)
                            if voicemail_type == 'text':
                                unavail_vm_intro_xml = ' <SpeakSentence voice="susan" locale="en_US" ' \
                                                       'gender="female" volume="4">{}</SpeakSentence>'\
                                    .format(voicemail_text)
                            else:
                                vm_audio = Audio.query \
                                    .filter(and_(Audio.whisper_message_type == 'voicemailMessage',
                                                 Audio.inbound_id == inbound_id)).first()
                                if vm_audio:
                                    vm_audio_link = vm_audio.audio_url
                                else:
                                    vm_audio_link = ''
                                unavail_vm_intro_xml = '<PlayAudio volume="4">{}</PlayAudio>'.format(vm_audio_link)
                            # Get the beep sound for voicemail
                            vm_beep_sound = app.config.get('BEEP_SOUND')
                            unavail_vm_beep_xml = '<PlayAudio volume="4">{}</PlayAudio>'.format(vm_beep_sound)
                            # Create the full VM BMXL
                            unavail_vm_xml = unavail_vm_intro_xml + unavail_vm_beep_xml + unavail_vm_end_xml
                            log.info('The vm for unavbailable xml looks like {}'.format(unavail_vm_xml))
                            return '<Response>{}</Response>'.format(unavail_vm_xml)
                        else:
                            unavailable_hangup_xml = '<Response><Hangup></Hangup></Response>'
                            return unavailable_hangup_xml
                    # Else if it's an inbound call to the SIP then perform the below
                    inbound_transfer_to = json.dumps(sip_endpoint.sip_uri)
                    # Set the XML to construct BXML for transfer
                    start_response_xml = '<Response>'
                    start_transfer_xml = '<Transfer transferTo=' + inbound_transfer_to + ' tag=' + call_id_tag +\
                                         ' callTimeout="23" requestUrl=' + transfer_callback + ' >'
                    end_transfer_xml = '</Transfer>'
                    end_response_xml = '</Response>'
                    if recording_enabled:
                        record_xml = '<Record requestUrl=' + \
                                     record_callback + ' fileFormat="wav" transcribe=' + \
                                     json.dumps(transcribe_call_enabled_str) + \
                                     ' transcribeCallbackUrl=' + transcribe_callback + ' />'
                    else:
                        record_xml = ''
                    if greeting_enabled:
                        if greeting_type == 'text':
                            greet_xml = '<SpeakSentence voice="susan" locale="en_US" gender="female" volume="4">' + \
                                        greeting_text + '</SpeakSentence>'
                        else:
                            greet_audio = Audio.query \
                                .filter(and_(Audio.whisper_message_type == 'whisperMessage',
                                             Audio.inbound_id == inbound_id)).first()
                            if greet_audio:
                                greet_audio = greet_audio.audio_url
                            else:
                                greet_audio = ''
                            greet_xml = '<PlayAudio volume="4">{}</PlayAudio>'.format(greet_audio)
                    else:
                        greet_xml = ''
                    if voicemail_enabled:
                        vm_end_xml = '<Record requestUrl=' + record_callback + \
                                                ' fileFormat="wav" transcribe=' + \
                                     json.dumps(transcribe_voicemail_enabled_str) + ' transcribeCallbackUrl=' + \
                                                transcribe_callback + ' tag="voicemail" />'
                        if voicemail_type == 'text':
                            vm_intro_xml = ' <SpeakSentence voice="susan" locale="en_US" gender="female" volume="4">' \
                                           + voicemail_text + '</SpeakSentence>'
                        else:
                            vm_audio = Audio.query \
                                .filter(and_(Audio.whisper_message_type == 'voicemailMessage',
                                             Audio.inbound_id == inbound_id)).first()
                            if vm_audio:
                                vm_audio_link = vm_audio.audio_url
                            else:
                                vm_audio_link = ''
                            vm_intro_xml = '<PlayAudio volume="4">{}</PlayAudio>'.format(vm_audio_link)
                        # Get the beep sound for voicemail
                        vm_beep_sound = app.config.get('BEEP_SOUND')
                        vm_beep_xml = '<PlayAudio volume="4">{}</PlayAudio>'.format(vm_beep_sound)
                        # Create the full VM BMXL
                        vm_xml = vm_intro_xml + vm_beep_xml + vm_end_xml
                    else:
                        vm_xml = ''
                    # create XML
                    inbound_xml_response = "{}{}{}{}{}{}{}".format(start_response_xml, greet_xml,
                                                                   start_transfer_xml, record_xml,
                                                                   end_transfer_xml, vm_xml,
                                                                   end_response_xml)
                    log.info('The xml response looks like: {}'.format(inbound_xml_response))

                    return inbound_xml_response

            # Cancel the call if the event is hangup
            if event_type == 'hangup':
                final_call = client.calls.info(call_id)
                # Check to see if a lead exist already
                lead = Lead.query.filter(Lead.call_sid == call_id).first()
                log.info('The call id is {}'.format(call_id))
                call_duration = final_call.get('chargeableDuration')
                if lead:
                    lead.duration = call_duration
                    db.session.commit()
                # Retrieve the agent that's associated with the endpoint
                agent = Agent.query.filter(Agent.id == sip_endpoint.agent_id).first()
                if agent.available_now is False and voicemail_enabled is False:
                    if lead:
                        lead.status = 'missed'
                        db.session.commit()
                    webhooker.trigger_generic_webhook('mobile_end_call', lead.id)
                    contact = Contact.query.filter(Contact.id == lead.contact_id).first()
                    if contact and agent and contact.agent_id != agent.id:
                        contact.agent_id = agent.id
                        contact.agent_assigned = agent.full_name
                        db.session.commit()
                        send_agent_push_notification(contact)
                elif agent.available_now is False and voicemail_enabled:
                    if lead:
                        lead.status = 'missed'
                        db.session.commit()
                try:
                    is_num_blocked = Block.blocked(inbound_id, args.get('from'))
                    if is_num_blocked:
                        if lead:
                            lead.status = 'blocked'
                            db.session.commit()
                        webhooker.trigger_generic_webhook('mobile_end_call', lead.id)
                        contact = Contact.query.filter(Contact.id == lead.contact_id).first()
                        if contact and agent and contact.agent_id != agent.id:
                            contact.agent_id = agent.id
                            contact.agent_assigned = agent.full_name
                            db.session.commit()
                            send_agent_push_notification(contact)
                except Exception as e:
                    log.info('The caller is not in the blocked number list')

            # Check when the transfer is complete and get a new id
            elif event_type == 'transferComplete':
                original_call_id = args.get('tag')
                # Update the lead with the transfer call id
                # Check to see if a lead exist already
                lead = Lead.query.filter(Lead.call_sid == original_call_id).first()
                lead.call_sid = call_id
                if args.get('callState') == 'completed':
                    final_call = client.calls.info(original_call_id)
                    # Get the sip account info
                    sip_endpoint = Endpoint.query.filter(Endpoint.inbound_id == lead.inbound_id).first()
                    log.info('The call id is {}'.format(call_id))
                    call_duration = final_call.get('chargeableDuration')
                    from .tasks import mobile_lead_update
                    mobile_lead_update(lead, sip_endpoint.agent_id, call_duration)
                    if recording_enabled is False:
                        webhooker.trigger_generic_webhook('mobile_end_call', lead.id)
                        agent = Agent.query.filter(Agent.id == sip_endpoint.agent_id).first()
                        contact = Contact.query.filter(Contact.id == lead.contact_id).first()
                        if contact and agent and contact.agent_id != agent.id:
                            contact.agent_id = agent.id
                            contact.agent_assigned = agent.full_name
                            db.session.commit()
                            send_agent_push_notification(contact)
                db.session.commit()

        except BandwidthException:
            log.error(traceback.format_exc())
        return ''
    else:
        log.info('No sip endpoint exist and associated with inbound id: {}'.format(inbound_id))
        return ''


@mobile_call_inbound.route('/mobile/inbound/callRecording/', methods=['GET', 'POST'])
@csrf.exempt
def mobile_inbound_call_record():
    # This function is a call back url for SIP incoming phone calls.
    # Whenever a call is complete and record is enabled by a SIP account this call back url will be called.

    # Get the params of the Request
    args = request.json or request.args
    log.info('The recording callback info is: {}'.format(args))

    record_status = args.get('status')
    call_id = args.get('callId')
    recording_id = args.get('recordingId')

    lead = Lead.query.filter(Lead.call_sid == call_id).first()
    lead.recording_id = recording_id
    db.session.commit()

    sip_endpoint = Endpoint.query.filter(Endpoint.inbound_id == lead.inbound_id).first()

    # Retrieve mobile number configuration
    mobile_pn = Phone.query.filter(Phone.id == lead.inbound_id).first()
    # Retrieve transcription setting. If on, the web-hook gets sent after transcription. If off it will be sent now.
    call_transcribe_enabled = False
    try:
        if mobile_pn.routing_config['transcribeAnsweredCall']:
            call_transcribe_enabled = mobile_pn.routing_config['transcribeAnsweredCall']
    except Exception as e:
        log.info('There is no transcribe answered call boolean set for this number id: {}'.format(mobile_pn.id))
    vm_transcribe_enabled = False
    try:
        if mobile_pn.routing_config['transcribeVoiceMail']:
            vm_transcribe_enabled = mobile_pn.routing_config['transcribeVoiceMail']
    except Exception as e:
        log.info('There is no transcribe voicemail boolean set for this number id: {}'.format(mobile_pn.id))

    try:
        if record_status == 'complete':
            from buyercall.blueprints.phonenumbers.bw_tasks import bw_upload_recording
            from buyercall.blueprints.mobile.utils import send_agent_push_notification
            agent = Agent.query.filter(Agent.id == sip_endpoint.agent_id).first()
            bw_upload_recording(call_id)
            if lead.status == 'missed':
                contact = Contact.query.filter(Contact.id == lead.contact_id).first()
                if contact is not None and contact.user_fullname not in ('', ' '):
                    contact_detail = contact.user_fullname
                else:
                    contact_detail = format_phone_number_bracket(lead.phonenumber)
                vm_push_msg = 'New voicemail from ' + contact_detail
                from buyercall.blueprints.mobile.tasks import push_notification
                push_notification(sip_username=sip_endpoint.sip_username,
                                  push_type='NotifyGenericTextMessage',
                                  message=vm_push_msg)
                if vm_transcribe_enabled is False:
                    webhooker.trigger_generic_webhook('mobile_end_call', lead.id)
                    contact = Contact.query.filter(Contact.id == lead.contact_id).first()
                    if contact and agent and contact.agent_id != agent.id:
                        contact.agent_id = agent.id
                        contact.agent_assigned = agent.full_name
                        db.session.commit()
                        send_agent_push_notification(contact)
            elif lead.status == 'completed' and call_transcribe_enabled is False:
                webhooker.trigger_generic_webhook('mobile_end_call', lead.id)
                contact = Contact.query.filter(Contact.id == lead.contact_id).first()
                if contact and agent and contact.agent_id != agent.id:
                    contact.agent_id = agent.id
                    contact.agent_assigned = agent.full_name
                    db.session.commit()
                    send_agent_push_notification(contact)

    except BandwidthException:
        log.error(traceback.format_exc())
    return ''


@mobile_call_inbound.route('/mobile/inbound/callTranscribe/', methods=['GET', 'POST'])
@csrf.exempt
def mobile_inbound_call_transcribe():
    # This function is a call back url for SIP incoming phone calls.

    # Get the params of the Request
    args = request.json or request.args
    recording_id = args.get('recordingId')
    lead = Lead.query.filter(Lead.recording_id == recording_id).first()
    lead.transcription_text = args.get('text')
    db.session.commit()
    webhooker.trigger_generic_webhook('mobile_end_call', lead.id)
    agent = Agent.query.filter(Agent.id == lead.agent_id).first()
    contact = Contact.query.filter(Contact.id == lead.contact_id).first()
    if contact and agent and contact.agent_id != agent.id:
        contact.agent_id = agent.id
        contact.agent_assigned = agent.full_name
        db.session.commit()
        from buyercall.blueprints.mobile.utils import send_agent_push_notification
        send_agent_push_notification(contact)
    log.info('The transcribe callback info is: {}'.format(args))

    return ''


@mobile_call_inbound.route('/mobile/inbound/callTransfer/<int:inbound_id>', methods=['GET', 'POST'])
@csrf.exempt
def mobile_inbound_call_transfer(inbound_id):
    # This function is a call back url for SIP incoming phone calls.

    # Retrieve mobile number configuration
    mobile_pn = Phone.query.filter(Phone.id == inbound_id).first()
    # import partnership information to get partnership id
    from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == mobile_pn.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    # Initiate Bandwidth API
    client = bw_client(partner.id)

    voicemail_enabled = mobile_pn.routing_config['voicemail']

    # Get the params of the Request
    args = request.json or request.args
    log.info('The transfer callback info is: {}'.format(args))
    call_id = args.get('callId')
    call_hangup_cause = args.get('cause')
    original_call_id = args.get('tag')
    if args.get('callState') == 'completed':
        final_call = client.calls.info(original_call_id)
        # Check to see if a lead exist already
        lead = Lead.query.filter(Lead.call_sid == original_call_id).first()
        # Get the sip account info
        sip_endpoint = Endpoint.query.filter(Endpoint.inbound_id == lead.inbound_id).first()
        log.info('The call id is {}'.format(call_id))
        call_duration = final_call.get('chargeableDuration')
        from .tasks import mobile_lead_update
        mobile_lead_update(lead, sip_endpoint.agent_id, call_duration, call_hangup_cause)
        if voicemail_enabled is False or call_hangup_cause in ['ORIGINATOR_CANCEL', 'USER_BUSY', 'CALL_REJECTED']:
            webhooker.trigger_generic_webhook('mobile_end_call', lead.id)
            agent = Agent.query.filter(Agent.id == lead.agent_id).first()
            contact = Contact.query.filter(Contact.id == lead.contact_id).first()
            if contact and agent and contact.agent_id != agent.id:
                contact.agent_id = agent.id
                contact.agent_assigned = agent.full_name
                db.session.commit()
                from buyercall.blueprints.mobile.utils import send_agent_push_notification
                send_agent_push_notification(contact)
    else:
        log.info('The call; {} is active'.format(call_id))

    return ''


def lead_mark_finished(lead_id, new_state, response_time_seconds=None, duration=None,
                       end_time=None, cause=None, cause_description=None, tag=None, agent=None):
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead_id)
    lead = Lead.query.options(load_only("id", "status")).filter(
        Lead.id == lead_id
    ).first()

    if storage.call_cause:
        cause = storage.call_cause

    if storage.cause_description:
        cause_description = storage.cause_description

    log.info('THE RESPONSE TIME: {}'.format(response_time_seconds))

    if new_state == State.MISSED:
        lead.status = 'missed'
        lead.duration = 60
        lead.endtime = end_time
        lead.missed_call_cause = cause
        lead.cause_description = cause_description
    elif new_state == State.CAPTURED:
        lead.status = 'completed'
        lead.response_time_seconds = response_time_seconds
        lead.duration = duration
        lead.endtime = end_time
        lead.missed_call_cause = 'completed'
        if cause_description:
            lead.cause_description = cause_description
    elif new_state == State.BLOCKED:
        lead.status = 'blocked'
        lead.response_time_seconds = response_time_seconds
        lead.duration = duration
        lead.endtime = end_time
        lead.missed_call_cause = cause
        lead.cause_description = cause_description
    elif new_state == State.ERROR:
        lead.status = 'error'
        lead.response_time_seconds = response_time_seconds
        lead.duration = duration
        lead.endtime = end_time
        lead.missed_call_cause = cause
        lead.cause_description = cause_description
    lead.call_count += 1
    db.session.commit()
    # Check to see if recording is turned on. If so then we will send the web hook if the recording call back
    recording = storage.routing_config.get('recordCalls', '')
    voicemail = storage.routing_config.get('voicemail', '')
    log.info('THE TAG IS: {}'.format(tag))
    log.info('THE LEAD STATUS IS: {}'.format(lead.status))
    from ..phonenumbers.bw_operational_tasks import delay_webhook_trigger
    if lead.status == 'completed' and not recording:
        print('a')
        delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
    elif lead.status == 'completed' and tag not in ['start-recording']:
        print('ab')
        delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
    elif lead.status == 'missed' and not voicemail:
        print('abc')
        delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
    elif lead.status == 'missed' and tag in ['pre-record', 'transfer-initiated', 'pre-transfer']:
        print('abcd')
        delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
    elif lead.status == 'error':
        print('abcde')
        delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
    elif lead.status == 'blocked':
        print('abcdef')
        delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
    contact = Contact.query.filter(Contact.id == lead.contact_id).first()
    if contact and agent and contact.agent_id != agent.id:
        contact.agent_id = agent.id
        contact.agent_assigned = agent.full_name
        db.session.commit()
    # Send push notifications after call
    from buyercall.blueprints.mobile.utils import send_agent_push_notification
    send_agent_push_notification(contact)
    return ''


def missed_call(storage, lead_id, inbound_id):
    # Declare the bandwidth response xml tag. This will be used for the xml responses
    bxml = Response()
    storage.state = State.MISSED
    lead = Lead.query.join(Lead.inbound).filter(Lead.id == lead_id).first()
    lead.status = 'missed'
    db.session.commit()
    if storage.routing_config.get('voicemail', ''):
        if storage.routing_config.get('voicemailMessageType', '') == 'audio':
            vm_audio = Audio.query \
                .filter(and_(Audio.whisper_message_type == 'voicemailMessage',
                             Audio.inbound_id == inbound_id))\
                .order_by(Audio.id.desc()).first()
            if vm_audio:
                vm_audio_link = vm_audio.audio_url
                bxml.tag('pre-record')
                bxml.play_audio(vm_audio_link)
                bxml.custom_pause('1')
        else:
            vm_greet = storage.routing_config.get('voicemailMessage', '')
            if storage.routing_config.get('voicemail') == 'es':
                bxml.tag('pre-record')
                bxml.custom_pause('1')
                bxml.say(vm_greet, 'female', 'es_MX', 'esperanza')
                bxml.custom_pause('1')
            else:
                bxml.tag('pre-record')
                bxml.custom_pause('2')
                bxml.say(vm_greet)
                bxml.custom_pause('1')

        record_call_back_url = url_for(
            'bw_operational.operational_call_record_bw_webhook', lead_id=lead.id,
            _external=True, _scheme='https')

        if storage.routing_config.get('transcribeVoiceMail', ''):
            transcribe = "true"
            transcribe_call_back_url = url_for(
                'bw_operational.operational_call_transcribe_bw_webhook', lead_id=lead.id,
                _external=True, _scheme='https')
        else:
            transcribe = "false"
            transcribe_call_back_url = ""

        bxml.play_audio(app.config['BEEP_SOUND'])
        bxml.tag('post-record')
        bxml.record(recording_available_url=record_call_back_url, transcribe=transcribe,
                    transcribe_available_url=transcribe_call_back_url, tag='voicemail')
    else:
        bxml.hangup()
    return create_xml_response(bxml)