# By: Riasat Ullah
# This file contains all Microsoft Teams integration related views.

from data_syncers import syncer_services
from dbqueries import db_integrations, db_users
from dbqueries.integrations import db_microsoft_teams
from exceptions.user_exceptions import UnauthorizedRequest
from integrations import microsoft_teams as ms_teams
from modules.router import Router
from objects.task_payload import TaskPayload
from rest_framework.decorators import api_view
from rest_framework.response import Response
from translators import label_translator as _lt
from utils import cdn_enablers, constants, errors, info, integration_type_names as intt, logging, permissions, s3,\
    times, tokenizer, var_names
from utils.db_connection import CACHE_CLIENT, CONN_POOL
from validations import request_validator
import configuration
import jwt


@api_view(['POST'])
def authorize_microsoft_teams_integration(request, conn=None):
    '''
    Authorizes a Microsoft Teams integration. After the app installation on Microsoft Teams, users are required to
    authorize the integration to allow us to map it to an organization and service. The users are provided with a
    button which directs them to taskcall web. From there the request is made to this view.
    :param request: Http request
    :param conn: db conn
    :return: JSON response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.token]
        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, region_agnostic=True)

            if permissions.is_user_admin(user_perm):
                current_time = times.get_current_timestamp()
                token_details = db_integrations.get_temporary_verification_token_details(
                    conn, current_time, request.data[var_names.token], intt.microsoft_teams)
                if token_details is not None:
                    return Response(token_details)
                else:
                    return Response(_lt.get_label(errors.err_unknown_resource, lang), status=400)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except PermissionError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=403)
        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 confirm_microsoft_teams_integration(request, conn=None):
    '''
    Confirms that a Microsoft Teams authorization has been confirmed. This will invalidate the temporary token
    that was issued for the authorization process.
    :param request: Http request
    :param conn: db conn
    :return: JSON response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.token, var_names.channel_id, var_names.vendor_endpoint]
        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, region_agnostic=True)

            if permissions.is_user_admin(user_perm):
                current_time = times.get_current_timestamp()
                db_integrations.confirm_temporary_token_verification(
                    conn, current_time, request.data[var_names.token], intt.microsoft_teams)

                # get the Microsoft Teams credentials
                creds = s3.read_json(ms_teams.ms_teams_s3_bucket, ms_teams.ms_teams_s3_key)
                access_token = creds[var_names.access_token]
                bot_id = creds[var_names.bot_id]

                # send a note to the channel saying that the authorization has completed
                ms_teams_api_url = ms_teams.url_proactive_message.format(request.data[var_names.vendor_endpoint])
                message = ms_teams.get_ms_teams_proactive_text(
                    bot_id, request.data[var_names.channel_id],
                    _lt.get_label(info.msg_ms_teams_authorization_succeeded, constants.lang_en)
                )
                status, output = ms_teams.ms_teams_post_api_request(ms_teams_api_url, access_token, message)
                if status not in (200, 201):
                    logging.error(output)

                return Response(_lt.get_label(info.msg_success, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except PermissionError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=403)
        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 process_microsoft_teams_actions(request, conn=None, cache=None):
    '''
    Handles user actions taken on messages from TaskCall's Microsoft Teams app.
    Calls to this function come from requests made directly from Microsoft Teams.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :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
            cache = CACHE_CLIENT if cache is None else cache

            # parse through the payload
            event_type = request.data['type']
            service_url = request.data['serviceUrl']
            tenant_id = request.data['channelData']['tenant']['id']
            team_id = request.data['channelData']['team']['id']\
                if 'team' in request.data['channelData'] else None
            channel_id = request.data['channelData']['channel']['id']\
                if 'channel' in request.data['channelData'] else None
            recipient_id = request.data['from']['id']
            conversation_id = request.data['conversation']['id']
            activity_id = request.data['id']

            # get the Microsoft Teams credentials
            creds = s3.read_json(ms_teams.ms_teams_s3_bucket, ms_teams.ms_teams_s3_key)
            access_token = creds[var_names.access_token]
            bot_id = creds[var_names.bot_id]

            # Installation addition/removal
            if event_type == ms_teams.payload_type_installation_update:
                action_type = request.data['action']
                if action_type == 'add':
                    ms_teams.MsTeamsInstallationWelcomeHandler(
                        conn, access_token, tenant_id, team_id, service_url, bot_id, conversation_id,
                        activity_id, channel_id
                    ).start()
                elif action_type == 'remove':
                    current_time = times.get_current_timestamp()
                    org_id, service_id, integ_id, external_info =\
                        db_microsoft_teams.get_microsoft_teams_integration_details(
                            conn, current_time, tenant_id, channel_id)

                    if service_id is not None and external_info is not None and var_names.users in external_info \
                            and recipient_id in external_info[var_names.users]:
                        taskcall_user_id = external_info[var_names.users][recipient_id]
                        integ_key = db_integrations.get_integration_key_from_id(conn, current_time, integ_id)

                        cdn_key_info = None
                        if channel_id is not None:
                            cdn_key_info = (
                                intt.microsoft_teams,
                                cdn_enablers.create_cdn_cache_key(intt.microsoft_teams, tenant_id, channel_id)
                            )

                        syncer_services.delete_service_integration(
                            conn, cache, current_time, org_id, taskcall_user_id, integ_key,
                            check_adv_perm=False, has_comp_perm=False, has_team_perm=False, cdn_key_info=cdn_key_info
                        )

            elif event_type == ms_teams.payload_type_message:

                if 'text' in request.data:
                    original_msg_list = request.data['text'].strip('\\n').split('</at>')
                    if len(original_msg_list) < 2:
                        ms_teams.MsTeamsTextReplier(
                            access_token, service_url, bot_id, conversation_id, activity_id, channel_id,
                            info.msg_ms_teams_help
                        ).start()
                        logging.error(ms_teams.unknown_command_error)
                        return Response(ms_teams.unknown_command_error, status=200)

                    command = original_msg_list[0].lstrip('<at>')
                    text_list = original_msg_list[1].strip().split()
                    instruction = text_list[0]
                    if command not in [ms_teams.ms_teams_command, ms_teams.ms_teams_bot_command] or\
                            instruction not in ms_teams.ms_teams_valid_instructions:
                        ms_teams.MsTeamsTextReplier(
                            access_token, service_url, bot_id, conversation_id, activity_id, channel_id,
                            info.msg_ms_teams_help
                        ).start()
                        logging.error(ms_teams.unknown_command_error)
                        return Response(ms_teams.unknown_command_error, status=200)
                    else:
                        if instruction == ms_teams.ms_teams_request_authorization:
                            existing_integ = db_microsoft_teams.get_microsoft_teams_integration_details(
                                conn, times.get_current_timestamp(), tenant_id, channel_id)
                            if existing_integ[0] is None:
                                ms_teams.MsTeamsInstallationWelcomeHandler(
                                    conn, access_token, tenant_id, team_id, service_url, bot_id, conversation_id,
                                    activity_id, channel_id
                                ).start()
                            else:
                                message = ms_teams.get_ms_teams_text_reply_body(
                                    bot_id, channel_id, activity_id,
                                    _lt.get_label(info.msg_ms_teams_integration_exists, constants.lang_en))
                                response_url = ms_teams.url_reply_message.format(
                                    service_url, conversation_id, activity_id)
                                status, output = ms_teams.ms_teams_post_api_request(response_url, access_token, message)
                                if status not in (200, 201):
                                    logging.exception(str(output))

                        elif instruction == ms_teams.ms_teams_verify_user_cmd:
                            if len(text_list) != 2:
                                ms_teams.MsTeamsTextReplier(
                                    access_token, service_url, bot_id, conversation_id, activity_id, channel_id,
                                    errors.err_invalid_request
                                ).start()
                            else:
                                taskcall_pref_name = text_list[1]
                                aad_object_id = request.data['from']['aadObjectId']
                                ms_teams.MsTeamsUserVerifier(
                                    conn, access_token, tenant_id, service_url, bot_id, recipient_id, aad_object_id,
                                    conversation_id, activity_id, channel_id, taskcall_pref_name
                                ).start()

                        elif instruction == ms_teams.ms_teams_remove_user_cmd:
                            ms_teams.MsTeamsUserRemover(
                                conn, access_token, tenant_id, service_url, bot_id, recipient_id, conversation_id,
                                activity_id, channel_id
                            ).start()

                        elif instruction == ms_teams.ms_teams_get_user_cmd:
                            ms_teams.MsTeamsUserInfo(
                                conn, access_token, tenant_id, service_url, bot_id, recipient_id,
                                conversation_id, activity_id, channel_id
                            ).start()

                        elif instruction == ms_teams.ms_teams_create_incident_cmd:
                            if len(text_list) < 2:
                                ms_teams.MsTeamsTextReplier(
                                    access_token, service_url, bot_id, conversation_id, activity_id, channel_id,
                                    errors.err_invalid_request
                                ).start()
                            else:
                                incident_title = ' '.join(text_list[1:])
                                response_url = ms_teams.url_reply_message.format(service_url, conversation_id,
                                                                                 activity_id)
                                current_time = times.get_current_timestamp()
                                message = ms_teams.get_ms_teams_text_reply_body(
                                    bot_id, channel_id, activity_id,
                                    _lt.get_label(info.msg_ms_teams_user_unverified_or_integration_missing,
                                                  constants.lang_en))
                                try:
                                    org_id, service_id, integ_id, external_info =\
                                        db_microsoft_teams.get_microsoft_teams_integration_details(
                                            conn, current_time, tenant_id, channel_id)

                                    if service_id is not None and external_info is not None\
                                        and var_names.users in external_info \
                                            and recipient_id in external_info[var_names.users]:
                                        payload = TaskPayload(current_time, org_id, current_time.date(), incident_title,
                                                              configuration.standard_timezone, current_time.time(),
                                                              trigger_method=constants.integrations_api,
                                                              service_id=service_id, integration_id=integ_id)
                                        Router(conn, cache, payload).start()

                                        message = ms_teams.get_ms_teams_text_reply_body(
                                            bot_id, channel_id, activity_id,
                                            _lt.get_label(info.msg_success, constants.lang_en)
                                        )
                                except Exception as e:
                                    logging.exception(str(e))
                                    message = ms_teams.get_ms_teams_text_reply_body(
                                        bot_id, channel_id, activity_id,
                                        _lt.get_label(errors.err_system_error, constants.lang_en))
                                finally:
                                    status, output = ms_teams.ms_teams_post_api_request(
                                        response_url, access_token, message)
                                    if status not in (200, 201):
                                        logging.exception(str(output))

                        else:
                            ms_teams.MsTeamsTextReplier(
                                access_token, service_url, bot_id, conversation_id, activity_id, channel_id,
                                info.msg_ms_teams_help
                            ).start()

                elif 'value' in request.data:
                    action_value = request.data['value']
                    recipient_name = request.data['from']['name']
                    ms_teams.MsTeamsIncidentActionProcessor(
                        conn, access_token, tenant_id, service_url, bot_id, conversation_id, activity_id,
                        channel_id, recipient_id, recipient_name, action_value
                    ).start()

            return Response(_lt.get_label(info.msg_success, lang), status=200)
        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 microsoft_teams_account_meta_data(request, conn=None):
    '''
    Get all the metadata of an organization's Microsoft accounts.
    :param request: Http request
    :param conn: db conn
    :return: JSON 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, region_agnostic=True)

            current_time = times.get_current_timestamp()
            curr_integs = db_microsoft_teams.get_org_microsoft_teams_integrations(conn, current_time, org_id)

            # map the verified user ids to their display name and preferred username before responding
            all_user_ids = []
            for item in curr_integs:
                all_user_ids += list(item[var_names.additional_info][var_names.user_object_id].values())\
                    if var_names.user_object_id in item[var_names.additional_info] else []
            user_mappings = db_users.get_user_name_pref_keyed_on_id(conn, current_time, org_id, list(set(all_user_ids)))

            mapped_integs = []
            for item in curr_integs:
                mapped_integs.append({
                    var_names.service_name: item[var_names.service_name],
                    var_names.integration_name: item[var_names.integration_name],
                    var_names.integration_key: item[var_names.integration_key],
                    var_names.users: [
                        user_mappings[x] for x in item[var_names.additional_info][var_names.user_object_id].values()
                    ] if var_names.user_object_id in item[var_names.additional_info] else []
                })

            return Response(mapped_integs)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except PermissionError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=403)
        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)
