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_new/buyercall/buyercall/blueprints/phonenumbers/bw_tasks.py
import os
import traceback
import logging as log
from datetime import datetime, timedelta
from tempfile import TemporaryFile
import uuid
import pytz
import boto3
import redis
from flask import url_for, current_app as app
from sqlalchemy.orm import load_only, subqueryload

from buyercall.app import create_celery_app
from buyercall.lib.util_twilio import (
    CallStorage,
    subaccount_client,
    InboundCallState as State,
    bw_client,
    select_voice,
    split_name,
)
from buyercall.lib.util_webhooks import WebhookUtil
from buyercall.lib.messages import (
    MSG_SERVICE_UNAVAILABLE
)
from buyercall.extensions import db
from ..leads.models import Lead
from ..agents.models import Agent
from ..phonenumbers.models import (
    Phone,
    HoldMusic,
    Audio,
)
from .routing import (
    get_routing_agents,
    get_agent_number
)

from ..contacts.models import Contact
from .bw_inbound import hold_music_url


HOURS = 3600
DAYS = 86400  # The length of a day in seconds
AGENT_TIMEOUT = 30

# TODO Parse Redis URL
celery = create_celery_app(app)
webhooker = WebhookUtil()


@celery.task
def bw_lead_accept_or_reject_call(inbound_id, call_id, **kwargs):

    inbound = Phone.query.options(
        subqueryload(Phone.partnership_account)
    ).filter(
        Phone.id == inbound_id
    ).first()
    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == inbound.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    if inbound is None:
        log.error('Cannot find inbound route {} in the database!'.format(
            inbound_id
        ))
        client.calls[call_id].reject()
        return

    # Check if the account has been disabled; if so, reject the call
    if not inbound.partnership_account.has_active_subscription:
        log.error('Partnership account {} has no active subscription!'.format(
            inbound.partnership_account_id
        ))
        client.calls[call_id].reject()
        return

    client.calls[call_id].accept()


@celery.task
def bw_lead_create(
    inbound_id, call_id, phone_number, caller_name=None, start_time=None, **kwargs
):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    inbound = Phone.query.filter(
        Phone.id == inbound_id
    ).first()

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == inbound.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    first_name, last_name = split_name(caller_name)

    if (not first_name and not last_name) or (first_name == 'Unknown'):
        first_name, last_name = Lead.get_last_known_name(
            inbound.partnership_account_id, phone_number
        )

    previous_lead = Lead.query.filter(
        phone_number == Lead.phonenumber,
        inbound.partnership_account_id == Lead.partnership_account_id
    ).order_by(Lead.created_on.desc()).first()

    if previous_lead is not None:
        contact_id = previous_lead.contact_id
        progress_status = previous_lead.progress_status
    else:
        contact_id = None
        progress_status = 'no status'

    lead = Lead(
        partnership_account_id=inbound.partnership_account_id,
        firstname=first_name,
        lastname=last_name,
        phonenumber=phone_number,
        email='',
        starttime=datetime.utcnow(),
        call_sid=call_id,
        call_type='inbound',
        my_phone=inbound.phonenumber,
        call_source=inbound.friendly_name,
        inbound_id=inbound_id,
        status='ringing',
        contact_id=contact_id,
        originating_number=phone_number,
        progress_status=progress_status
    )
    db.session.add(lead)
    db.session.commit()

    # Return Bandwidth call for CNAM value for lead
    # info = client.number_info(phone_number)
    # if not info:
    #    log.debug('Caller ID information not found')
    #    return

    # log.debug('Caller info: {}'.format(info))

    # Query the leads table to retrieve the latest lead that was added
    latest_lead = Lead.query\
        .filter(Lead.phonenumber == phone_number)\
        .order_by(Lead.created_on.desc())\
        .first()
    log.info('latest lead: {}'.format(latest_lead))

    # Query contacts to see if lead already exist in the contacts table
    contact = Contact.query\
        .filter(Contact.phonenumber_1 == latest_lead.phonenumber)\
        .filter(Contact.partnership_account_id == inbound.partnership_account_id)\
        .first()

    if contact:
        log.info('a lead exist with number {}'.format(contact.phonenumber_1))
        contact.updated_on = latest_lead.updated_on
        # contact.caller_id = info.get('name', '')
        log.info('The updated date is: {}'.format(latest_lead.updated_on))
        db.session.commit()
    else:
        contact = Contact(
            firstname=first_name,
            lastname=last_name,
            # caller_id=info.get('name', ''),
            phonenumber_1=phone_number,
            email='',
            partnership_account_id=inbound.partnership_account_id,
        )
        db.session.add(contact)
        db.session.commit()
        log.info('The contact has been added')

    new_contact = Contact.query\
        .filter(Contact.phonenumber_1 == latest_lead.phonenumber)\
        .filter(Contact.partnership_account_id == inbound.partnership_account_id)\
        .first()
    if new_contact:
        latest_lead.contact_id = new_contact.id
        db.session.commit()
    else:
        log.info('no lead contact exist')

    # Associate the lead ID with the Bandwidth call ID
    key = 'CALL:{}'.format(call_id)
    redis_db.setex(key, 2*HOURS, lead.id)

    storage = CallStorage(redis_db, lead.id)
    storage.init()

    storage.call_sid = call_id
    storage.routing_config = inbound.routing_config
    storage.clear_retry_cnt()
    storage.clear_callback_cnt()
    storage.caller_number = phone_number
    storage.start_time = start_time

    # TODO: Bandwidth user ID
    storage.subaccount_sid = None

    storage.state = State.NEW
    storage.inbound = inbound.phonenumber

    if previous_lead is not None:
        storage.caller_name = caller_name

    # Make a CNAM lookup to find out the caller ID
    # TODO: Make this conditional on the agent's settings
    bw_lead_cnam_lookup.delay(lead.id, phone_number)

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

    client.calls[call_id].redirect(url_for(
        'bw_inbound.lead_inbound_call_continue',
        lead_id=lead.id,
        _external=True
    ))
    bw_lead_whisper_message.delay(lead.id, call_id, inbound_id)


@celery.task
def bw_lead_cnam_lookup(lead_id, phone_number, **kwargs):

    lead = Lead.query.filter(Lead.id == lead_id).first()
    if not lead:
        log.warning('Lead not found in the database')
        return

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    # Check leads in the database for caller ID information
    created_cutoff = datetime.utcnow().replace(tzinfo=pytz.utc) - timedelta(days=30)
    existing = Lead.query.filter(
        Lead.phonenumber == lead.phonenumber,
        Lead.partnership_account_id == lead.partnership_account_id,
        Lead.caller_id != '',
        Lead.created_on > created_cutoff,
    ).first()
    if existing:
        log.debug('Using existing caller ID')
        lead.caller_id = existing.caller_id
        db.session.commit()
        return

    info = client.number_info(phone_number)
    if not info:
        log.debug('Caller ID information not found')
        return

    log.debug('Caller info: {}'.format(info))

    lead.caller_id = info.get('name', '')
    db.session.commit()


@celery.task
def bw_lead_whisper_message(lead_id, call_id, inbound_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    inbound = Phone.query.options(
        subqueryload(Phone.partnership_account)
    ).filter(
        Phone.id == inbound_id
    ).first()

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == inbound.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    storage = CallStorage(redis_db, lead_id)
    log.debug('{}: {}'.format(lead_id, storage.state))
    routing_config = storage.routing_config
    routing_type = routing_config.get('routingType', '')
    language = routing_config.get('language', 'en')
    gender, locale, voice = select_voice(language)

    whisper_message = routing_config.get('whisperMessage', '')
    whisper_message_type = routing_config.get('whisperMessageType', '')
    whisper_message_audio = routing_config.get('whisperMessageAudio', '')

    if routing_type == 'default':
        routing = routing_config['defaultRouting']
        storage.routing = routing

        # If there's a whisper message, say it
        if whisper_message_type == 'audio':
            audio = Audio.query.filter(
                Audio.id == whisper_message_audio,
                Audio.inbound_id == inbound_id,
                Audio.whisper_message_type == 'whisperMessage',
                Audio.enabled == True,
            ).order_by(Audio.id.desc()).first()

            if audio:
                client.calls[call_id].audio(
                    file_url=audio.audio_url,
                    tag='whisper_default',
                )
            return
        elif whisper_message:
            # TODO: language
            client.calls[call_id].audio(
                sentence=whisper_message,
                gender=gender,
                locale=locale,
                voice=voice,
                tag='whisper_default'
            )
            return

        # Otherwise play hold music and call agents directly
        bw_play_hold_music(lead_id, call_id, routing_config)

        bw_call_agents.delay(lead_id, routing)
    elif routing_type == 'digits':
        client.calls[call_id].gather({
            'prompt': {
                'sentence': whisper_message,
                'gender': gender,
                'locale': locale
            },
            'max_digits': 1
        })
    else:
        log.error(
            'Unknown routing type for lead {}'.format(lead_id)
        )
        client.calls[call_id].audio(
            sentence=MSG_SERVICE_UNAVAILABLE,
            gender=gender, locale=locale, voice=voice
        )


@celery.task
def bw_play_hold_music(lead_id, call_id, routing_config, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead_id)

    lead = Lead.query.filter(Lead.id == lead_id).first()
    if not lead:
        log.warning('Lead not found in the database')
        return

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    conf = client.conferences.create(
        from_=storage.inbound,
        callback_url=url_for(
            'bw_inbound.lead_inbound_conference',
            lead_id=lead_id,
            _external=True
        )
    )
    storage.bw_conf_id = conf.id
    conf.add_member(call_id)
    conf.audio(
        file_url=hold_music_url(routing_config),
        loop_enabled='true',
        tag='hold_music',
    )


@celery.task
def bw_stop_hold_music(lead_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead_id)

    lead = Lead.query.filter(Lead.id == lead_id).first()
    if not lead:
        log.warning('Lead not found in the database')
        return

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)
    client.conferences[storage.bw_conf_id].audio(file_url='')


@celery.task
def bw_call_agents(lead_id, routing, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead_id)
    agents = get_routing_agents(routing)
    agent_ids = [a.id for a in agents]

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

    storage.set_agents_to_call(agent_ids)
    bw_try_call_agent_sequence(routing, lead_id)


@celery.task
def bw_try_call_agent_sequence(routing, lead_id, **kwargs):
    """ Call a single agent from the list of available agents.
    """
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead_id)
    lead = Lead.query\
        .join(Lead.inbound)\
        .filter(Lead.id == lead_id)\
        .first()
    calls = None

    a = None
    while not calls:
        agent_id = storage.next_agent_to_call()
        if not agent_id:
            log.info('No more agents left to call.')
            break

        a = Agent.query.filter(Agent.id == agent_id).first()
        if not a:
            continue

        agents_to_call = a.agents if a.is_group else [a]

        calls = bw_call_multiple_agents(
            agents=agents_to_call, lead=lead, routing=routing
        )

    if not calls:
        bw_redirect_to_voicemail(lead_id)
        # client.calls[storage.call_sid].hangup()
    else:
        webhooker.trigger_generic_webhook('operational_agent_call', lead_id)
        if a and lead:
            contact = Contact.query.filter(Contact.id == lead.contact_id).first()
            if contact and not contact.agent_id and contact.agent_id != a.id:
                contact.agent_id = a.id
                contact.agent_assigned = a.full_name
                db.session.commit()
                from buyercall.blueprints.mobile.utils import send_agent_push_notification
                send_agent_push_notification(contact)


def bw_call_multiple_agents(agents=None, lead=None, routing=None, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead.id)
    calls = []

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    for agent in agents:
        try:
            agent_number, extension = get_agent_number(agent, routing)
            # digits = 'wwww' + extension if extension else None

            if not agent_number:
                log.warning(u'Invalid agent number for {}'.format(agent.full_name))
                continue

            with storage.lock():
                if storage.state in [
                    State.NEW, State.LEAD_ONHOLD, State.CALLBACK_PROMPT
                ]:
                    call = client.call(
                        from_=lead.inbound.phonenumber,
                        to=agent_number,
                        callback_url=url_for(
                            'bw_inbound.agent_status_callback',
                            lead_id=lead.id,
                            agent_id=agent.id,
                            _external=True,
                            _scheme='https'
                        ),
                        callback_http_method='POST'
                    )
                    storage.push_agent_call(agent.id, call.id)
                    calls.append(call)
        except Exception as e:
            log.warning(traceback.format_exc())
            log.warning('Error calling agent {}...'.format(
                agent.id
            ))

    return calls


@celery.task
def bw_hangup_calls(call_ids, **kwargs):

    for call_id in call_ids:
        lead = Lead.query.filter(Lead.call_sid == call_id).first()
        if not lead:
            log.warning('Lead not found in the database')
            return
        # import partnership information to get partnership id
        from ..partnership.models import Partnership, PartnershipAccount
        partner_account = PartnershipAccount.query \
            .filter(PartnershipAccount.id == lead.partnership_account_id).first()
        partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()
        client = bw_client(partner.id)
        try:
            client.calls.hangup(call_id)
        except Exception as e:
            log.warning(traceback.format_exc())


@celery.task
def bw_redirect_to_voicemail(lead_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

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

    with storage.lock():
        if storage.prompt_task:
            celery.control.revoke(storage.prompt_task)
            storage.prompt_task = None

    lead = Lead.query.filter(Lead.id == lead_id).first()
    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    try:
        # If voicemail enabled, blindly redirect there...
        voicemail = routing_config.get('voicemail', False)
        voicemail_message_type = routing_config.get('voicemailMessageType')
        voicemail_message = routing_config.get('voicemailMessage')
        voicemail_message_audio = routing_config.get('voicemailMessageAudio')

        call = client.calls[storage.call_sid]
        if not voicemail:
            call.hangup()
            return

        conf = client.conferences[storage.bw_conf_id]
        conf.audio(file_url='')
        if voicemail_message_type == 'audio':
            inbound_id = lead.inbound_id

            audio = Audio.query.filter(
                Audio.id == voicemail_message_audio,
                Audio.inbound_id == inbound_id,
                Audio.whisper_message_type == 'voicemailMessage',
                Audio.enabled == True,
            ).order_by(Audio.id.desc()).first()

            if audio:
                call.audio(
                    file_url=audio.audio_url,
                    tag='voicemail_prompt',
                )
        elif voicemail_message:
            call.say(
                voicemail_message, gender, locale, voice,
                tag='voicemail_prompt'
            )
    except Exception as e:
        log.error(traceback.format_exc())
        return


@celery.task
def bw_play_voicemail_beep(lead_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead_id)

    lead = Lead.query.filter(Lead.id == lead_id).first()
    if not lead:
        log.warning('Lead not found in the database')
        return
    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)
    call = client.calls[storage.call_sid]
    call.audio(file_url=celery.conf['BEEP_SOUND'], tag='voicemail_beep')


@celery.task
def bw_start_recording(lead_id, call_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    storage = CallStorage(redis_db, lead_id)

    lead = Lead.query.filter(Lead.id == lead_id).first()
    if not lead:
        log.warning('Lead not found in the database')
        return
    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)

    call = client.calls[storage.call_sid]
    call.post(
        recording_enabled='true',
        recording_file_format='wav'
    )


@celery.task
def bw_agent_bridge_lead(lead_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    lead = Lead.query.filter(Lead.id == lead_id).first()
    if not lead:
        log.warning('Lead not found in the database')
        return

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    client = bw_client(partner.id)
    storage = CallStorage(redis_db, lead_id)
    routing_config = storage.routing_config

    if routing_config.get('recordCalls', False):
        client.calls[storage.call_sid].post(
            recording_enabled='true',
            recording_file_format='mp3'
        )

    client.bridge(call_ids=[storage.call_sid, storage.agent_call_id])

    client.calls[storage.agent_call_id].audio(
        file_url='https://s3.amazonaws.com/buyercall-static-sounds/beep.wav'
    )


@celery.task
def bw_cancel_agent_calls(lead_id, other_than=None, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    log.debug('Canceling agent calls for lead {}...'.format(lead_id))

    lead = Lead.query.filter(Lead.id == lead_id).first()
    if not lead:
        log.warning('Lead not found in the database')
        return

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

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

    agent_calls = storage.clear_agent_calls(
        lambda id_, _: id_ != other_than
    )
    if not agent_calls:
        return

    for (agent_id, call_sid) in agent_calls:
        try:
            log.debug('...agent {}, call {}'.format(agent_id, call_sid))
            client.calls[call_sid].hangup()
        except Exception as e:
            log.info('Cannot hangup call {}'.format(call_sid))


# TODO: FIXME: What was I going to do here?
@celery.task
def bw_agent_bridge_lead2(lead_id, agent_id, call_settings, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    from ..agents.models import Agent

    lead = Lead.query.join(Lead.inbound).filter(
        Lead.id == lead_id
    ).first()
    if not lead:
        return

    # import partnership information to get partnership id
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    # Get the partner id to get relevant twilio credentails with twilio client
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    storage = CallStorage(redis_db, lead_id)
    storage.routing = call_settings
    storage.manual_call = True
    storage.routing_config = {}
    twilio_client = subaccount_client(lead.partnership_account.subscription.twilio_subaccount_sid, partner.id)

    agent = Agent.query.filter(Agent.id == agent_id).first()
    if not agent:
        log.error('Cannot find agent with ID {}!'.format(lead_id))
        return

    storage.state = State.CALLBACK
    log.debug('Calling agent with ID {}...'.format(agent_id))
    call = None

    try:
        agent_number, extension = get_agent_number(agent, call_settings)
        digits = 'wwww' + extension if extension else None

        with storage.lock():
            if storage.state == State.CALLBACK:
                call = twilio_client.calls.create(
                    to=agent_number,
                    from_=lead.inbound.phonenumber,
                    url=url_for(
                        'twilio_inbound.agent_callback_lead',
                        lead_id=lead_id,
                        agent_id=agent.id,
                        _external=True
                    ),
                    status_callback=url_for(
                        'twilio_inbound.agent_callback_call_status',
                        agent_id=agent.id,
                        lead_id=lead_id,
                        _external=True
                    ),
                    status_callback_event=['answered', 'completed'],
                    send_digits=digits,
                    timeout=AGENT_TIMEOUT
                )
                storage.push_agent_call(agent.id, call.sid)
    except Exception as e:
        log.warning(traceback.format_exc())
        log.warning('Error calling agent {}...'.format(
            agent.id
        ))

    if call is None:
        # None of the calls got through
        lead.status = 'missed'
        db.session.commit()


@celery.task
def bw_upload_recording(call_id, **kwargs):
    """ After the call, grab the first recording from Bandwidth, if any, and
    upload to S3.
    """
    lead = Lead.query.filter(Lead.call_sid == call_id).first()
    from ..partnership.models import Partnership, PartnershipAccount
    partner_account = PartnershipAccount.query. \
        filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner_name = partner_account.name

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

    client = bw_client(partner.id)
    recs = client.calls[call_id].recordings()
    if len(recs) == 0:
        log.warning('No recordings found for call {}!'.format(call_id))
        return
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], call_id)
    log.info('The file path is: {}'.format(file_path))
    with open(file_path, 'wb') as f:
        count = 0
        for c in recs[0].data().iter_content(chunk_size=1024):
            f.write(c)
            count += len(c)
        log.info("Read {} bytes.".format(count))

    with open(file_path, 'rb') as f:

        hex_value = uuid.uuid4().hex
        key = '{}_{}/{}_{}_{}'.format(
            partner_name, lead.partnership_account_id, lead.id, 'recording', hex_value)
        app.logger.debug('The recording key is {}'.format(key))
        s3 = boto3.client(
            's3',
            aws_access_key_id=celery.conf['AMAZON_ACCESS_KEY'],
            aws_secret_access_key=celery.conf['AMAZON_SECRET_KEY']
        )
        s3.upload_fileobj(f,
                       celery.conf['RECORDING_BUCKET'],
                       key,
                       ExtraArgs={'Metadata': {'Content-Type': 'audio/mpeg'},
                                  'ACL': 'private'})
        log.info('Uploaded file {}'.format(key))

    # Update recording link
    updated_lead = Lead.query.options(
        load_only("id", "recording_url")
    ).filter(Lead.id == lead.id).first()
    updated_lead.recording_url = '{}::{}'.format(celery.conf['RECORDING_BUCKET'], key)
    db.session.commit()
    os.remove(file_path)


@celery.task
def bw_upload_holdmusic(user_id, hold_music_id, file_path, **kwargs):
    """ Upload the given MP3 file to S3.
    """
    # Update recording link
    hold_music = HoldMusic.query.options(
        load_only("id", "uuid", "url")
    ).filter(HoldMusic.id == hold_music_id).first()

    with open(file_path, 'r') as f:
        conn = S3Connection(
            celery.conf['AMAZON_ACCESS_KEY'], celery.conf['AMAZON_SECRET_KEY']
        )
        bucket = conn.get_bucket(celery.conf['HOLD_MUSIC_BUCKET'])
        k = Key(bucket)
        k.set_metadata('Content-Type', 'audio/mpeg')
        k.key = hold_music.uuid
        k.set_contents_from_file(f)
        k.set_acl('public-read')
        log.info('Uploaded file {}'.format(k.key))

    os.remove(file_path)

    hold_music.url = k.generate_url(expires_in=0, query_auth=False)
    db.session.commit()


@celery.task()
def update_tn(pa_id, p_id, tn, tn_type, sms_option, **kwargs):
    # If it's a bandwidth number update the SMS enable status for the TN
    from buyercall.lib.util_bandwidth import bw_client
    if tn_type == 'mobile':
        client = bw_client(p_id, 'voice', 'mobile')
        r = client.phone_numbers_options.update(partner_account_id=pa_id,
                                                phonenumber=tn,
                                                sms_option=sms_option,
                                                # Add back these campaign items when campaigns have been moved.
                                                # sms_campaign_id=app.config.get('SMS_MOBILE_CAMPAIGN_ID'),
                                                # sms_campaign_class=app.config.get('SMS_MOBILE_CAMPAIGN_CLASS')
                                                )
    else:
        client = bw_client(p_id, 'voice')
        r = client.phone_numbers_options.update(partner_account_id=pa_id,
                                                phonenumber=tn,
                                                sms_option=sms_option)
    return r