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/phonenumbers/bw_operational.py
from __future__ import print_function
import traceback
import logging
from dateutil import parser
import pytz
from datetime import datetime
from xml.dom.minidom import parseString
from flask import (
    Blueprint,
    request,
    redirect,
    jsonify,
    make_response,
    current_app as app,
    url_for
)
import redis
from sqlalchemy import and_
from sqlalchemy.orm import load_only
from buyercall.blueprints.filters import format_phone_number
from buyercall.extensions import csrf, db
from buyercall.lib.util_twilio import (
    CallStorage,
    InboundCallState as State
)
import buyercall.lib.bandwidth as bandwidth
from buyercall.lib.util_bandwidth import bw_client
from buyercall.lib.bandwidth import (
    BandwidthException
)
from buyercall.lib.bandwidth_bxml import create_xml_response, Response
from buyercall.lib.util_webhooks import WebhookUtil
from .models import (
    Phone, Audio
)
from ..leads.models import Lead
from ..block_numbers.models import Block
from ..partnership.models import PartnershipAccount, Partnership
from ..mobile.models import Endpoint
from ..agents.models import Agent
from ..user.models import User
from .routing import (
    get_routing_agents,
    get_agent_number
)

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

bw_operational = Blueprint(
    'bw_operational', __name__, template_folder='templates'
)

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


# Testing Bandwidth Migration
@bw_operational.route('/bw/voice', methods=['GET', 'POST'])
@csrf.exempt
def voice_call():
    """ Entry point for the incoming phone call. 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:
        # Fetch the request data. This includes message data for incoming sms/mms
        args = request.json or request.args
        log.info('The initiated callback args: {}'.format(args))
        return handle_incoming_call(args)
    else:
        print('Authentication failed')
        status_code = 401
        message = jsonify(message='Authentication failed.')
        response = make_response(message, status_code)
        return response


def handle_incoming_call(args):
    """
    Handle incoming voice call from Bandwidth API V2
    :param args:
    :return:
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    # Declare BXML response
    bxml = Response()

    # Determine some request data
    event_type = args.get('eventType')
    tag = args.get('tag', '')
    call_id = args.get('callId', '')
    caller_number = args.get('from', '')
    bc_number = args.get('to', '')
    cause = args.get('cause', '')
    req_start_time = args.get('startTime', '')
    if req_start_time:
        start_time = req_start_time
    else:
        start_time = args.get('enqueuedTime', '')

    # Find the phone number/inbound id being called in the BuyerCall database
    phone_number = Phone.query.filter(and_(Phone.phonenumber == bc_number,
                                           Phone.is_deactivated.is_(False))).first()

    # start save inbound-id to resource field of request-log data
    try:
        partnership_account_id = phone_number.partnership_account_id
        partnership_account = PartnershipAccount.query.get(partnership_account_id) if partnership_account_id else None
        partnership_id = partnership_account.partnership_id
        partnership = Partnership.query.get(partnership_id) if partnership_id else None
        from buyercall.blueprints.sysadmin.models import RequestLog
        request_id = request.environ.get('HTTP_X_REQUEST_ID')
        # updating the request log with the inbound id
        update_data = {
            "partnership_id": partnership_id if partnership_id else None,
            'partnership_name': partnership.name if partnership else None,
            "partnership_account_id": partnership_account_id if partnership_account_id else None,
            'partnership_account_name': partnership_account.name if partnership_account else None,
            "resource": phone_number.id if phone_number else None,
            'user_id': None
            }
        RequestLog().update_record(request_id, update_data)
        # RequestLog().update_record(
        #     request_id, {"resource": phone_number.id if phone_number else None})
    except Exception as e:
        print(f"The exception in saving inbound-id in handle_incoming_call is {e}")
    # end save inbound-id to resource field of request-log data

    if not phone_number:
        bxml.hangup()
        return create_xml_response(bxml)

    # Create lead and contact
    from .bw_operational_tasks import bw_operational_lead_create
    lead_id = bw_operational_lead_create(
        phone_number.id, call_id, caller_number,
        start_time=start_time)

    # Send the first webhook saying that an incoming call started
    webhooker.trigger_generic_webhook('operational_start_call', lead_id)
    # Activate and set redis storage and add lead
    storage = CallStorage(redis_db, lead_id)
    # Set the storage retry value to false. If it's a retry it will be set to true based on routing value
    storage.is_call_retry = False
    # Check if the incoming call is labeled as a blocked number
    is_number_blocked = Block.blocked(phone_number.id, caller_number)
    if is_number_blocked:
        storage.state = State.BLOCKED
        storage.cause_description = 'Incoming phone number on blocked list.'
        bxml.hangup()
        return create_xml_response(bxml)
    # Set a variable containing the phone number full routing not just default agent routing info
    routing_config = phone_number.routing_config
    # Check what type of routing is saved for the Number. If its default its normal sequence and simultaneously
    # calling Whereas if its digits it means its IVR or phone tree routing where the user decide who to talk
    # to based on a digit menu
    routing_type = routing_config.get('routingType', '')
    log.info('THE ROUTING TYPE IS: {}'.format(routing_type))
    if routing_type == 'digits':
        transfer_call = lead_operational_call_digits_routing(lead_id)
        return transfer_call
    else:
        # If the routing is not digits (phone tree/IVR) then the routing is default and just normal
        # routing occurs
        transfer_call = lead_operational_call_default_routing(phone_number.id, routing_config, lead_id)
        return transfer_call


@bw_operational.route('/bw/voice/status', methods=['GET', 'POST'])
@csrf.exempt
def 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', ''))
        try:
            end_time = parser.parse(args.get('endTime', ''))
        except Exception as e:
            log.error(e)
            end_time = 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()
        from ..partnership.models import PartnershipAccount,Partnership
        partnership_account_id = call_lead.partnership_account_id
        partnership_account = PartnershipAccount.query.get(partnership_account_id) if partnership_account_id else None
        partnership_id = partnership_account.partnership_id

        # start save inbound-id to resource field of request-log data
        try:
            partnership = Partnership.query.get(partnership_id) if partnership_id else None
            from buyercall.blueprints.sysadmin.models import RequestLog
            request_id = request.environ.get('HTTP_X_REQUEST_ID')
            update_data = {
                "partnership_id": partnership_id if partnership_id else None,
                'partnership_name': partnership.name if partnership else None,
                "partnership_account_id": partnership_account_id if partnership_account_id else None,
                'partnership_account_name': partnership_account.name if partnership_account else None,
                "resource": call_lead.inbound_id if call_lead else None,
                'user_id': None
                }
            RequestLog().update_record(request_id, update_data)
            # updating the request log with the inbound id
            # RequestLog().update_record(
            #     request_id, {"resource": call_lead.inbound_id if call_lead else None})
        except Exception as e:
            print(f"The exception in saving inbound-id in handle_incoming_call is {e}")
        # end save inbound-id to resource field of request-log data

        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()
                # Label call as complete
                lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time,
                                   cause, cause_description, tag)
            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


@bw_operational.route('/bw/voice/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 voice_status_callback(args)
    else:
        print('Authentication failed')
        status_code = 401
        message = jsonify(message='Authentication failed.')
        response = make_response(message, status_code)
        return response


def lead_operational_call(args):
    """ Entry point for a operational bw phone number on BW Voice V2.
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])
    # Import hang up calls code
    from .bw_tasks import bw_hangup_calls
    # Determine some request data
    event_type = args.get('eventType')
    tag = args.get('tag', '')
    call_id = args.get('callId', '')
    caller_number = args.get('from', '')
    bc_number = args.get('to', '')
    cause = args.get('cause', '')
    req_start_time = args.get('startTime', '')
    if req_start_time:
        start_time = req_start_time
    else:
        start_time = args.get('enqueuedTime', '')

    # Find the phone number/inbound id being called in the BuyerCall database
    phone_number = Phone.query.filter(and_(Phone.phonenumber == bc_number,
                                           Phone.is_deactivated.is_(False))).first()
    if not phone_number:
        bw_hangup_calls.delay(call_id)
        log.info('Unable to find the phone number; {} in BuyerCall DB'.format(bc_number))
        return ''

    is_number_blocked = Block.blocked(phone_number.id, caller_number)
    if is_number_blocked:
        bw_hangup_calls.delay(call_id)
        log.info('This call was not processed because phone number: {} was blocked'.format(caller_number))
        return ''

    if event_type == 'answer':
        if phone_number:
            # Retrieve the Partnership Account details for the phone number
            partnership_account = PartnershipAccount.query \
                .filter(PartnershipAccount.id == phone_number.partnership_account_id).first()
            # Set a variable containing the phone number full routing not just default agent routing info
            routing_config = phone_number.routing_config
        else:
            log.error('A BuyerCall phone number was not found in the db for inbound id: {}'.format(phone_number.id))
            return

        # Create lead and contact
        from .bw_operational_tasks import bw_operational_lead_create
        lead_id = bw_operational_lead_create(
            phone_number.id, call_id, caller_number,
            start_time=start_time)

        # Send the first webhook saying that a incoming call started
        webhooker.trigger_generic_webhook('operational_start_call', lead_id)
        # Set the storage retry value to false. If it's a retry it will be set to true below
        storage = CallStorage(redis_db, lead_id)
        storage.is_call_retry = False
        # Check what type of routing is saved for the Number. If its default its normal sequence and simultaneously
        # calling Whereas if its digits it means its IVR or phone tree routing where the user decide who to talk
        # to based on a digit menu
        routing_type = phone_number.routing_config.get('routingType', '')
        log.info('THE ROUTING TYPE IS: {}'.format(routing_type))
        if routing_type == 'digits':
            log.info('An incoming call for phone number id: {}, has been received'.format(phone_number.id))
            transfer_call = lead_operational_call_digits_routing(lead_id)
            return transfer_call
        else:
            # If the routing is not digits (phone tree/IVR) then the routing is default and just normal
            # routing occurs
            log.info('An incoming call for phone number id: {}, has been received'.format(phone_number.id))
            transfer_call = lead_operational_call_default_routing(phone_number.id, routing_config, lead_id)
            return transfer_call

    if event_type == 'transferComplete':
        log.info('The transfer event is complete, meaning the call has ended after a successful answer by agent')
        call_lead = Lead.query.filter(Lead.call_sid == tag).first()
        storage = CallStorage(redis_db, call_lead.id)
        storage.state = State.CAPTURED
        start_time = datetime.strptime(
            storage.start_time, '%Y-%m-%dT%H:%M:%SZ'
        )
        duration, response_time_seconds, end_time = None, None, None

        if storage.connect_time:
            connect_time = datetime.strptime(
                storage.connect_time, '%Y-%m-%dT%H:%M:%SZ'
            )
            response_time_seconds = (connect_time - start_time).total_seconds()

        end_time = datetime.strptime(
            args.get('time'), '%Y-%m-%dT%H:%M:%SZ'
        )
        duration = (end_time - start_time).total_seconds()

        lead_mark_finished(call_lead.id, storage.state, response_time_seconds, duration, end_time)

    if event_type == 'hangup' and cause != 'NORMAL_CLEARING':
        call_lead = Lead.query.filter(Lead.call_sid == call_id).first()
        storage = CallStorage(redis_db, call_lead.id)
        storage.state = State.MISSED
        log.info('The call lead is: {}'.format(call_lead.id))
        lead_mark_finished(call_lead.id, storage.state, cause=cause)
    return ''


def lead_operational_call_default_routing(inbound_id, routing_config, lead_id):
    """ This function is used when an incoming call comes in and the routing on the phone number is set to default.
    The function will then determine how to route the calls for default routing
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    default_routing = routing_config['defaultRouting']
    # Get the list of available agents
    agents = get_routing_agents(default_routing, expand_groups=True)
    # Get the call order from the phone number routing_config
    call_order = default_routing.get('callOrder', 'sequence')
    # Set the storage db model in redis
    storage = CallStorage(redis_db, lead_id)
    if call_order == 'shuffle':
        import random
        random.shuffle(agents)
        call_order = 'sequence'
    storage.call_order = call_order
    storage.routing = default_routing
    if call_order == 'sequence':
        agent_ids = [agent.id for agent in agents]
        log.info('the agent ids {}'.format(agent_ids))
        storage.set_agents_to_call(agent_ids)
        # We set a storage variable to save the first agent id. This is used to play greeting messages to the
        # Caller if its the first agent in line being called. We don't want to play the greeting message again
        # if we route the call to the second agent.
        if agent_ids:
            storage.first_call_agent_id = agent_ids[0]
        else:
            storage.cause_description = 'No available agents.'
            return missed_call(storage, lead_id, inbound_id)
        return lead_operational_call_sequence_continue(inbound_id, lead_id)
    if call_order == 'simultaneous':
        agent_ids = [agent.id for agent in agents]
        log.info('the agent ids {}'.format(agent_ids))
        if agent_ids:
            agent_ids = [agent_ids[0]]
            storage.set_agents_to_call(agent_ids)
        else:
            storage.cause_description = 'No available agents.'
            return missed_call(storage, lead_id, inbound_id)
        return lead_operational_call_simultaneous_continue(inbound_id, lead_id)


@bw_operational.route('/api/bw/operational/sequence/continue/<int:inbound_id>/<int:lead_id>',
                      methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_call_sequence_continue(inbound_id, lead_id):
    # 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)

    lead = Lead.query.join(Lead.inbound).filter(Lead.id == lead_id).first()

    agent_id = storage.next_agent_to_call()
    log.info('The next agent id to call: {}'.format(agent_id))
    if not agent_id:
        from .bw_operational_tasks import retry_once_more
        retry_agents = retry_once_more(lead_id)
        if retry_agents:
            # Set this storage value to True because a retry is happening
            storage.is_call_retry = True
            if storage.routing_config.get('routingType') == 'digits':
                return lead_operational_call_digits_routing(lead_id)
            else:
                return lead_operational_call_default_routing(inbound_id, storage.routing_config, lead_id)
        else:
            storage.cause_description = 'No available agents.'
            return missed_call(storage, lead_id, inbound_id)

    return sequence_calling(inbound_id, lead, agent_id)


def sequence_calling(inbound_id, lead, agent_id):
    # 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)

    # 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', '')
    client_voicemail = storage.routing_config.get('clientVoicemail', '')
    caller_id_info = storage.routing_config.get('callerIdPn', False)

    if not agent_id:
        agent_id = storage.agent_id

    agent = Agent.query.filter(Agent.id == agent_id).first()
    if agent.is_group:
        agents = agent.agents
        storage.is_group = True
        agent_numbers = []
        for agent in agents:
            agent_number, extension = get_agent_number(agent, storage.routing)
            agent_numbers.append({"agent_id": agent.id, "agent_number": agent_number})
    else:
        agent_number, extension = get_agent_number(agent, storage.routing)
        agent_numbers = [{"agent_id": agent.id, "agent_number": agent_number}]
    # Set the list of agent id and numbers that is being called in redis storage for later use
    storage.agent_list = agent_numbers

    # This is the web-hook that will be called if a transfer is answered
    transfer_answer_webhook_url = url_for(
        'bw_operational.lead_operational_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(
        'bw_operational.lead_operational_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(
        'bw_operational.lead_operational_transfer_disconnect_bw_webhook', lead_id=lead.id,
        _external=True, _scheme='https')
    log.info('THE INBOUND ID IS: {}'.format(inbound_id))
    # This is the web-hook that will be called if a transfer is completed
    transfer_complete_webhook_url = url_for(
        'bw_operational.lead_operational_transfer_complete_bw_webhook', inbound_id=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(
        'bw_operational.lead_operational_transfer_complete_fallback_bw_webhook', inbound_id=inbound_id, lead_id=lead.id,
        _external=True, _scheme='https')

    # This is the first sequence call/transfer. If the agent doesn't pick up the call
    # will redirect to the next agent
    redirect_sequence_url = url_for(
        'bw_operational.lead_operational_call_sequence_continue', inbound_id=inbound_id, lead_id=lead.id,
        _external=True, _scheme='https')

    # Declare the bandwidth response xml tag. This will be used for the xml responses
    bxml = Response()

    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 and storage.first_call_agent_id == agent_id and not storage.is_call_retry:
            if greeting_type == 'audio':
                greeting_audio = Audio.query \
                    .filter(and_(Audio.whisper_message_type == 'whisperMessage',
                                 Audio.inbound_id == 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)
        if storage.is_call_retry:
            bxml.custom_pause('4')
        if client_voicemail:
            call_time_out = "50"
        else:
            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
        if caller_id_info:
            caller_id = lead.my_phone
        else:
            caller_id = lead.phonenumber
        # Perform the transfer to the agent number
        transfer = bxml.transfer(caller_id, call_time_out, transfer_complete_webhook_url,
                                 transfer_complete_fallback_webhook_url, tag='transfer-initiated')
        for number in agent_numbers:
            # Lookup to see if the agent number is associated with a buyercall sip endpoint
            buyercall_sip_lookup = Phone.mobile_sip_uri(number["agent_number"])
            if buyercall_sip_lookup:
                agent_call_number = buyercall_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(number["agent_number"])
                transfer.phone_number(agent_call_number, transfer_answer_webhook_url,
                                      transfer_answer_fallback_webhook_url,
                                      transfer_disconnect_webhook_url)

        log.info('THE AGENT THAT TOOK THE CALL: {}'.format(storage.agent_id))
        storage.push_agent_call(storage.agent_id, storage.call_sid)
        return create_xml_response(bxml)
    except BandwidthException:
        log.error(traceback.format_exc())
        log.error('Error calling agent {}...'.format(agent.id))
        return ''


@bw_operational.route('/api/bw/operational/simultaneous/continue/<int:inbound_id>/<int:lead_id>',
                      methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_call_simultaneous_continue(inbound_id, lead_id):
    # 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)

    agents = get_routing_agents(storage.routing, expand_groups=True)

    lead = Lead.query.join(Lead.inbound).filter(Lead.id == lead_id).first()

    agent_id = storage.next_agent_to_call()
    log.info('The next agent id to call: {}'.format(agent_id))
    if not agent_id:
        from buyercall.blueprints.phonenumbers.bw_operational_tasks import retry_once_more
        retry_agents = retry_once_more(lead_id)
        if retry_agents:
            # Set this storage value to True because a retry is happening
            storage.is_call_retry = True
            if storage.routing_config.get('routingType') == 'digits':
                return lead_operational_call_digits_routing(lead_id)
            else:
                return lead_operational_call_default_routing(inbound_id, storage.routing_config, lead_id)
        else:
            storage.cause_description = 'No available agents.'
            return missed_call(storage, lead_id, inbound_id)

    return simultaneous_calling(inbound_id, lead, agents)


def simultaneous_calling(inbound_id, lead, agents):
    # 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)

    # 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')
    record_call = storage.routing_config.get('recordCalls', '')
    client_voicemail = storage.routing_config.get('clientVoicemail', '')
    transcribe_call = storage.routing_config.get('transcribeAnsweredCall', '')
    hidden_information = storage.routing_config.get('hiddenInformation', '')
    caller_id_info = storage.routing_config.get('callerIdPn', False)

    agent_numbers = []
    for agent in agents:
        agent_number, extension = get_agent_number(agent, storage.routing)
        agent_numbers.append({"agent_id": agent.id, "agent_number": agent_number})
    # Set the list of agent id and numbers that is being called in redis storage for later use
    storage.agent_list = agent_numbers
    # This is the web-hook that will be called if a transfer is answered
    transfer_answer_webhook_url = url_for(
        'bw_operational.lead_operational_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(
        'bw_operational.lead_operational_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(
        'bw_operational.lead_operational_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(
        'bw_operational.lead_operational_transfer_complete_bw_webhook', inbound_id=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(
        'bw_operational.lead_operational_transfer_complete_fallback_bw_webhook', inbound_id=inbound_id, lead_id=lead.id,
        _external=True, _scheme='https')

    # Declare the bandwidth response xml tag. This will be used for the xml responses
    bxml = Response()

    try:
        # If greeting is enabled and not a retry call we play a greeting message to the caller
        if greeting_enabled and not storage.is_call_retry:
            if greeting_type == 'audio':
                greeting_audio = Audio.query \
                    .filter(and_(Audio.whisper_message_type == 'whisperMessage',
                                 Audio.inbound_id == 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)
        if storage.is_call_retry:
            bxml.custom_pause('4')
        log.info(f"storage call sid id before saving  2: {storage.call_sid} {type(storage.call_sid)}")
        if client_voicemail:
            call_time_out = "50"
        else:
            call_time_out = TRANSFER_CALL_TIMEOUT
        #transfer = xml_response.transfer(request_url=transfer_call_webhook_url,
        #                                 callTimeout=call_time_out, tag=storage.call_sid)
        if caller_id_info:
            caller_id = lead.my_phone
        else:
            caller_id = lead.phonenumber
        # Perform the transfer to the agent number
        transfer = bxml.transfer(caller_id, call_time_out, transfer_complete_webhook_url,
                                 transfer_complete_fallback_webhook_url, tag='transfer-initiated')
        for number in agent_numbers:
            # Lookup to see if the agent number is associated with a buyercall sip endpoint
            buyercall_sip_lookup = Phone.mobile_sip_uri(number["agent_number"])
            if buyercall_sip_lookup:
                agent_call_number = buyercall_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(number["agent_number"])
                transfer.phone_number(agent_call_number, transfer_answer_webhook_url,
                                      transfer_answer_fallback_webhook_url, transfer_disconnect_webhook_url)

        agent_ids = [agent.id for agent in agents]
        agent_id = agent_ids[0]
        storage.push_agent_call(agent_id, storage.call_sid)
        # Return and send the BXML code transferring the call to Bandwidth
        return create_xml_response(bxml)
    except BandwidthException:
        log.error(traceback.format_exc())
        log.error('Error calling agent simultaneously for lead id: {}'.format(lead.id))
        return ''


def lead_operational_call_digits_routing(lead_id):
    """ This function is used when a incoming call comes in and the routing on the phone number is set to digits.
    The function will then determine how to route the calls for digits where a phone tree/IVR  is used. This means
    The user is presented with a phone menu and routed to agents based on the digits entered in phone. for example
    Press 1 to speak to A and Press 2 to speak to B
    """
    # 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)
    # Declare the bandwidth response xml tag. This will be used for the xml responses
    bxml = Response()
    digit_routing = storage.routing_config.get('digitRoutings', '')
    digit_greeting_type = storage.routing_config.get('digitWhisperMessageType', '')
    log.info('The digit greeting type is: {}'.format(digit_greeting_type))
    log.info('The digit routing is: {}'.format(digit_routing))
    if not storage.is_call_retry:
        # This is the gathering digit call web-hook. Every time there's a new event with the gather verb
        # this endpoint will be hit with some call-back information
        digits_webhook_url = url_for(
            'bw_operational.lead_operational_phone_call_digits_bw_webhook', lead_id=lead_id,
            _external=True, _scheme='https')
        digits_fallback_webhook_url = url_for(
            'bw_operational.lead_operational_phone_call_fallback_digits_bw_webhook', lead_id=lead_id,
            _external=True, _scheme='https')
        digit_gather = bxml.gather(digits_webhook_url, digits_fallback_webhook_url, tag='gather-digits',
                                   first_digit_timeout='7', max_digits='1', repeat_count='3')
        if digit_greeting_type == 'audio':
            lead = Lead.query.filter(Lead.id == lead_id).first()
            greeting_audio = Audio.query.filter(and_(Audio.whisper_message_type == 'digitWhisperMessage',
                                                     Audio.inbound_id == lead.inbound_id)).first()
            if greeting_audio:
                greeting_url = greeting_audio.audio_url
                digit_gather.play_audio(greeting_url)
        else:
            digit_greeting = storage.routing_config.get('digitWhisperMessage', '')
            digit_gather.say(digit_greeting)
        return create_xml_response(bxml)

    return lead_operational_phone_call_digits_bw_webhook(lead_id)


@bw_operational.route('/api/bw/operational_phone_call_digits/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_phone_call_digits_bw_webhook(lead_id, *args):
    # 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
    log.info('The operation call gather digits call-back/web-hook response: {}'.format(args))
    if storage.is_call_retry:
        pressed_digits = storage.pressed_digit
    else:
        pressed_digits = args.get('digits', '')
        storage.pressed_digit = pressed_digits
    # Return the digit routings from the phone number routing config
    digit_routings = storage.routing_config.get('digitRoutings', '')
    if digit_routings and pressed_digits:
        for routing in digit_routings:
            dial_digit = routing.get('dialDigit', '')
            if dial_digit == pressed_digits:
                # Get the lead information to get the inbound id
                lead = Lead.query.filter(Lead.id == lead_id).first()
                agents = get_routing_agents(routing, expand_groups=False)
                # Get the call order from the phone number routing_config
                call_order = routing.get('callOrder', 'sequence')
                # Set the storage db model in redis
                storage = CallStorage(redis_db, lead_id)
                if call_order == 'shuffle':
                    import random
                    log.debug('Shuffling agent list...')
                    random.shuffle(agents)
                    call_order = 'sequence'
                storage.call_order = call_order
                storage.routing = routing
                if call_order == 'sequence':
                    agent_ids = [agent.id for agent in agents]
                    log.info('the agent ids {}'.format(agent_ids))
                    storage.set_agents_to_call(agent_ids)
                    # We set a storage variable to save the first agent id. This is used to play greeting messages to
                    # the Caller if its the first agent in line being called. We don't want to play the greeting
                    # message again. if we route the call to the second agent.
                    if agent_ids:
                        storage.first_call_agent_id = agent_ids[0]
                    else:
                        log.info('No agent available for inbound id: {} at this time'.format(lead.inbound_id))
                        missed_call(storage, lead_id, lead.inbound_id)
                    return lead_operational_call_sequence_continue(lead.inbound_id, lead_id)
                if call_order == 'simultaneous':
                    # Get the list of available agents
                    agents = get_routing_agents(routing, expand_groups=True)
                    agent_ids = [agent.id for agent in agents]
                    log.info('the agent ids {}'.format(agent_ids))
                    if agent_ids:
                        agent_ids = [agent_ids[0]]
                        storage.set_agents_to_call(agent_ids)
                    else:
                        log.info('No agent available for inbound id: {} at this time'.format(lead.inbound_id))
                        missed_call(storage, lead_id, lead.inbound_id)
                    return lead_operational_call_simultaneous_continue(lead.inbound_id, lead_id)
    elif digit_routings and not pressed_digits:
        # Declare the bandwidth response xml tag. This will be used for the xml responses
        bxml = Response()
        # This is the gathering digit call web-hook. Every time there's a new event with the gather verb
        # this endpoint will be hit with some call-back information
        digits_webhook_url = url_for(
            'bw_operational.lead_operational_phone_call_digits_bw_webhook', lead_id=lead_id,
            _external=True, _scheme='https')
        digits_fallback_webhook_url = url_for(
            'bw_operational.lead_operational_phone_call_fallback_digits_bw_webhook', lead_id=lead_id,
            _external=True, _scheme='https')
        digit_gather = bxml.gather(digits_webhook_url, digits_fallback_webhook_url, tag='gather-digits',
                                   first_digit_timeout='7', max_digits='1', repeat_count='3')
        no_digit_whisper_message = storage.routing_config.get('noDigitWhisperMessageType', '')
        if no_digit_whisper_message == 'audio':
            lead = Lead.query.filter(Lead.id == lead_id).first()
            no_digit_audio = Audio.query.filter(and_(Audio.whisper_message_type == 'noDigitWhisperMessage',
                                                     Audio.inbound_id == lead.inbound_id)).first()
            if no_digit_audio:
                greeting_url = no_digit_audio.audio_url
                digit_gather.play_audio(greeting_url)
        else:
            no_digit_text_whisper = storage.routing_config.get('noDigitWhisperMessage', '')
            digit_gather.say(no_digit_text_whisper)
        return create_xml_response(bxml)
    else:
        log.error('No digit routing was found for lead id: {}'.format(lead_id))
    return ''


@bw_operational.route('/api/bw/operational_phone_call_fallback_digits/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_phone_call_fallback_digits_bw_webhook(lead_id):
    """ This is the fallback callback url for the gather digits callback event
        https://dev.bandwidth.com/voice/bxml/callbacks/gather.html
    """
    # Get the params of the Request
    args = request.json or request.args
    log.info('The call gather digits fallback callback response: {}'.format(args))
    return lead_operational_phone_call_digits_bw_webhook(lead_id, args)


@bw_operational.route('/api/bw/operational_transfer_answer/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_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', ''))

    from .bw_operational_tasks import bw_operational_update_lead
    bw_operational_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')
    # 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)
        bxml.say('....')
        bxml.play_audio(app.config['BEEP_SOUND'])
    if record_call:
        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')
    return create_xml_response(bxml)


@bw_operational.route('/api/bw/operational_transfer_answer_fallback/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_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 lead_operational_transfer_answer_bw_webhook(lead_id, args)


@bw_operational.route('/api/bw/operational_transfer_disconnect/webhook/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_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 == 'cancel':
    #    storage.state = State.MISSED
    #    storage.cause_description = error_message
    else:
        storage.state = State.MISSED
        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 ''


@bw_operational.route('/api/bw/operational_transfer_complete/webhook/<int:inbound_id>/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_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']:
        # This is the first sequence call/transfer. If the agent doesn't pick up the call
        if storage.call_order == 'simultaneous':
            # will redirect to the next agent
            redirect_sequence_url = url_for(
                'bw_operational.lead_operational_call_simultaneous_continue', inbound_id=inbound_id, lead_id=lead_id,
                _external=True, _scheme='https')
        else:
            # will redirect to the next agent
            redirect_sequence_url = url_for(
                'bw_operational.lead_operational_call_sequence_continue', inbound_id=inbound_id, lead_id=lead_id,
                _external=True, _scheme='https')
        bxml.redirect(redirect_sequence_url, redirect_sequence_url, tag='transfer-redirect')

    else:
        bxml.tag('transfer-complete')
    return create_xml_response(bxml)


@bw_operational.route('/api/bw/operational_transfer_complete_fallback/webhook/<int:inbound_id>/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_operational_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 lead_operational_transfer_complete_bw_webhook(lead_id, inbound_id, args)


@bw_operational.route('/api/bw/operational_phone_call/call_recording/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def operational_call_record_bw_webhook(lead_id):
    # This function is a call back url for operational incoming phone calls.
    # Whenever a call is complete and record is enabled this call back url will be called.

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

    call_id = args.get('callId', '')
    recording_id = args.get('recordingId', '')
    lead = Lead.query.filter(Lead.id == lead_id).first()
    lead.recording_id = recording_id
    db.session.commit()

    from .bw_operational_tasks import bw_upload_recording
    bw_upload_recording.delay(lead_id, recording_id=recording_id, call_id=call_id)

    inbound = Phone.query.filter(Phone.id == lead.inbound_id).first()
    if inbound:
        # Send push notification to sip mobile user
        if inbound.type == 'mobile' and lead.status == 'missed':
            from buyercall.blueprints.mobile.tasks import send_vm_push_notification
            send_vm_push_notification.delay(lead.id)
    return ''


@bw_operational.route('/api/bw/operational_phone_call/call_transcribe/<int:lead_id>', methods=['GET', 'POST'])
@csrf.exempt
def operational_call_transcribe_bw_webhook(lead_id):
    # This function is a call back url for operational phone calls.
    # Get the params of the Request
    args = request.json or request.args
    recording_id = args.get('recordingId', '')
    call_id = args.get('callId', '')
    lead = Lead.query.filter(Lead.recording_id == recording_id).first()
    partner_account = PartnershipAccount.query.options(load_only('id', 'partnership_id')) \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    inbound = Phone.query.filter(Phone.id == lead.inbound_id).first()

    from buyercall.lib.util_bandwidth import bw_client
    if inbound.type == 'mobile':
        client = bw_client(partner_account.partnership_id, request_type='voice', tn_type='mobile')
    else:
        client = bw_client(partner_account.partnership_id, request_type='voice')

    # Retrieve the transcription text from BW
    transcript = client.call.transcription(call_id, recording_id)
    try:
        transcript_channel_1 = transcript['transcripts'][0].get('text', '')
        transcript_channel_1_confidence = transcript['transcripts'][0].get('confidence', '')
        # Save the transcription text and confidence to the database
        lead.transcription_text = transcript_channel_1
        lead.transcription_1_confidence = transcript_channel_1_confidence
        if len(transcript['transcripts']) > 1:
            transcript_channel_2 = transcript['transcripts'][1].get('text', '')
            transcript_channel_2_confidence = transcript['transcripts'][1].get('confidence', '')
            lead.transcription_text_2 = transcript_channel_2
            lead.transcription_2_confidence = transcript_channel_2_confidence
        db.session.commit()
    except IndexError:
        log.info('No transcription information was returned for recording id: {}'.format(recording_id))

    # If this call back is hit it means there's a transcription and we only send
    # web hook after receiving this call back
    # Delay the webhook because we need to wait for the recording to be upload with celery too
    from .bw_operational_tasks import delay_webhook_trigger
    if inbound.type == 'mobile':
        delay_webhook_trigger.apply_async(args=['mobile_end_call', lead_id], countdown=15)
    else:
        delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
    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):
    # 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.cause_description:
        cause_description = storage.cause_description

    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 .bw_operational_tasks import delay_webhook_trigger
    if lead.status == 'completed' and not recording:
        print('a')
        delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
    elif lead.status == 'completed' and tag not in ['start-recording']:
        print('ab')
        delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)
    elif lead.status == 'missed' and not voicemail:
        print('abc')
        delay_webhook_trigger.apply_async(args=['operational_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=['operational_end_call', lead_id], countdown=15)
    elif lead.status in ['error', 'blocked']:
        print('abcde')
        delay_webhook_trigger.apply_async(args=['operational_end_call', lead_id], countdown=15)

    agent_list = storage.agent_list
    log.info('The agent list is: {}'.format(agent_list))
    # Send notifications after call
    from .bw_operational_tasks import send_notifications
    send_notifications.delay(lead_id)


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['voicemail']:
        if storage.routing_config['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['voicemailMessage']
            if storage.routing_config['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['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)


def to_response(r):
    """ Transform a Twiml Response to a Flask response object. """
    xml = parseString(str(r))
    result = xml.toprettyxml()
    return result