# By: Riasat Ullah
# This file contains code for handling Zendesk integration related views.

from constants import api_paths, component_names as cnm, label_names as lnm, pages, static_vars, url_paths, var_names
from context_manager import integrations_context
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from integrations import zendesk
from taskcallweb import settings
from translators import label_translator as lt
from utils import helpers, logging
from validations import request_validator
import datetime
import json
import requests


@require_http_methods(['POST'])
def initiate_zendesk_integration(request):
    '''
    Initiates the Zendesk integration process. Stores the integration details in the session,
    forms the oauth url
    :param request: Http request
    :return: Json response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        if request_validator.user_in_session(request):
            body = json.loads(request.body.decode())
            if var_names.integration_name in body and var_names.vendor_endpoint_name in body:
                request.session[var_names.services] = body

                zd_subdomain = body[var_names.vendor_endpoint_name]
                serv_ref = body[var_names.service_ref_id]
                zd_oauth_url = zendesk.get_zendesk_oauth_path(zd_subdomain, serv_ref)
                return JsonResponse(zd_oauth_url, safe=False)
            else:
                return JsonResponse(lt.get_label(lnm.err_invalid_request, lang), status=401, safe=False)
        else:
            return JsonResponse(lt.get_label(lnm.err_unauthorized_access, lang), status=401, safe=False)


@require_http_methods(['GET'])
def authorize_zendesk_integration(request):
    '''
    Redirects to the Zendesk authorization page.
    :param request: Http request
    :return: Http response
    '''
    if request.method == 'GET':
        try:
            if not (
                'code' in request.GET and request_validator.user_in_session(request)
                and var_names.services in request.session
                and var_names.service_ref_id in request.session[var_names.services]
                and request.session[var_names.services][var_names.integration_type] == static_vars.integ_type_zendesk
                and (var_names.integration_name in request.session[var_names.services] or
                     var_names.integration_key in request.session[var_names.services])
            ):
                logging.error('Expected parameter "code" is missing or all service session variables were not found.' +
                              'Denying access to Zendesk integration authorization. Redirecting to login page.')
                return redirect(pages.login_url)
            else:
                lang = request_validator.get_user_language(request)
                code = request.GET['code']
                state = request.GET['state']
                serv_session = request.session[var_names.services]
                srv_ref = serv_session[var_names.service_ref_id]
                zd_subdomain = serv_session[var_names.vendor_endpoint_name]
                zd_acc_exists = False
                new_access_token = None

                if state == srv_ref:
                    # Only if the Zendesk subdomain is not already associated to this organization,
                    # then get  an entry in the organization_integration_type_details. Do not duplicate.
                    curr_acc_status, curr_acc_output = helpers.post_api_request(
                        api_paths.integrations_zendesk_account, dict(), request, lang=lang
                    )
                    if curr_acc_status == 200:
                        if zd_subdomain in curr_acc_output:
                            zd_acc_exists = True
                    else:
                        logging.exception(curr_acc_output)
                        return redirect(url_paths.configurations_services + '/' + srv_ref + '?error=' + curr_acc_output)

                    # Only get an access token if an account does not exist or if it is a reauthorization.
                    if not zd_acc_exists or (zd_acc_exists and var_names.integration_key in serv_session):
                        zd_creds = zendesk.get_zendesk_credentials()
                        oauth_body = {
                            'grant_type': 'authorization_code',
                            'code': code,
                            'client_id': zd_creds[var_names.client_id],
                            'client_secret': zd_creds[var_names.client_secret],
                            'redirect_uri': zendesk.zendesk_tc_redirect_uri,
                            'scope': 'read write'
                        }

                        # Get the Zendesk access token.
                        oauth_resp = requests.post(zendesk.zendesk_oauth_token_path.format(zd_subdomain),
                                                   json=oauth_body)
                        resp_data = oauth_resp.json()
                        if oauth_resp.status_code == 200:
                            new_access_token = resp_data['access_token']

                            # Check if this is a reauthorization. Process as such if it is.
                            if var_names.integration_key in serv_session:
                                upd_post_body = {var_names.vendor_endpoint_name: zd_subdomain,
                                                 var_names.access_token: new_access_token}
                                upd_sts, upd_out = helpers.post_api_request(
                                    api_paths.integrations_zendesk_account_update, upd_post_body, request, lang=lang)
                                if upd_sts == 200:
                                    return redirect(url_paths.configurations_services + '/' + srv_ref +
                                                    '?tab=integrations&message=' +
                                                    lt.get_label(lnm.msg_zendesk_reauthorized, lang))
                                else:
                                    return redirect(url_paths.configurations_services + '/' + srv_ref + '?error=' +
                                                    upd_out)
                        else:
                            return redirect(url_paths.configurations_services + '/' + srv_ref + '?error=' +
                                            str(resp_data))

                    post_body = {
                        var_names.service_ref_id: srv_ref,
                        var_names.integration_type: static_vars.integ_type_zendesk,
                        var_names.integration_name: serv_session[var_names.integration_name],
                        var_names.vendor_endpoint_name: zd_subdomain,
                        var_names.additional_info: serv_session[var_names.additional_info]
                    }
                    if new_access_token is not None:
                        post_body[var_names.external_id] = zd_subdomain
                        post_body[var_names.external_info] = {var_names.access_token: new_access_token}

                    if settings.TEST_MODE:
                        status, output = 200, 'Success'
                    else:
                        status, output = helpers.post_api_request(api_paths.services_integrations_add, post_body,
                                                                  request, lang=lang)

                    if status == 200:
                        return redirect(url_paths.configurations_services + '/' + srv_ref
                                        + '?tab=integrations&message=' + lt.get_label(lnm.msg_zendesk_added, lang))
                    else:
                        return redirect(url_paths.configurations_services + '/' + srv_ref +
                                        '?tab=integrations&error=' + output)

            logging.exception(lnm.err_zendesk_authorization_failed)
            return redirect(url_paths.configurations_services + '/' + srv_ref + '?error=' +
                            lt.get_label(lnm.err_zendesk_authorization_failed, lang))
        except Exception as e:
            logging.exception(str(e))
            return redirect(pages.login_url)
        finally:
            if var_names.services in request.session:
                del request.session[var_names.services]


@require_http_methods(['GET'])
def initiate_zendesk_reauthorization(request):
    '''
    Initiates the reauthorization of Zendesk integration of an organization.
    :param request: Http request
    :return: Http response
    '''
    if request.method == 'GET':
        try:
            if 'service' not in request.GET or 'integration' not in request.GET or 'subdomain' not in request.GET:
                logging.error('Expected parameter "service" or "integration" or "subdomain" is missing. ' +
                              'Denying access to Zendesk reauthorization. Redirecting to login page.')
                return redirect(pages.login_url)
            else:
                srv_ref = request.GET['service']
                integ_key = request.GET['integration']
                zd_subdomain = request.GET['subdomain']

                request.session[var_names.services] = {
                    var_names.service_ref_id: srv_ref,
                    var_names.integration_key: integ_key,
                    var_names.integration_type: static_vars.integ_type_zendesk,
                    var_names.vendor_endpoint_name: zd_subdomain
                }

                zd_oauth_url = zendesk.get_zendesk_oauth_path(zd_subdomain, srv_ref)
                return redirect(zd_oauth_url)
        except Exception as e:
            logging.exception(str(e))
            return redirect(pages.login_url)


@require_http_methods(['POST'])
def get_zendesk_account_subdomain(request):
    '''
    Get the subdomain of the Zendesk account associated to an integration.
    This will be displayed on the Zendesk integration modal.
    :param request: Http request
    :return: JSON response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        if request_validator.user_in_session(request):
            body = json.loads(request.body.decode())
            if settings.TEST_MODE:
                return JsonResponse(['d3v-taskcallapp'], safe=False)
            else:
                status, output = helpers.post_api_request(api_paths.integrations_zendesk_account, body,
                                                          request, lang=lang)
                return JsonResponse(output, status=status, safe=False)
        else:
            return JsonResponse(lt.get_label(lnm.err_unauthorized_access, lang), status=401, safe=False)


@require_http_methods(['GET', 'POST'])
def login_zendesk_user(request):
    '''
    Log in a Zendesk user.
    :param request: Http request
    :return: JSON response
    '''
    if request.method == 'GET':
        lang = request_validator.get_user_language(request)
        if 'state' not in request.GET and 'subdomain' not in request.GET:
            logging.error('Expected parameter "state" or "subdomain" is missing.'
                          'Denying access to Zendesk user log in. Redirecting to login page.')
            context = {
                var_names.error_code: 403,
                var_names.error_message: lt.get_label(lnm.err_invalid_request, lang)
            }
            return render(request, pages.error_page, context=context)
        else:
            # Make sure the request originates from Zendesk.
            # Get the Origin and state, and save them in the session, so they can be accessed from the POST request.
            request.session[zendesk.str_zendesk_ticket_id] = request.GET.get('state')
            request.session[zendesk.str_zendesk_origin] = request.GET.get('subdomain')

            if request_validator.user_in_session(request):
                nav_bar_components = request_validator.get_nav_bar_components(request)
                has_incident_view_perm, has_incident_edit_perm = request_validator.get_session_permission(
                    request, cnm.dis_com_incidents, nav_bar_components
                )
                has_dashboard_view_perm, has_dashboard_edit_perm = request_validator.get_session_permission(
                    request, cnm.dis_com_status_dashboard, nav_bar_components
                )

                if cnm.feat_integrations_customer_service not in request_validator.get_component_features(request):
                    context = {
                        var_names.error_code: 403,
                        var_names.error_message: lt.get_label(lnm.err_zendesk_organization_permission, lang)
                    }
                    return render(request, pages.error_page, context=context)

                if not has_incident_edit_perm or not has_dashboard_view_perm:
                    context = {
                        var_names.error_code: 403,
                        var_names.error_message: lt.get_label(lnm.err_zendesk_user_permission, lang)
                    }
                    return render(request, pages.error_page, context=context)

                context = integrations_context.get_zendesk_user_login_context(lang)
                return render(request, pages.integrations_zendesk_user_login, context=context)
            else:
                helpers.set_session_redirect_page(request)
                return redirect(pages.login_url)

    elif request.method == 'POST':
        lang = request_validator.get_user_language(request)
        if request_validator.user_in_session(request):
            # retrieve the Zendesk ticket id and origin subdomain from the session
            ticket_id = request.session[zendesk.str_zendesk_ticket_id]
            origin = request.session[zendesk.str_zendesk_origin]

            if settings.TEST_MODE:
                status, output = 200, {
                    var_names.access_token: 'access_token',
                    var_names.refresh_token: 'refresh_token',
                    var_names.display_name: 'Adam Smith',
                    var_names.language: 'en',
                    var_names.expires_on: datetime.datetime.now() + datetime.timedelta(days=2),
                    var_names.valid_end: datetime.datetime.now() + datetime.timedelta(days=10)
                }
            else:
                body = {var_names.subdomain: origin}
                status, output = helpers.post_api_request(api_paths.integrations_zendesk_login, body,
                                                          request, lang=lang)

            if status == 200 and zendesk.str_zendesk_origin in request.session\
                    and zendesk.str_zendesk_ticket_id in request.session:

                # Delete the session data that was stored
                del request.session[zendesk.str_zendesk_ticket_id]
                del request.session[zendesk.str_zendesk_origin]

                # redirect the user to Zendesk without completing the JSON request sent from the web app
                url_to_send = zendesk.zendesk_ticket_path_format.format(origin, ticket_id)
                resp = JsonResponse(url_to_send, safe=False)
                resp.set_cookie(zendesk.str_zendesk_access_token, output[var_names.access_token],
                                expires=output[var_names.expires_on], secure=True, httponly=True, samesite='None')
                resp.set_cookie(zendesk.str_zendesk_refresh_token, output[var_names.refresh_token],
                                expires=output[var_names.valid_end], secure=True, httponly=True, samesite='None')
                resp.set_cookie(zendesk.str_zendesk_display_name, output[var_names.display_name],
                                expires=output[var_names.valid_end], secure=True, httponly=True, samesite='None')
                return resp
            else:
                err = output if status != 200 else lt.get_label(lnm.err_processing_failed, lang)
                return JsonResponse(err, status=400, safe=False)
        else:
            return JsonResponse(lt.get_label(lnm.err_unauthorized_access, lang), status=401, safe=False)


@require_http_methods(['GET'])
def logout_zendesk_user(request):
    '''
    Log out a Zendesk user.
    :param request: Http request
    :return: JSON response
    '''
    if request.method == 'GET':
        lang = request_validator.get_user_language(request)
        if 'state' not in request.GET and 'subdomain' not in request.GET:
            logging.error('Expected parameter "state" or "subdomain" is missing.'
                          'Denying Zendesk user logout. Redirecting to login page.')
            context = {
                var_names.error_code: 403,
                var_names.error_message: lt.get_label(lnm.err_invalid_request, lang)
            }
            return render(request, pages.error_page, context=context)
        else:
            # Make sure the request originates from Zendesk.
            # Get the Origin and state, so that the user can be redirected to the same page they were sent from.
            ticket_id = request.GET.get('state')
            origin = request.GET.get('subdomain')

            access_token = None
            if zendesk.str_zendesk_access_token in request.COOKIES:
                access_token = request.COOKIES.get(zendesk.str_zendesk_access_token)

            url_to_send = zendesk.zendesk_ticket_path_format.format(origin, ticket_id)
            resp = redirect(url_to_send)
            resp.delete_cookie(zendesk.str_zendesk_access_token)
            resp.delete_cookie(zendesk.str_zendesk_refresh_token)
            resp.delete_cookie(zendesk.str_zendesk_display_name)

            if access_token is not None and not settings.TEST_MODE:
                helpers.post_api_request(api_paths.integrations_zendesk_logout, dict(), None, lang=lang)
            return resp


@xframe_options_exempt
@require_http_methods(['GET'])
def get_zendesk_taskcall_token(request):
    '''
    Get the access token for a Zendesk user if they are logged in. Otherwise, send 'undefined' as the value.
    :param request: Http request
    :return: Http response
    '''
    if request.method == 'GET':
        access_token, token_expiry, display_name = zendesk.get_token_details_for_zendesk(request)
        context = {
            var_names.token: access_token if access_token is not None else zendesk.str_undefined,
            var_names.display_name: display_name if display_name is not None else ''
        }
        resp = render(request, pages.integrations_zendesk_get_token, context=context)
        if access_token is not None:
            resp.set_cookie(zendesk.str_zendesk_access_token, access_token, token_expiry, secure=True,
                            httponly=True, samesite='None')

        return resp


@csrf_exempt
@require_http_methods(['POST'])
def process_ticket_actions(request, action):
    '''
    Process the actions triggered from the Zendesk TaskCall app.
    :param request: Http request
    :param action: the type of action to perform
    :return: JSON response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        access_token = helpers.extract_token_from_header(request)
        body = json.loads(request.body.decode())
        url = None

        if action == zendesk.cmd_acknowledge:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_acknowledge

        elif action == zendesk.cmd_add_responders:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_add_responders

        elif action == zendesk.cmd_create_incident:
            url = api_paths.integrations_zendesk_create_incident

        elif action == zendesk.cmd_link:
            body[var_names.integration_type] = static_vars.integ_type_zendesk
            url = api_paths.incidents_link

        elif action == zendesk.cmd_linked_incidents:
            body[var_names.integration_type] = static_vars.integ_type_zendesk
            url = api_paths.incidents_linked_incidents

        elif action == zendesk.cmd_list_policies:
            body = {var_names.data_type: [var_names.policies, var_names.users]}
            url = api_paths.org_list_maker

        elif action == zendesk.cmd_list_response_sets:
            body = {var_names.data_type: [var_names.response_sets]}
            url = api_paths.org_list_maker

        elif action == zendesk.cmd_list_services:
            body = {var_names.data_type: [var_names.services]}
            url = api_paths.org_list_maker

        elif action == zendesk.cmd_notate:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_notate

        elif action == zendesk.cmd_reassign:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_reassign

        elif action == zendesk.cmd_resolve:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_resolve

        elif action == zendesk.cmd_run_response_set:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_run_response_set

        elif action == zendesk.cmd_status_dashboard:
            url = api_paths.status_dashboard

        elif action == zendesk.cmd_subscribe:
            body[var_names.access_method] = static_vars.integrations_api
            body[var_names.subscribers] = None
            url = api_paths.incidents_add_subscribers

        elif action == zendesk.cmd_unacknowledge:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_un_acknowledge

        elif action == zendesk.cmd_unlink:
            body[var_names.integration_type] = static_vars.integ_type_zendesk
            url = api_paths.incidents_unlink

        elif action == zendesk.cmd_unsubscribe:
            body[var_names.access_method] = static_vars.integrations_api
            body[var_names.subscribers] = None
            url = api_paths.incidents_remove_subscribers

        elif action == zendesk.cmd_update_status:
            body[var_names.access_method] = static_vars.integrations_api
            url = api_paths.incidents_update_status

        elif action == zendesk.cmd_view_incidents:
            body[var_names.status] = static_vars.open_state
            url = api_paths.incidents_list

        if access_token is None:
            return JsonResponse(lt.get_label(lnm.err_unauthorized_access, lang), status=401, safe=False)
        elif url is None:
            return JsonResponse(lt.get_label(lnm.err_invalid_request, lang), status=401, safe=False)
        else:
            status, output = helpers.send_post_request(url, body, access_token, lang=lang)
            return JsonResponse(output, status=status, safe=False)
