# By: Riasat Ullah
# This file contains all the views related to live call routing, starting from registration to Twilio interactions.

from data_syncers import syncer_live_calls, syncer_policies, syncer_services
from dbqueries import db_accounts, db_live_call_routing, db_organizations, db_users
from django.http import HttpResponse
from exceptions.user_exceptions import InvalidRequest, UnauthorizedRequest
from objects.live_call_routing import LiveCallRouting
from rest_framework.decorators import api_view
from rest_framework.response import Response
from taskcallrest import settings
from threading import Thread
from translators import label_translator as _lt
from utils import constants, errors, info, logging, permissions, times, tokenizer, var_names
from utils.communication_vendors import Twilio
from utils.db_connection import CACHE_CLIENT, CONN_POOL
from validations import configuration_validator, request_validator, string_validator
import configuration as configs
import jwt


@api_view(['POST'])
def create_live_call_routing(request, conn=None):
    '''
    Creates a new live call routing specification.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.routing_name, var_names.iso_country_code, var_names.phone, var_names.phone_type,
                           var_names.default_forwarding_service]
        optional_fields = [var_names.description, var_names.option_forwarding_services,
                           var_names.greeting_text, var_names.greeting_audio_filename,
                           var_names.greeting_audio_location, var_names.greeting_audio_url,
                           var_names.ending_text, var_names.ending_audio_filename,
                           var_names.ending_audio_location, var_names.ending_audio_url,
                           var_names.text_language, var_names.is_male_voice, var_names.max_forwarding_users,
                           var_names.forwarding_timeout, var_names.urgency_level, var_names.block_numbers,
                           var_names.show_caller_id, var_names.resolve_answered_calls,
                           var_names.resolve_unanswered_calls, var_names.to_alert, var_names.record_voicemail,
                           var_names.record_call, var_names.prompt_call_acceptance, var_names.prompt_format,
                           var_names.incident_title_format]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields, optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.is_user_admin(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            # Read all the data sent in the payload
            routing_name = request.data[var_names.routing_name]
            routing_description = request.data[var_names.description] if var_names.description in request.data else None
            phone_iso_code = request.data[var_names.iso_country_code]
            phone_number = request.data[var_names.phone]
            phone_type = request.data[var_names.phone_type]
            greeting_text = request.data[var_names.greeting_text] if var_names.greeting_text in request.data else None
            greeting_audio_filename = request.data[var_names.greeting_audio_filename]\
                if var_names.greeting_audio_filename in request.data else None
            greeting_audio_location = request.data[var_names.greeting_audio_location]\
                if var_names.greeting_audio_location in request.data else None
            greeting_audio_url = request.data[var_names.greeting_audio_url]\
                if var_names.greeting_audio_url in request.data else None
            ending_text = request.data[var_names.ending_text] if var_names.ending_text in request.data else None
            ending_audio_filename = request.data[var_names.ending_audio_filename]\
                if var_names.ending_audio_filename in request.data else None
            ending_audio_location = request.data[var_names.ending_audio_location]\
                if var_names.ending_audio_location in request.data else None
            ending_audio_url = request.data[var_names.ending_audio_url]\
                if var_names.ending_audio_url in request.data else None
            default_service = request.data[var_names.default_forwarding_service]
            option_services = request.data[var_names.option_forwarding_services]\
                if var_names.option_forwarding_services in request.data else None
            text_language = request.data[var_names.text_language] if var_names.text_language in request.data else None
            male_voice = request.data[var_names.is_male_voice] if var_names.is_male_voice in request.data else None
            max_forwarding_users = request.data[var_names.max_forwarding_users]\
                if var_names.max_forwarding_users in request.data else None
            forwarding_timeout = request.data[var_names.forwarding_timeout]\
                if var_names.forwarding_timeout in request.data\
                else configs.call_routing_default_max_forwarding_timeout_seconds
            incident_urgency = request.data[var_names.urgency_level]\
                if var_names.urgency_level in request.data else configs.call_routing_default_incident_urgency
            block_numbers = request.data[var_names.block_numbers] if var_names.block_numbers in request.data else None
            show_caller_id = request.data[var_names.show_caller_id]\
                if var_names.show_caller_id in request.data else None
            resolve_answered_calls = request.data[var_names.resolve_answered_calls]\
                if var_names.resolve_answered_calls in request.data else None
            resolve_unanswered_calls = request.data[var_names.resolve_unanswered_calls]\
                if var_names.resolve_unanswered_calls in request.data else None
            to_alert = request.data[var_names.to_alert] if var_names.to_alert in request.data else None
            record_voice_mail = request.data[var_names.record_voicemail]\
                if var_names.record_voicemail in request.data else None
            prompt_call_acceptance = request.data[var_names.prompt_call_acceptance]\
                if var_names.prompt_call_acceptance in request.data else None

            if permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_SECONDARY_PERMISSION):
                record_call = request.data[var_names.record_call] if var_names.record_call in request.data else False

                if var_names.prompt_format in request.data and request.data[var_names.prompt_format] is not None and \
                        not string_validator.is_empty_string(request.data[var_names.prompt_format]):
                    prompt_format = request.data[var_names.prompt_format]
                else:
                    prompt_format = _lt.get_label(info.msg_live_call_route_default_call_acceptance_prompt, lang)

                if var_names.incident_title_format in request.data and \
                    request.data[var_names.incident_title_format] is not None and \
                        not string_validator.is_empty_string(request.data[var_names.incident_title_format]):
                    incident_title_format = request.data[var_names.incident_title_format]
                else:
                    incident_title_format = _lt.get_label(info.msg_live_call_route_default_incident_title, lang)
            else:
                record_call = False
                prompt_format = _lt.get_label(info.msg_live_call_route_default_call_acceptance_prompt, lang)
                incident_title_format = _lt.get_label(info.msg_live_call_route_default_incident_title, lang)

            # Validate all the details here before registering the number with Twilio
            assert phone_iso_code in configs.allowed_live_call_routing_countries
            assert string_validator.is_phone_number(phone_number)
            configuration_validator.validate_live_call_routing_data(
                routing_name, routing_description, greeting_text, greeting_audio_filename, greeting_audio_location,
                greeting_audio_url, ending_text, ending_audio_filename, ending_audio_location, ending_audio_url,
                default_service, option_services, text_language, male_voice, max_forwarding_users, forwarding_timeout,
                incident_urgency, block_numbers, show_caller_id, resolve_answered_calls, resolve_unanswered_calls,
                to_alert, record_voice_mail, record_call, prompt_call_acceptance, prompt_format, incident_title_format
            )

            # Only if the validation succeeds, register the phone number and create the routing
            current_time = times.get_current_timestamp()
            if settings.TEST_MODE:
                phone_number = constants.twilio_magic_number
                phone_sid = 'PXN0829JPJ21321'
            else:
                curr_cards = db_accounts.get_cards(conn, current_time, org_id)
                if len(curr_cards) == 0:
                    return Response(_lt.get_label(errors.err_live_call_route_card_required, lang), status=412)

                twilio_vendor = Twilio(Twilio.get_authentication())

                # Check if a vendor given address ID exists. If it does not try to create one.
                address_sid = db_live_call_routing.get_live_call_phone_address(conn, current_time, org_id)
                if address_sid is None:
                    org_details = db_organizations.get_organization_details(
                        conn, current_time, with_organization_id=org_id)
                    try:
                        address_sid = twilio_vendor.create_address_resource(
                            org_details[var_names.organization_name], org_details[var_names.address],
                            org_details[var_names.city], org_details[var_names.state],
                            org_details[var_names.zip_code], org_details[var_names.country]
                        )
                        db_live_call_routing.store_live_call_phone_address(conn, current_time, org_id, address_sid)
                    except Exception as e:
                        logging.exception(str(e))
                        return Response(_lt.get_label(errors.err_live_call_phone_address, lang), status=500)

                try:
                    phone_sid = db_live_call_routing.get_and_expire_pre_approved_phone_number(
                        conn, current_time, org_id, phone_iso_code, phone_number)
                    if phone_sid is None:
                        bundle_id = None
                        if phone_iso_code in configs.approval_required_live_call_countries:
                            can_purchase, docs_pending, req_docs, bundle_id, bundle_address_id =\
                                db_live_call_routing.get_regulations_and_approval_id(
                                    conn, current_time, org_id, phone_iso_code, phone_type)

                            if bundle_address_id is not None:
                                address_sid = bundle_address_id

                            if not can_purchase:
                                logging.error(errors.err_live_call_regulatory_approval)
                                return Response(_lt.get_label(errors.err_live_call_regulatory_approval, lang),
                                                status=451)

                        phone_sid = twilio_vendor.provision_number(phone_number, address_sid, bundle_sid=bundle_id)
                except Exception as e:
                    logging.exception(str(e))
                    return Response(_lt.get_label(errors.err_live_call_phone_address, lang), status=500)

            ref_id = db_live_call_routing.create_live_call_routing(
                conn, current_time, org_id, routing_name, routing_description, phone_iso_code, phone_number,
                phone_type, constants.twilio, phone_sid, greeting_text, greeting_audio_filename,
                greeting_audio_location, greeting_audio_url, ending_text, ending_audio_filename, ending_audio_location,
                ending_audio_url, default_service, option_services, text_language, male_voice, max_forwarding_users,
                forwarding_timeout, incident_urgency, block_numbers, show_caller_id, resolve_answered_calls,
                resolve_unanswered_calls, to_alert, record_voice_mail, record_call, prompt_call_acceptance,
                prompt_format, incident_title_format
            )

            return Response(ref_id)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def edit_live_call_routing(request, conn=None):
    '''
    Edits a live call routing specification.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.call_routing_ref_id, var_names.routing_name, var_names.default_forwarding_service]
        optional_fields = [var_names.description, var_names.option_forwarding_services,
                           var_names.greeting_text, var_names.greeting_audio_filename,
                           var_names.greeting_audio_location, var_names.greeting_audio_url,
                           var_names.has_greeting_audio_changed, var_names.ending_text,
                           var_names.ending_audio_filename, var_names.ending_audio_location,
                           var_names.ending_audio_url, var_names.has_ending_audio_changed,
                           var_names.text_language, var_names.is_male_voice, var_names.max_forwarding_users,
                           var_names.forwarding_timeout, var_names.urgency_level, var_names.block_numbers,
                           var_names.show_caller_id, var_names.resolve_answered_calls,
                           var_names.resolve_unanswered_calls, var_names.to_alert, var_names.record_voicemail,
                           var_names.record_call, var_names.prompt_call_acceptance, var_names.prompt_format,
                           var_names.incident_title_format]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields, optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.is_user_admin(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            if permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_SECONDARY_PERMISSION):
                record_call = request.data[var_names.record_call] if var_names.record_call in request.data else False

                if var_names.prompt_format in request.data and request.data[var_names.prompt_format] is not None and \
                        not string_validator.is_empty_string(request.data[var_names.prompt_format]):
                    prompt_format = request.data[var_names.prompt_format]
                else:
                    prompt_format = _lt.get_label(info.msg_live_call_route_default_call_acceptance_prompt, lang)

                if var_names.incident_title_format in request.data and \
                    request.data[var_names.incident_title_format] is not None and \
                        not string_validator.is_empty_string(request.data[var_names.incident_title_format]):
                    incident_title_format = request.data[var_names.incident_title_format]
                else:
                    incident_title_format = _lt.get_label(info.msg_live_call_route_default_incident_title, lang)
            else:
                record_call = False
                prompt_format = _lt.get_label(info.msg_live_call_route_default_call_acceptance_prompt, lang)
                incident_title_format = _lt.get_label(info.msg_live_call_route_default_incident_title, lang)

            db_live_call_routing.edit_live_call_routing(
                conn, times.get_current_timestamp(), org_id, request.data[var_names.call_routing_ref_id],
                request.data[var_names.routing_name],
                request.data[var_names.description] if var_names.description in request.data else None,
                request.data[var_names.greeting_text] if var_names.greeting_text in request.data else None,
                request.data[var_names.greeting_audio_filename]
                if var_names.greeting_audio_filename in request.data else None,
                request.data[var_names.greeting_audio_location]
                if var_names.greeting_audio_location in request.data else None,
                request.data[var_names.greeting_audio_url] if var_names.greeting_audio_url in request.data else None,
                request.data[var_names.has_greeting_audio_changed]
                if var_names.has_greeting_audio_changed in request.data else True,
                request.data[var_names.ending_text] if var_names.ending_text in request.data else None,
                request.data[var_names.ending_audio_filename]
                if var_names.ending_audio_filename in request.data else None,
                request.data[var_names.ending_audio_location]
                if var_names.ending_audio_location in request.data else None,
                request.data[var_names.ending_audio_url] if var_names.ending_audio_url in request.data else None,
                request.data[var_names.has_ending_audio_changed]
                if var_names.has_ending_audio_changed in request.data else True,
                request.data[var_names.default_forwarding_service],
                request.data[var_names.option_forwarding_services]
                if var_names.option_forwarding_services in request.data else None,
                request.data[var_names.text_language] if var_names.text_language in request.data else None,
                request.data[var_names.is_male_voice] if var_names.is_male_voice in request.data else None,
                request.data[var_names.max_forwarding_users] if var_names.max_forwarding_users in request.data
                else None,
                request.data[var_names.forwarding_timeout] if var_names.forwarding_timeout in request.data
                else configs.call_routing_default_max_forwarding_timeout_seconds,
                request.data[var_names.urgency_level] if var_names.urgency_level in request.data
                else configs.call_routing_default_incident_urgency,
                request.data[var_names.block_numbers] if var_names.block_numbers in request.data else None,
                request.data[var_names.show_caller_id] if var_names.show_caller_id in request.data else None,
                request.data[var_names.resolve_answered_calls]
                if var_names.resolve_answered_calls in request.data else None,
                request.data[var_names.resolve_unanswered_calls]
                if var_names.resolve_unanswered_calls in request.data else None,
                request.data[var_names.to_alert] if var_names.to_alert in request.data else None,
                request.data[var_names.record_voicemail] if var_names.record_voicemail in request.data else None,
                record_call,
                request.data[var_names.prompt_call_acceptance]
                if var_names.prompt_call_acceptance in request.data else None,
                prompt_format,
                incident_title_format
            )

            return Response(_lt.get_label(info.msg_live_call_route_edited, lang))
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def add_live_call_routing_audio_info(request, conn=None):
    '''
    Edits a live call routing specification.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.call_routing_ref_id]
        optional_fields = [var_names.greeting_audio_filename, var_names.greeting_audio_location,
                           var_names.greeting_audio_url,  var_names.ending_audio_filename,
                           var_names.ending_audio_location, var_names.ending_audio_url]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields, optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.is_user_admin(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            db_live_call_routing.add_live_call_routing_audio_info(
                conn, times.get_current_timestamp(), org_id, request.data[var_names.call_routing_ref_id],
                request.data[var_names.greeting_audio_filename]
                if var_names.greeting_audio_filename in request.data else None,
                request.data[var_names.greeting_audio_location]
                if var_names.greeting_audio_location in request.data else None,
                request.data[var_names.greeting_audio_url] if var_names.greeting_audio_url in request.data else None,
                request.data[var_names.ending_audio_filename]
                if var_names.ending_audio_filename in request.data else None,
                request.data[var_names.ending_audio_location]
                if var_names.ending_audio_location in request.data else None,
                request.data[var_names.ending_audio_url] if var_names.ending_audio_url in request.data else None
            )

            return Response(_lt.get_label(info.msg_live_call_route_edited, lang))
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def delete_live_call_routing(request, conn=None):
    '''
    Deletes a live call routing specification.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.call_routing_ref_id]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.is_user_admin(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            ref_id = request.data[var_names.call_routing_ref_id]
            current_time = times.get_current_timestamp()
            del_details = db_live_call_routing.get_live_call_routing_deletion_details(
                conn, current_time, org_id, ref_id, constants.twilio)

            if len(del_details) == 0:
                raise InvalidRequest(errors.err_unknown_resource)
            else:
                del_details = del_details[0]
                phone_sid = del_details[var_names.vendor_phone_id]
                if not settings.TEST_MODE:
                    twilio_vendor = Twilio(Twilio.get_authentication())
                    twilio_vendor.delete_number(phone_sid)

                db_live_call_routing.delete_live_call_routing(conn, current_time, org_id,
                                                              request.data[var_names.call_routing_ref_id])

                del del_details[var_names.vendor_phone_id]
                return Response(del_details)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def list_live_call_routing(request, conn=None):
    '''
    List all the live call routing specifications of an organization.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, [])
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.is_user_admin(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            lcr_list = db_live_call_routing.list_live_call_routing(conn, times.get_current_timestamp(), org_id)
            return Response(lcr_list)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_live_call_routing_details(request, conn=None):
    '''
    Get the details of a specific live call routing setup.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            expected_fields = [var_names.call_routing_ref_id]
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.is_user_admin(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            lcr_details = db_live_call_routing.get_live_call_routing_details(
                conn, times.get_current_timestamp(), organization_id=org_id,
                call_routing_ref_id=request.data[var_names.call_routing_ref_id]
            )[0]
            return Response(lcr_details)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_available_twilio_numbers(request, conn=None):
    '''
    Get the list of available numbers from Twilio.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.iso_country_code, var_names.phone_type]
        optional_fields = [var_names.area_code]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields, optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.is_user_admin(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            iso_code = request.data[var_names.iso_country_code]
            phone_type = request.data[var_names.phone_type]
            area_code = request.data[var_names.area_code] if var_names.area_code in request.data else None
            current_time = times.get_current_timestamp()

            twilio_client = Twilio(Twilio.get_authentication())
            available_numbers = twilio_client.get_available_numbers(iso_code, phone_type, area_code=area_code)
            available_numbers += db_live_call_routing.get_pre_approved_phone_numbers(
                conn, current_time, org_id, iso_code, phone_type)
            return Response(available_numbers)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)


@api_view(['POST'])
def initiate_twilio_live_call_routing(request, conn=None, cache=None):
    '''
    This is the first step of handling an incoming call for live call routing.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            current_time = times.get_current_timestamp()

            from_number = request.data[Twilio.var_From]
            from_iso = request.data[Twilio.var_FromCountry] if Twilio.var_FromCountry in request.data else None
            to_number = request.data[Twilio.var_To]
            call_sid = request.data[Twilio.var_CallSid]

            logging.info('Live call routing request received on {0} from {1}'.format(to_number, from_number))

            # get the associate live call routing and then proceed
            lcr = db_live_call_routing.get_live_call_routing(conn, current_time, to_number, from_number)
            lcr.caller = from_number
            lcr.twilio_call_sid = call_sid

            # Reject calls from blocked numbers.
            if lcr.is_number_blocked(from_number):
                lcr.call_status = constants.blocked_state

                # Create a suppressed task and log the call
                syncer_live_calls.log_live_call(conn, cache, current_time, request.data, lcr, from_iso)
                return HttpResponse(lcr.twilio_reject(), content_type=constants.content_type_xml)
            else:
                greet_timeout = 5 if lcr.has_greeting() else 0
                greet_resp = lcr.twilio_greeting(timeout=greet_timeout)

                # store the live call routing object in the cache before responding
                Thread(target=syncer_live_calls.store_live_call, args=(cache, lcr)).start()

                return HttpResponse(greet_resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)

            # Reject the call in the very first step to avoid billing. In the other steps user hang up.
            resp = LiveCallRouting.twilio_reject()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def start_twilio_call_forwarding(request, conn=None, cache=None):
    '''
    The call forwarding process will be initiated here. If the service is not in support or there are no on-calls,
    then the call ending process will also be initiated.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            current_time = times.get_current_timestamp()

            lang = request_validator.get_user_language(request)
            from_iso = request.data[Twilio.var_FromCountry] if Twilio.var_FromCountry in request.data else None
            call_sid = request.data[Twilio.var_CallSid]
            digits = request.data[Twilio.var_Digits] if Twilio.var_Digits in request.data else None
            twilio_call_status = request.data[Twilio.var_CallStatus] if Twilio.var_CallStatus in request.data else None

            logging.info('Processing live call routing response for CallSid {0}. Option selected - {1}'.format(
                call_sid, digits))

            lcr = syncer_live_calls.get_live_call(cache, call_sid)

            if digits is not None and len(digits) > 4 and lcr.caller_details[var_names.policy_id] is not None:
                dial_number, org_inst_id = lcr.get_return_call_instructions(digits)
                org_perm, rel_inst_id, rel_task_id = db_live_call_routing.get_return_call_meta_data(
                    conn, current_time, lcr.organization_id, dial_number, org_inst_id)

                if permissions.has_org_permission(org_perm, permissions.ORG_LIVE_CALL_ROUTING_SECONDARY_PERMISSION):
                    lcr.call_status = constants.outgoing
                    if twilio_call_status == Twilio.call_status_completed:
                        resp = lcr.twilio_hangup()
                        to_end = True
                    else:
                        resp = lcr.twilio_outbound_dial(dial_number)
                        to_end = False

                    # DO NOT REMOVE THIS. This ensures that the instance ID associated to the outgoing return call
                    # is the same as the incoming call.
                    if rel_inst_id is not None:
                        lcr.instance_id = rel_inst_id

                    syncer_live_calls.log_live_call(conn, cache, current_time, request.data, lcr, from_iso,
                                                    with_call_end=to_end, with_task_id=rel_task_id,
                                                    with_assignee=lcr.caller_details[var_names.policy_id])
                else:
                    resp = lcr.twilio_hangup()
            else:
                if lcr.option_forwarding_services is not None and digits in lcr.option_forwarding_services:
                    serv_id = lcr.option_forwarding_services[digits]
                else:
                    serv_id = lcr.default_forwarding_service
                fwd_service = syncer_services.get_single_service(conn, cache, current_time, serv_id, store_misses=False)

                # Set up the variables that will be needed next
                resp, nxt_user, with_voicemail = None, None, False

                # Check if the service is currently in support mode
                if fwd_service.is_available(current_time) and fwd_service.in_support_mode(current_time):
                    fwd_policy = syncer_policies.get_policies(conn, cache, current_time, [fwd_service.for_policy_id],
                                                              store_misses=False)[fwd_service.for_policy_id]
                    lcr.policy_on_calls = [x for item in fwd_policy.get_all_level_on_calls(check_datetime=current_time)
                                           for x in item]

                    # If there is at least one available on-call personnel then start forwarding the call
                    if lcr.has_next_on_call():
                        all_user_ids = set([item[0] for item in lcr.policy_on_calls])
                        lcr.on_call_phones = db_users.get_user_phone_numbers(conn, current_time, lcr.organization_id,
                                                                             list(all_user_ids))
                        nxt_user, nxt_phone = lcr.next_on_call_user_id(), lcr.next_on_call_phone_number()
                        logging.info('Forwarding call to ' + str(nxt_phone))

                        # Prepare a Dial Twiml and respond back
                        lcr.increase_forwarding_count()
                        resp = lcr.twilio_dial(nxt_phone)

                if resp is None:
                    resp = lcr.twilio_ending()

                # End the call if the user ends the call before we can start forwarding
                to_end = False
                if twilio_call_status == Twilio.call_status_completed:
                    nxt_user = None
                    to_end = True
                    resp = lcr.twilio_hangup()

                inst_id = syncer_live_calls.log_live_call(
                    conn, cache, current_time, request.data, lcr, from_iso, serv_id, nxt_user, with_call_end=to_end
                )

                if inst_id is None:
                    resp = lcr.twilio_hangup()

            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            lcr = syncer_live_calls.get_live_call(cache, request.data[Twilio.var_CallSid])
            if lcr is not None:
                syncer_live_calls.end_call(conn, cache, times.get_current_timestamp(), lcr)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def prompt_forward_acceptance(request, conn=None, cache=None):
    '''
    Asks the user to confirm whether they want to connect with the caller or not.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            call_sid = request.data[Twilio.var_ParentCallSid]\
                if Twilio.var_ParentCallSid in request.data else request.data[Twilio.var_CallSid]

            logging.info('CallSid {0} has been picked up.'.format(call_sid))
            lcr = syncer_live_calls.get_live_call(cache, call_sid)

            if lcr.prompt_call_acceptance:
                logging.info('Asking for confirmation before connecting with the caller.')
                resp = lcr.twilio_connection_prompt()
            else:
                logging.info('Answering call without call acceptance confirmation.')
                lcr.call_status = constants.answered_state
                syncer_live_calls.answer_call(conn, cache, times.get_current_timestamp(), lcr)
                Thread(target=syncer_live_calls.store_live_call, args=(cache, lcr)).start()
                resp = LiveCallRouting.twilio_empty()

            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            lcr = syncer_live_calls.get_live_call(cache, request.data[Twilio.var_CallSid])
            if lcr is not None:
                syncer_live_calls.end_call(conn, cache, times.get_current_timestamp(), lcr)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def confirm_forward_acceptance(request, conn=None, cache=None):
    '''
    Confirms whether the user has accepted the call or not.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            call_sid = request.data[Twilio.var_CallSid]
            parent_sid = request.data[Twilio.var_ParentCallSid]
            digits = request.data[Twilio.var_Digits] if Twilio.var_Digits in request.data else None

            logging.info('Confirming call acceptance for child CallSid {0}. Key pressed - {1}'.format(
                call_sid, digits))

            lcr = syncer_live_calls.get_live_call(cache, parent_sid)
            if digits is None or len(digits) == 0:
                logging.info('Call not acknowledged. Ending child call.')
                twilio_vendor = Twilio(Twilio.get_authentication())
                Thread(target=syncer_live_calls.store_live_call, args=(cache, lcr)).start()
                twilio_vendor.force_call_end(call_sid)
            else:
                logging.info('Call forwarding accepted. Updating status to answered.')
                lcr.call_status = constants.answered_state
                syncer_live_calls.answer_call(conn, cache, times.get_current_timestamp(), lcr)
                Thread(target=syncer_live_calls.store_live_call, args=(cache, lcr)).start()

            resp = LiveCallRouting.twilio_empty()
            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            lcr = syncer_live_calls.get_live_call(cache, request.data[Twilio.var_CallSid])
            if lcr is not None:
                syncer_live_calls.end_call(conn, cache, times.get_current_timestamp(), lcr)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def handle_twilio_call_forwarding_outcome(request, conn=None, cache=None):
    '''
    Call forwarding outcomes are handled by this view - call answers, call completions, need to forward again.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            current_time = times.get_current_timestamp()

            if Twilio.var_ParentCallSid in request.data:
                call_sid = request.data[Twilio.var_ParentCallSid]
            else:
                call_sid = request.data[Twilio.var_CallSid]

            twilio_call_status = request.data[Twilio.var_CallStatus]
            twilio_call_duration = int(request.data[Twilio.var_CallDuration]) \
                if Twilio.var_CallDuration in request.data else None

            logging.info('Processing live call routing response for CallSid {0}. Received call status - {1}'.format(
                call_sid, twilio_call_status))
            if Twilio.var_DialCallStatus in request.data:
                logging.info('Dial call status - ' + request.data[Twilio.var_DialCallStatus])

            logging.info('Final Status: ' + twilio_call_status)
            lcr = syncer_live_calls.get_live_call(cache, call_sid)
            if lcr is None:
                resp = LiveCallRouting.twilio_hangup()
            else:
                if twilio_call_status == Twilio.call_status_in_progress and lcr.call_status != constants.answered_state:
                    if lcr.has_next_on_call():
                        nxt_user, nxt_phone = lcr.next_on_call_user_id(), lcr.next_on_call_phone_number()
                        logging.info('Forwarding call to ' + str(nxt_phone))

                        lcr.increase_forwarding_count()
                        resp = lcr.twilio_dial(nxt_phone)
                        syncer_live_calls.forward_call(conn, current_time, lcr, nxt_user)
                        Thread(target=syncer_live_calls.store_live_call, args=(cache, lcr)).start()
                    else:
                        logging.info('No more on-calls found')
                        resp = lcr.twilio_ending()
                elif twilio_call_status == Twilio.call_status_completed:
                    resp = lcr.twilio_hangup()
                    syncer_live_calls.end_call(conn, cache, current_time, lcr)
                else:
                    lcr.call_duration = twilio_call_duration
                    if lcr.call_status != constants.answered_state:
                        lcr.call_status = constants.no_answer_state
                    resp = lcr.twilio_hangup()
                    syncer_live_calls.end_call(conn, cache, current_time, lcr)

            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            call_sid = request.data[Twilio.var_ParentCallSid] if Twilio.var_ParentCallSid in request.data\
                else request.data[Twilio.var_CallSid]
            lcr = syncer_live_calls.get_live_call(cache, call_sid)
            if lcr is not None:
                syncer_live_calls.end_call(conn, cache, times.get_current_timestamp(), lcr)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def check_twilio_ending(request, conn=None, cache=None):
    '''
    Checks if the user left a message or hangup when prompted for a voicemail.
    This will allow us to end the call if the user hangs up.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            current_time = times.get_current_timestamp()

            call_sid = request.data[Twilio.var_CallSid]
            twilio_call_status = request.data[Twilio.var_CallStatus] if Twilio.var_CallStatus in request.data else None

            logging.info('Checking status of the call after ending note is played for CallSid {0}'.format(call_sid))

            lcr = syncer_live_calls.get_live_call(cache, call_sid)

            # End the call if the user ends the call before the voicemail recording can start
            if twilio_call_status == Twilio.call_status_completed:
                resp = lcr.twilio_hangup()
                if lcr.call_status != constants.answered_state:
                    lcr.call_status = constants.no_answer_state
                syncer_live_calls.end_call(conn, cache, current_time, lcr)
            else:
                if lcr.record_voicemail:
                    resp = lcr.twilio_voicemail()
                    syncer_live_calls.prompt_for_voicemail(conn, cache, current_time, lcr)
                    Thread(target=syncer_live_calls.store_live_call, args=(cache, lcr)).start()
                else:
                    resp = lcr.twilio_hangup()
                    if lcr.call_status != constants.answered_state:
                        lcr.call_status = constants.no_answer_state
                    syncer_live_calls.end_call(conn, cache, current_time, lcr)

            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            lcr = syncer_live_calls.get_live_call(cache, request.data[Twilio.var_CallSid])
            if lcr is not None:
                syncer_live_calls.end_call(conn, cache, times.get_current_timestamp(), lcr)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def process_twilio_voicemail(request, conn=None, cache=None):
    '''
    Process voicemail details provided by Twilio.
    :param request: HttpRequest
    :param conn: db connection
    :param cache: cache client
    :return: HttpResponse
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            current_time = times.get_current_timestamp()
            call_sid = request.data[Twilio.var_ParentCallSid]\
                if Twilio.var_ParentCallSid in request.data else request.data[Twilio.var_CallSid]
            recording_url = request.data[Twilio.var_RecordingUrl]

            logging.info('Received live call routing voicemail for CallSid {0}.'.format(call_sid))
            lcr = db_live_call_routing.get_live_call(conn, current_time, constants.twilio, call_sid)

            lcr.call_status = constants.message_left_state
            syncer_live_calls.end_call(conn, cache, current_time, lcr, recording_url)

            resp = lcr.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            call_sid = request.data[Twilio.var_ParentCallSid] if Twilio.var_ParentCallSid in request.data\
                else request.data[Twilio.var_CallSid]
            lcr = syncer_live_calls.get_live_call(cache, call_sid)
            if lcr is not None:
                syncer_live_calls.end_call(conn, cache, times.get_current_timestamp(), lcr)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def handle_twilio_outgoing_call_outcome(request, conn=None, cache=None):
    '''
    Outbound call outcomes are handled by this view.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            current_time = times.get_current_timestamp()

            is_intermediate_event = False
            if Twilio.var_ParentCallSid in request.data:
                is_intermediate_event = True
                call_sid = request.data[Twilio.var_ParentCallSid]
            else:
                call_sid = request.data[Twilio.var_CallSid]

            twilio_call_status = request.data[Twilio.var_CallStatus]
            twilio_call_duration = int(request.data[Twilio.var_CallDuration]) \
                if Twilio.var_CallDuration in request.data else None

            logging.info('Processing outgoing call response for CallSid {0}. Received call status - {1}'.format(
                call_sid, twilio_call_status))
            if Twilio.var_DialCallStatus in request.data:
                logging.info('Dial call status - ' + request.data[Twilio.var_DialCallStatus])

            lcr = syncer_live_calls.get_live_call(cache, call_sid)
            logging.info(lcr.to_dict())
            resp = LiveCallRouting.twilio_empty()

            if lcr is None:
                resp = LiveCallRouting.twilio_hangup()
            else:
                if is_intermediate_event:
                    if twilio_call_status == Twilio.call_status_in_progress:
                        logging.info('Outgoing call in progress. No action taken.')

                    elif twilio_call_status == Twilio.call_status_completed:
                        logging.info('Outgoing call dial status - completed. No action taken. Wait for call to end.')
                else:
                    lcr.call_duration = twilio_call_duration
                    resp = lcr.twilio_hangup()
                    syncer_live_calls.end_call(conn, cache, current_time, lcr)

            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            call_sid = request.data[Twilio.var_ParentCallSid] if Twilio.var_ParentCallSid in request.data\
                else request.data[Twilio.var_CallSid]
            lcr = syncer_live_calls.get_live_call(cache, call_sid)
            if lcr is not None:
                syncer_live_calls.end_call(conn, cache, times.get_current_timestamp(), lcr)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def process_twilio_call_recording(request, conn=None):
    '''
    Process call recording details provided by Twilio.
    :param request: HttpRequest
    :param conn: db connection
    :return: HttpResponse
    '''
    if request.method == 'POST':
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            call_sid = request.data[Twilio.var_ParentCallSid]\
                if Twilio.var_ParentCallSid in request.data else request.data[Twilio.var_CallSid]
            recording_url = request.data[Twilio.var_RecordingUrl]

            logging.info('Received live call recording for CallSid {0}.'.format(call_sid))
            db_live_call_routing.add_call_recording(conn, constants.twilio, call_sid, recording_url)

            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml)
        except Exception as e:
            logging.info(str(e))
            logging.exception(request.data)
            resp = LiveCallRouting.twilio_hangup()
            return HttpResponse(resp, content_type=constants.content_type_xml, status=200)
        finally:
            CONN_POOL.put_db_conn(conn)
