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_inbound.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.
"""
# +13127618003
from __future__ import print_function

import traceback
import logging
from datetime import datetime

from flask import (
    Blueprint,
    request,
    current_app as app,
    url_for,
    jsonify
)
from twilio.twiml.voice_response import VoiceResponse
import redis
from sqlalchemy.orm import (
    load_only,
)
import pytz

from buyercall.extensions import csrf, db
from buyercall.lib.flask_mailplus import _try_renderer_template
from buyercall.lib.util_ses_email import send_ses_email
import buyercall.lib.bandwidth as bandwidth
from buyercall.lib.util_twilio import (
    CallStorage,
    InboundCallState as State,
    bw_client,
    select_voice,
)
from buyercall.lib.util_webhooks import WebhookUtil
from .models import (
    Phone,
)
from buyercall.blueprints.leads.models import Lead
from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.user.models import User
from buyercall.blueprints.block_numbers.models import Block
from buyercall.blueprints.partnership.models import Partnership, PartnershipAccount
from .routing import (
    get_routing_agents, schedule_callback,
    get_agent_number, get_agent_text,
    hold_music_url,
)
from .twilio_inbound import send_notifications

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

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

HOURS = 3600
""" The length of an hour in seconds """

DAYS = 86400
""" The length of a day in seconds """

TWIMLET_CLASSICAL = 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.classical'  # noqa
""" The URL for the Twimlet with some nice hold music """

CALLBACK_PROMPT_DELAY = 60
""" Seconds to wait until prompting the user for callback """

DEFAULT_VOICE = 'alice'
""" The voice to use when speaking a message for the lead """

MSG_TOO_SLOW = "We're sorry, another agent has accepted this call."

MSG_DIGIT_PROMPT = 'Press any key to accept this call, or this call will \
be canceled.'

MSG_SERVICE_UNAVAILABLE = "We're sorry, our service is currently unavailable. \
An agent will call you back as soon as possible."

MSG_CANNOT_CONNECT = """We're sorry, we cannot connect you to an agent
presently. Please try again later."""

MSG_NOT_AVAILABLE = """Sorry, we are not available at the moment. We will
remember your call, and will get back to you as soon as possible."""

MSG_INVALID_DIGIT = 'You have pressed an invalid digit. Please try again.'


@bw_inbound.route('/api/bw/random')
@csrf.exempt
def random_url():
    print(request.args)
    print(request.form)
    return ''


@bw_inbound.route('/api/bw/inbound/<int:inbound_id>', methods=['GET', 'POST'])
@csrf.exempt
def lead_inbound_call(inbound_id):
    """ Entry point for the incoming lead call.
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(
        host=app.config['REDIS_CONFIG_URL'],
        port=app.config['REDIS_CONFIG_PORT'],
        decode_responses=True
    )

    args = request.json
    print(args)

    is_number_blocked = Block.blocked(inbound_id, args.get('from'))
    if is_number_blocked:
        from .bw_tasks import bw_hangup_calls
        bw_hangup_calls.delay(args.get('callId', ''))
        log.info('This call was not processed because phone number: {} was blocked'.format(args.get('from')))
        return ''

    event_type = args.get('eventType')
    call_state = args.get('callState')
    call_id = args.get('callId', '')

    if event_type == 'incomingcall':
        # Do nothing, since the call is auto-accepted
        return ''

    if event_type == 'answer':
        phone_number = args.get('from', '')
        from .bw_tasks import bw_lead_create
        # FYI: Webhook added to method below.
        bw_lead_create.delay(
            inbound_id, call_id, phone_number,
            start_time=args.get('time'),
        )
        return ''

    if call_state == 'completed':
        key = 'CALL:{}'.format(call_id)
        lead_id = redis_db.get(key)

        lead_mark_finished(lead_id)

    return ''


@bw_inbound.route(
    '/api/bw/inbound/continue/<int:lead_id>',
    methods=['GET', 'POST']
)
@csrf.exempt
def lead_inbound_call_continue(lead_id):
    """ Continuation of the lead's call. Why two methods for the same call?
        To make sure that we call the agent after the lead's call connected.
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    args = request.json
    print(args)
    event_type = args.get('eventType')
    call_id = args.get('callId')
    # state = args.get('state')
    status = args.get('status')
    tag = args.get('tag')

    storage = CallStorage(redis_db, lead_id)
    log.debug('{}: {}'.format(lead_id, storage.state))

    routing_config = storage.routing_config
    language = routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    r = bandwidth.Response()

    if (status == 'done' and tag == 'whisper_default'):
        from .bw_tasks import bw_play_hold_music
        from .bw_tasks import bw_call_agents

        bw_play_hold_music.delay(lead_id, call_id, routing_config)

        bw_call_agents.delay(lead_id, storage.routing)

    elif event_type == 'timeout':
        r = voicemail_redirect(lead_id, r)

    elif status == 'done' and tag == 'voicemail_prompt':
        from .bw_tasks import bw_play_voicemail_beep
        bw_play_voicemail_beep.delay(lead_id)

    elif status == 'done' and tag == 'voicemail_beep':
        from .bw_tasks import bw_start_recording
        bw_start_recording.delay(lead_id, call_id)

    elif event_type == 'hangup' and call_id == storage.call_sid:
        # Calculate the total call duration and response time
        start_time = datetime.strptime(
            storage.start_time, '%Y-%m-%dT%H:%M:%SZ'
        )
        duration, response_time_seconds = 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()

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

        lead_mark_finished(
            lead_id,
            response_time_seconds=response_time_seconds,
            duration=duration,
        )

        from .bw_tasks import bw_upload_recording
        bw_upload_recording.delay(call_id)

    elif event_type == 'hangup' and call_id != storage.call_sid:
        # Agent hangs up or didn't answer
        pass

    return ''


@bw_inbound.route(
    '/api/bw/inbound/conference/<int:lead_id>',
    methods=['GET', 'POST']
)
@csrf.exempt
def lead_inbound_conference(lead_id):
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    args = request.json
    print(args)

    storage = CallStorage(redis_db, lead_id)
    log.debug('{}: {}'.format(lead_id, storage.state))

    if (args.get('eventType') == 'conference-member' and
       args.get('callId') == storage.call_sid):
        storage.bw_conf_member_id = args.get('memberId')

    return ''


def lead_mark_finished(lead_id, response_time_seconds=None, duration=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)
    routing_config = storage.routing_config
    new_state = None
    with storage.lock():
        if storage.state in ['NEW', 'LEAD_ONHOLD']:
            new_state = State.MISSED
        if storage.state in ['ONGOING']:
            new_state = State.CAPTURED
        if new_state:
            storage.state = new_state

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

    if new_state:
        from .bw_tasks import bw_hangup_calls
        call_ids = [call_id for _, call_id in storage.current_agent_calls]
        bw_hangup_calls.delay(call_ids)

        if new_state == State.MISSED:
            lead.status = 'missed'
            if routing_config.get('configSMSSetup') and routing_config.get('MissedCallAutoReply'):
                from buyercall.blueprints.sms.views import send_text_message
                text = routing_config.get('MissedCallAutoReplyText') + ' Reply STOP to unsubscribe.'
                media_url = ''
                send_text_message(lead.inbound_id, lead.phonenumber, text, media_url)
            else:
                log.info('The SMS Configuration is turned off for this phone number')
        elif new_state == State.CAPTURED:
            lead.status = 'completed'
            lead.response_time_seconds = response_time_seconds
        lead.duration = duration
        db.session.commit()

    webhooker.trigger_generic_webhook('operational_end_call', lead.id)

    send_notifications(lead)


def call_agents(lead_id, r, routing):
    """ Add the BXML instructions to call the agents to the response.

    :param lead_id: The database id of the lead calling us.
    :param r: The BXML Response being built.
    :param routing: The selected inbound routing dict (default or numbered).
    """
    # 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)

    agents = get_routing_agents(routing)
    if not agents:
        r = voicemail_redirect(storage, r)
        return to_response(r)

    inbound = Lead.query.options(
        load_only('my_phone')
    ).filter(Lead.id == lead_id).scalar()

    storage.routing = routing
    storage.state = State.LEAD_ONHOLD

    call_order = routing.get('callOrder', 'sequence')
    if call_order == 'shuffle':
        import random
        log.debug('Shuffling agent list...')
        random.shuffle(agents)
        call_order = 'sequence'
    storage.call_order = call_order

    if call_order == 'sequence':
        for a in agents:
            number, extension = get_agent_number(a, routing)
            r.transfer(
                transfer_to=number,
                request_url=url_for(
                    'bw_inbound.lead_call_transfer',
                    lead_id=lead_id,
                    agent_id=a.id,
                    _external=True,
                    _scheme='https'
                ))

    if call_order == 'simultaneous':
        with r.transfer(
            from_=inbound,
            to=get_agent_number(a, routing),
            request_url=url_for(
                'bw_inbound.lead_call_transfer',
                lead_id=lead_id,
                agent_id=a.id,
                _external=True,
                _scheme='https'
            )
        ) as t:
            for a in agents:
                number, extension = get_agent_number(a, routing)
                t.phone_number(number)

    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/lead_digits_choice/<int:lead_id>',
    methods=['GET']
)
@csrf.exempt
def lead_digits_choice(lead_id):
    """ Gather the digits from the lead.
    """
    # 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)
    log.debug('{}: {}'.format(lead_id, storage.state))

    routing_config = storage.routing_config

    r = bxml_digits_choice(lead_id, routing_config)

    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/lead_digits_choice_repeat/<int:lead_id>',
    methods=['GET']
)
@csrf.exempt
def lead_digits_choice_repeat(lead_id):
    """ Gather the digits from the lead.
    """
    # 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)
    log.debug('{}: {}'.format(lead_id, storage.state))

    routing_config = storage.routing_config
    no_digit_whisper_message = routing_config.get('noDigitWhisperMessage', '')

    r = bxml_digits_choice(lead_id, routing_config, no_digit_whisper_message)

    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/lead_digit_result/<int:lead_id>',
    methods=['GET']
)
@csrf.exempt
def lead_digit_result(lead_id):
    """ Route the lead according to the digit pressed.
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    args = request.args
    print(args)
    event_type = args.get('eventType')

    storage = CallStorage(redis_db, lead_id)
    log.debug('{}: {}'.format(lead_id, storage.state))

    routing_config = storage.routing_config
    language = routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    r = bandwidth.Response()

    if event_type == 'gather':
        digit = args['digits']

        routings = [
            x for x in routing_config['digitRoutings']
            if x['dialDigit'] == digit
        ]
        if not routings:
            r.say(MSG_INVALID_DIGIT, gender, locale, voice)
            r.redirect(url_for(
                'bw_inbound.lead_digits_choice',
                lead_id=lead_id,
                _external=True,
                _scheme='https'
            ))
            return to_response(r)

        routing = routings[0]
        agents = get_routing_agents(routing)
        if not agents:
            r = voicemail_redirect(storage, r)
            return to_response(r)

        storage.routing = routing
        storage.state = State.LEAD_ONHOLD

        # Play hold music while calling agent
        r.play_audio(hold_music_url(routing_config))

        from .bw_tasks import bw_call_agents
        bw_call_agents.delay(lead_id, routing)
    elif event_type == 'hangup':
        lead_mark_finished(lead_id)
        r.hangup()

    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/lead_call_transfer/<int:lead_id>/<int:agent_id>',
    methods=['GET']
)
@csrf.exempt
def lead_call_transfer(lead_id, agent_id):
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    args = request.args
    print(args)
    event_type = args.get('eventType', '')

    storage = CallStorage(redis_db, lead_id)
    language = storage.routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    r = bandwidth.Response()

    if event_type == 'transferError':
        r.say(MSG_CANNOT_CONNECT, gender, locale, voice)
        r.hangup()

    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/lead_voicemail/<int:lead_id>',
    methods=['GET']
)
@csrf.exempt
def lead_voicemail(lead_id):
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    args = request.args
    print(args)

    storage = CallStorage(redis_db, lead_id)
    log.debug('{}: {}'.format(lead_id, storage.state))

    routing_config = storage.routing_config
    language = routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    r = bandwidth.Response()
    if routing_config.get('voicemail', False):
        r.say(routing_config['voicemailMessage'], gender, locale, voice)
        r.record()
    r.hangup()

    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/lead_hangup/<int:lead_id>',
    methods=['GET']
)
@csrf.exempt
def lead_hangup(lead_id):
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    r = bandwidth.Response()

    storage = CallStorage(redis_db, lead_id)
    language = storage.routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    r.say(MSG_NOT_AVAILABLE, gender, locale, voice)
    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/lead_voicemail_callback/<int:lead_id>',
    methods=['POST']
)
@csrf.exempt
def lead_voicemail_callback(lead_id):
    lead = Lead.query.filter(Lead.id == lead_id).first()
    lead.recording_url = request.form['RecordingUrl']
    db.session.commit()

    r = VoiceResponse()
    return to_response(r)


@bw_inbound.route(
    '/api/bw/inbound/call_result',
    methods=['POST']
)
@csrf.exempt
def call_result_callback():
    """ Called after the lead hangs up. """

    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    call_sid = request.form.get('CallSid', '')
    key = 'LEAD_{}'.format(call_sid)
    lead_id = redis_db.get(key)

    storage = CallStorage(redis_db, lead_id)
    log.debug('{}: {}'.format(lead_id, storage.state))

    log.debug('Call sid is {}, lead id is {}'.format(call_sid, lead_id))

    if not lead_id:
        log.warning('Cannot determine lead ID')
        return ''

    with storage.lock():
        if storage.state in [State.ONGOING, State.CAPTURED]:
            storage.state = State.CAPTURED
        elif storage.state != State.CALL_ME_BACK:
            storage.state = State.MISSED

    from .bw_tasks import bw_cancel_agent_calls
    bw_cancel_agent_calls.delay(lead_id)

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

    lead.endtime = datetime.utcnow()
    lead.duration = (lead.endtime - lead.starttime).total_seconds()

    if storage.state == State.CAPTURED:
        lead.status = 'completed'
    elif storage.state == State.MISSED:
        lead.status = 'missed'
        schedule_callback(storage)

    db.session.commit()

    send_notifications(lead)

    return ''


@bw_inbound.route(  # noqa
    '/api/bw/inbound/agent/<int:agent_id>/lead/<int:lead_id>',
    methods=['POST', 'GET']
)
@csrf.exempt
def agent_status_callback(agent_id, lead_id):
    """ Callback method for Bandwidth agent call status events.
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    args = request.json
    print(args)

    lead = Lead.query.filter(Lead.id == lead_id).first()
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    storage = CallStorage(redis_db, lead_id)
    log.debug('{}: {}'.format(lead_id, storage.state))

    client = bw_client(partner.id)
    call_id = args.get('callId')
    call = client.calls[call_id]
    tag = args.get('tag')
    event_type = args.get('eventType')
    state = args.get('state')

    routing_config = storage.routing_config
    routing = storage.routing
    language = routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    do_gather = False
    if event_type == 'answer':
        storage.connect_time = args.get('time')

        agent_text = get_agent_text(
            routing_config.get('hiddenInformation', '')
        )

        if not agent_text:
            do_gather = True
        else:
            call.audio(
                sentence=agent_text, tag='hidden_info',
                gender=gender, locale=locale, voice=voice
            )

    if tag == 'end' and state == 'PLAYBACK_STOP':
        call.hangup()

    do_bridge = False
    if do_gather or (tag == 'hidden_info' and state == 'PLAYBACK_STOP'):
        # TODO: Digit prompt
        # If digit prompt is disabled for the phone number, just continue
        if not routing_config.get('digitPrompt', False):
            do_bridge = True
        else:
            call.gather({
                "max_digits": 1,
                "prompt": {
                    "sentence": MSG_DIGIT_PROMPT,
                    "gender": gender,
                    "locale": locale
                },
                "tag": 'gather',
            })

    if do_bridge or (
        tag == 'gather' and args.get('reason') == 'max-digits'
    ):
        with storage.lock():
            if storage.agent_id is None:
                storage.agent_id = agent_id
                storage.agent_call_id = call_id
        if int(storage.agent_id) != agent_id:
            call.audio(
                sentence=MSG_TOO_SLOW, tag='end',
                gender=gender, locale=locale, voice=voice
            )
            return ''

        from .bw_tasks import (
            bw_cancel_agent_calls,
            bw_agent_bridge_lead,
            bw_stop_hold_music,
        )
        bw_cancel_agent_calls.delay(lead_id, other_than=agent_id)
        bw_agent_bridge_lead.delay(lead_id)
        bw_stop_hold_music.delay(lead_id)

        storage.state = State.ONGOING
        lead.agent_id = agent_id
        lead.status = 'in-progress'
        db.session.commit()

    if tag == 'gather' and args.get('reason') == 'inter-digit-timeout':
        call.hangup()

    if event_type == 'hangup':
        storage.remove_agent_call(agent_id, call_id)
        if (
            args.get('cause') == 'NORMAL_CLEARING' and
            storage.state == State.ONGOING and
            storage.agent_id == str(agent_id)
        ):
            client.calls[storage.call_sid].hangup()
            lead = Lead.query.filter(Lead.id == lead_id).first()
            lead.status = 'completed'
            db.session.commit()

            # Update minutes consumed
            call_details = call.details()
            call_duration = call_details.get('chargeableDuration')
            if call_duration:
                # TODO: Optimize joined load
                lead = Lead.query.options(
                    load_only('partnership_account_id')
                ).filter(Lead.id == lead_id).first()
                subscription = lead.partnership_account.subscription
                subscription.update_usage(lead.partnership_account_id, seconds=call_duration)
        else:  # Agent busy or call rejected
            from .bw_tasks import (
                bw_call_agents,
                bw_try_call_agent_sequence,
                bw_redirect_to_voicemail,
            )

            storage.remove_agent_call(agent_id, call_id)

            # Are there still agents left on the call?
            agents_left = len(storage.current_agent_calls)
            log.info('{} agents still left on the call.'.format(agents_left))

            lead_on_call = storage.state not in [State.MISSED, State.CAPTURED]

            if (
                agents_left == 0 and
                routing.get('callOrder', '') == 'sequence' and
                len(storage.agents_to_call) > 0 and
                lead_on_call
            ):
                log.info('Calling next agent...')
                routing = storage.routing
                bw_try_call_agent_sequence.delay(routing, lead_id)
                return ''

            # Do we need to retry the call?
            cnt = storage.inc_retry_cnt()
            if cnt <= int(routing['retryRouting']) and lead_on_call:
                bw_call_agents.delay(lead_id, routing)
                return ''

            if lead_on_call:
                bw_redirect_to_voicemail.delay(lead_id)

    return ''


def bxml_digits_choice(lead_id, routing_config, message=None):
    """ :returns VoiceResponse
    """
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    whisper_message = message or routing_config.get('whisperMessage')

    storage = CallStorage(redis_db, lead_id)
    language = storage.routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    r = bandwidth.Response()

    with r.gather(
        request_url=url_for(
            'bw_inbound.lead_digit_result',
            lead_id=lead_id,
            _external=True,
            _scheme='https'
        ),
        max_digits=1
    ) as g:
        if whisper_message:
            g.say(whisper_message, gender, locale, voice)

    # If nothing pressed, prompt again.
    r.redirect(url_for(
        'bw_inbound.lead_digits_choice_repeat',
        lead_id=lead_id,
        _external=True,
        _scheme='https'
    ))

    return r


def voicemail_redirect(lead_id, r):
    """ Add the BXML instructions to redirect the calling user to voicemail.

    :param lead_id: The database id of the lead calling us.
    :param r: The BXML Response being built.
    """
    # 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)
    routing_config = storage.routing_config

    try:
        # If voicemail enabled, redirect there...
        voicemail = routing_config.get('voicemail', False)
        if voicemail:
            r.redirect(url_for(
                'bw_inbound.lead_voicemail',
                lead_id=storage.lead_id,
                _external=True,
                _scheme='https'
            ))
        else:
            # ...otherwise, hang up
            r.redirect(url_for(
                'bw_inbound.lead_hangup',
                lead_id=storage.lead_id,
                _external=True,
                _scheme='https'
            ))
    except Exception as e:
        log.error(traceback.format_exc())
    return r


def send_notifications(lead):  # noqa
    # Declare redis config url
    redis_db = redis.StrictRedis(host=app.config['REDIS_CONFIG_URL'], port=app.config['REDIS_CONFIG_PORT'])

    from buyercall.blueprints.widgets.models import split_emails

    storage = CallStorage(redis_db, lead.id)
    if storage.manual_call:
        log.debug('Notification email suppressed.')
        return

    inbound = Phone.query.filter(
        Phone.id == lead.inbound_id,
        Phone.partnership_account_id == lead.partnership_account_id
    ).first()
    if not inbound:
        return

    notifications = inbound.notifications
    routings = inbound.routing_config

    if notifications.get('notifyLeads', 'none') == 'none':
        return

    # Send eventual notifications
    emails = []
    adf_emails = []

    user_email = None
    admin = User.query.filter(
        User.role == 'admin',
        User.partnership_account_id == lead.partnership_account_id,
        User.is_deactivated.is_(False)
    ).first()

    if admin:
        user_email = admin.email

    agent_ids = [
        a['id'] for a in inbound.routing_config['defaultRouting']['agents']
    ]
    agent_emails = [
        a.email
        for a in Agent.query.options(load_only('email')).filter(
            Agent.partnership_account_id == lead.partnership_account_id,
            Agent.id.in_(agent_ids)
        ).all()
    ]

    if (notifications.get('notifyLeads', 'none') == 'missed'
       and lead.status == 'missed'):
        emails.extend(split_emails(notifications.get('notifyMissedCustom', '')))
        if notifications.get('notifyMissedAgents'):
            emails.extend(agent_emails)
        if notifications.get('notifyMissedMe') and user_email:
            emails.append(user_email)
    if notifications.get('notifyLeads', 'none') == 'all':
        emails.extend(split_emails(notifications.get('notifyAllCustom', '')))
        if notifications.get('notifyAllAgents'):
            emails.extend(agent_emails)
        if notifications.get('notifyAllMe') and user_email:
            emails.append(user_email)

    if not emails:
        log.warning('Nowhere to send {} lead notification!'.format(
            lead.status
        ))
        return

    partner_account = PartnershipAccount.query.filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    ctx = vars(lead)
    eastern = pytz.timezone('US/Eastern')
    ctx['created_on'] = lead.created_on.astimezone(eastern).strftime('%c')
    ctx['updated_on'] = lead.updated_on.astimezone(eastern).strftime('%c')

    if lead.firstname:
        ctx['caller_name_label'] = 'Caller Name:'
        ctx['caller_name'] = ''.join((lead.firstname, ' ', lead.lastname))

    if lead.caller_id:
        ctx['caller_id_label'] = 'Caller Id:'
        ctx['caller_id'] = lead.caller_id

    if lead.recording_url:
        ctx['vm_div_style'] = 'block'
    else:
        ctx['vm_div_style'] = 'none'

    if lead.source:
        ctx['call_source'] = lead.source

    if lead.inbound:
        ctx['reference'] = lead.inbound.routing_config.get(
            'hiddenInformation', ''
        )

    ctx['partner_logo'] = partner.logo
    ctx['company'] = partner.name

    try:
        app.logger.debug('The email list are {}'.format(emails))
        if routings['notifyAdf'] is not None and routings['notifyAdf'] is 'True' or routings['notifyAdf']:

            if routings['notifyAdfCustom'] is not None and routings['notifyAdfCustom'] is not '':
                multi_adf_emails = split_emails(routings['notifyAdfCustom'])

                if multi_adf_emails is not None:
                    adf_emails.extend(multi_adf_emails)
                else:
                    adf_emails.append(routings['notifyAdfCustom'])

            ctx['lead_comments'] = 'Call status: {}'.format(lead.status)
            ctx['receiver'] = ''
            agent_name = lead.agent_name
            friendly_name = lead.source
            phonenumber = lead.inbound.phonenumber
            dealership = partner_account.name
            if dealership:
                ctx['vendor_name'] = dealership
            else:
                ctx['vendor_name'] = ''
            call_lead_id = lead.id
            if call_lead_id:
                ctx['call_lead_id'] = call_lead_id
            else:
                ctx['call_lead_id'] = ''

            if agent_name and len(agent_name) > 2:
                ctx['lead_comments'] = '{}, Agent name: {}'.format(ctx['lead_comments'], agent_name)

            if friendly_name:
                ctx['lead_comments'] = '{}, Call source: {}'.format(ctx['lead_comments'], friendly_name)
                ctx['receiver'] = friendly_name

            if phonenumber:
                ctx['lead_comments'] = '{}, Phonenumber: '.format(ctx['lead_comments'], phonenumber)

            ctx['campaign_name'] = ''
            ctx['campaign_exists'] = False
            from buyercall.blueprints.contacts.models import Contact, Campaigns
            contact = Contact.query.filter(
                    Contact.id == lead.contact_id,
                    Contact.partnership_account_id == lead.partnership_account_id)\
                .first()
            if contact and contact.campaign_id:
                campaign = Campaigns.query.filter(
                    Campaigns.id == contact.campaign_id)\
                    .first()
                if campaign:
                    ctx['campaign_exists'] = True
                    ctx['campaign_name'] = campaign.display_name

            if adf_emails and len(adf_emails) > 0:
                # Render text template for adf email
                adf_lead_email_template = _try_renderer_template('mail/adf_lead', ext='txt', **ctx)
                send_ses_email(recipients=adf_emails,
                               p_id=partner.id,
                               subject='{} - ADF lead notification'.format(partner.name),
                               text=adf_lead_email_template
                               )
        if lead.status == 'missed' and storage.state == State.MISSED:
            # Render html template for email
            missed_lead_email_template = _try_renderer_template('mail/missed_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject='{} - Missed lead notification'.format(partner.name),
                           html=missed_lead_email_template
                           )

        elif lead.status == 'completed' and storage.state == State.CAPTURED:
            # Render html template for email
            completed_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject='{} - Answered lead notification'.format(partner.name),
                           html=completed_lead_email_template
                           )
        else:
            # Render html template for email
            captured_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject='{} - New lead notification'.format(partner.name),
                           html=captured_lead_email_template
                           )
    except Exception as e:
        log.error(traceback.format_exc())


# DEBUG
def to_response(r):
    """ Transform a Twiml Response to a Flask response object. """
    import xml.dom.minidom
    xml = xml.dom.minidom.parseString(str(r))
    result = xml.toprettyxml()
    print(result)
    return result