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

from cache_queries import cache_task_instances
from data_syncers import syncer_task_instances
from dbqueries import db_integrations, db_task_instances, db_users
from dbqueries.integrations import db_servicenow
from exceptions.user_exceptions import InvalidRequest, UnauthorizedRequest
from integrations import servicenow
from modules.router import Router
from objects.events import CustomActionEvent
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 constants, errors, info, integration_type_names as intt, helpers, logging, permissions,\
    times, tokenizer, url_paths, var_names
from utils.db_connection import CACHE_CLIENT, CONN_POOL
from validations import request_validator
import configuration
import datetime
import jwt


@api_view(['POST'])
def exists_servicenow_integration(request, conn=None):
    '''
    Checks if a ServiceNow integration exists or not.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    :errors: AssertionError, DatabaseError
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [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)

            srv_inst = request.data[var_names.vendor_endpoint]
            if permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_ITSM_PERMISSION):
                existing_integs = db_servicenow.servicenow_integration_exists(
                    conn, times.get_current_timestamp(), org_id, srv_inst)
                return Response(existing_integs)
            else:
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
        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 ConnectionRefusedError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_servicenow_connection, 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_servicenow_meta_data(request, conn=None):
    '''
    Get the available ServiceNow assignment groups.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        optional_fields = [var_names.vendor_endpoint, var_names.username, var_names.password, var_names.integration_key]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, [], optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            srv_host = request.data[var_names.vendor_endpoint] if var_names.vendor_endpoint in request.data else None
            srv_admin = request.data[var_names.username] if var_names.username in request.data else None
            srv_pwd = request.data[var_names.password] if var_names.password in request.data else None
            integ_key = request.data[var_names.integration_key] if var_names.integration_key in request.data else None

            if srv_host is None and integ_key is None:
                raise InvalidRequest(errors.err_invalid_request)

            current_time = times.get_current_timestamp()
            if permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_ITSM_PERMISSION):
                if permissions.is_user_admin(user_perm):
                    # Get the ServiceNow admin user details if it is not present.
                    if integ_key is not None or\
                            (srv_host is not None and (srv_admin is None or srv_pwd is None)):
                        srv_now_det = db_servicenow.get_servicenow_instance_details(
                            conn, current_time, org_id, integration_key=integ_key, host_address=srv_host)
                        srv_host = srv_now_det[var_names.vendor_endpoint]
                        srv_admin = srv_now_det[var_names.username]
                        srv_pwd = srv_now_det[var_names.password]

                    # get the projects
                    status, output = servicenow.servicenow_get_request(
                        servicenow.url_assignment_groups.format(srv_host), srv_admin, srv_pwd
                    )
                    if status == 200:
                        final_output = {var_names.vendor_endpoint: srv_host, var_names.username: srv_admin}
                        if len(output['result']) > 0:
                            final_output[servicenow.var_assignment_group] =\
                                [[x['name'], x['sys_id']] for x in output['result']]
                        return Response(final_output)
                    else:
                        return Response(output, status=status)
                else:
                    return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
            else:
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
        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 ConnectionRefusedError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_servicenow_connection, 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 update_servicenow_credentials(request, conn=None):
    '''
    Update a ServiceNow admin credentials. Ensure that an existing ServiceNow details exist.
    :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.integration_key, var_names.username, var_names.password]
        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 permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_ITSM_PERMISSION):
                if permissions.is_user_admin(user_perm):
                    return Response(db_servicenow.update_servicenow_credentials(
                        conn, times.get_current_timestamp(), org_id, request.data[var_names.integration_key],
                        request.data[var_names.username], request.data[var_names.password]
                    ))
                else:
                    return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
            else:
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
        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 process_incoming_webhook(request, integration_key, action, conn=None, cache=None):
    '''
    Handle actions generated by TaskCall's ServiceNow app. Actions taken manually by a user. E.g. add responders, etc.
    :param request: Http request
    :param integration_key: integration key passed in the url
    :param action: name of the action
    :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

            current_time = times.get_current_timestamp()
            retrieved_integration_information = db_integrations.get_integration_details(
                conn, current_time, integration_key, intt.servicenow)
            if retrieved_integration_information is None:
                logging.error(errors.err_integration_not_found)
                return Response(_lt.get_label(errors.err_integration_not_found, lang), status=404)
            else:
                org_id, org_perm, serv_id, integ_id, integ_type_id, integ_details = retrieved_integration_information

            sn_email = request.data[var_names.email] if var_names.email in request.data else None
            tc_uid = integ_details[var_names.default_user]
            if sn_email is not None:
                user_search = db_users.get_user(conn, current_time, keyed_on=var_names.email,
                                                organization_id=org_id, email=sn_email)
                if len(user_search) > 0:
                    tc_uid = user_search[sn_email].user_id
            org_id, user_perm, org_perm = db_users.get_user_token_details(conn, current_time, tc_uid)[:3]
            token_exp = times.get_current_timestamp() + datetime.timedelta(minutes=1)
            user_token = tokenizer.create_token(tc_uid, org_id, user_perm, org_perm, token_exp)
            url = None

            body = request.data
            body[var_names.access_method] = constants.integrations_api
            if var_names.email in body:
                del body[var_names.email]

            if action == servicenow.cmd_create_incident:
                tc_srv, tc_pol = servicenow.get_taskcall_service_and_policy(
                    integ_details, request.data[servicenow.var_assignment_group])
                sn_inc_id = request.data[servicenow.var_sys_id] if servicenow.var_sys_id in request.data else None
                sn_addn_info = {var_names.display_name: request.data[servicenow.var_number]}\
                    if servicenow.var_number in request.data else None
                title = request.data[servicenow.var_short_description]
                description = request.data[servicenow.var_description]
                urgency = servicenow.get_taskcall_urgency(integ_details, request.data[servicenow.var_urgency])

                payload = TaskPayload(
                    current_time, org_id, current_time.date(), title, configuration.standard_timezone,
                    current_time.time(), text_msg=description, urgency_level=urgency,
                    trigger_method=constants.integrations_api, trigger_info=request.data, integration_id=integ_id,
                    service_id=tc_srv, assignees=[tc_pol] if tc_pol is not None else None, dedup_key=intt.servicenow
                )
                inst_id = Router(conn, cache, payload).run()

                if inst_id is None:
                    return Response(errors.err_internal_multiple_entries_found, status=409)
                else:
                    event = CustomActionEvent(
                        inst_id, current_time, constants.internal, integration_id=integ_id,
                        integration_type_id=integ_type_id, vendor_id=sn_inc_id, vendor_url=None,
                        additional_info=sn_addn_info, is_synced=True,
                        configuration_name=integ_details[var_names.configuration_name]
                    )
                    syncer_task_instances.execute_custom_action(conn, cache, event, org_id=org_id, is_sys_action=True)
                    inst_obj = cache_task_instances.get_single_instance(cache, inst_id)
                    if inst_obj is not None:
                        org_inst_id = inst_obj.organization_instance_id
                    else:
                        org_inst_id = db_task_instances.get_organization_instance_id_of_instance(conn, inst_id)
                    resp_data = {var_names.instance_id: inst_id, var_names.organization_instance_id: org_inst_id}
                    return Response(resp_data)
            else:
                if action == servicenow.cmd_acknowledge:
                    url = url_paths.incidents_acknowledge

                elif action == servicenow.cmd_add_conference_bridge:
                    url = url_paths.incidents_add_conference_bridge

                elif action == servicenow.cmd_add_responders:
                    url = url_paths.incidents_add_responders

                elif action == servicenow.cmd_list_conference_bridges:
                    body = {var_names.data_type: [var_names.conference_bridges]}
                    url = url_paths.list_maker

                elif action == servicenow.cmd_list_policies:
                    body = {var_names.data_type: [var_names.policies, var_names.users]}
                    url = url_paths.list_maker

                elif action == servicenow.cmd_list_workflows:
                    body = {var_names.data_type: [var_names.workflows]}
                    url = url_paths.list_maker

                elif action == servicenow.cmd_reassign:
                    url = url_paths.incidents_reassign

                elif action == servicenow.cmd_resolve:
                    body[var_names.skip_syncing] = [intt.servicenow]
                    if var_names.instance_id not in body and servicenow.var_servicenow_incident_id in body:
                        synced_insts = db_integrations.get_vendor_synced_open_instance_ids(
                            conn, current_time, org_id, integ_id, integ_type_id,
                            body[servicenow.var_servicenow_incident_id])
                        if len(synced_insts) > 0:
                            body[var_names.instance_id] = synced_insts[0][0]
                            del body[servicenow.var_servicenow_incident_id]
                    url = url_paths.incidents_resolve

                elif action == servicenow.cmd_run_workflow:
                    url = url_paths.incidents_run_workflow

                elif action == servicenow.cmd_update_status:
                    url = url_paths.incidents_update_status

                if url is None:
                    return Response(_lt.get_label(errors.err_invalid_request, lang), status=401)
                else:
                    status, output = helpers.post_api_request(url, body, user_token)
                    return Response(output, status=status)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_invalid_request, lang), status=400)
        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)
