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

from data_syncers import syncer_task_instances
from dbqueries import db_events, db_integrations, db_users
from dbqueries.integrations import db_zendesk
from exceptions.user_exceptions import InvalidRequest, LockedAccount, NoOneIsOnCall, UnauthorizedRequest
from integrations import zendesk
from modules.router import Router
from objects.events import AcknowledgeEvent, CustomActionEvent, ResolveEvent
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, logging, permissions,\
    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 get_zendesk_accounts(request, conn=None):
    '''
    Get the Zendesk accounts 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)
        optional_fields = [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)

            integ_key = request.data[var_names.integration_key] if var_names.integration_key in request.data else None
            if permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_CUSTOMER_SERVICE_PERMISSION):
                if permissions.is_user_admin(user_perm):
                    return Response(db_zendesk.get_zendesk_accounts(conn, times.get_current_timestamp(),
                                                                    org_id, integ_key))
                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 update_zendesk_account_token(request, conn=None):
    '''
    Update Zendesk account token. Ensure that the account exists in the database first.
    :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.vendor_endpoint_name, var_names.access_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)

            if permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_CUSTOMER_SERVICE_PERMISSION):
                if permissions.is_user_admin(user_perm):
                    return Response(db_zendesk.update_zendesk_account_token(
                        conn, times.get_current_timestamp(), org_id, request.data[var_names.vendor_endpoint_name],
                        request.data[var_names.access_token]
                    ))
                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 login_zendesk_user(request, conn=None):
    '''
    Allow and process login from a Zendesk user.
    :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.subdomain]
        try:
            request_validator.validate_fields(request, expected_fields)
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            subdomain = request.data[var_names.subdomain]
            current_time = times.get_current_timestamp()
            if permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_CUSTOMER_SERVICE_PERMISSION):
                curr_zendesk_accounts = db_zendesk.get_zendesk_accounts(conn, current_time, org_id)
                if subdomain in curr_zendesk_accounts:

                    # Get the end time of the refresh token.
                    org_id, user_perm, org_perm, org_end, display_name, user_lang, photo_url =\
                        db_users.get_user_token_details(conn, current_time, user_id)

                    # Get the org level permissions specifically for the TaskCall Zendesk app
                    org_perm = permissions.create_advanced_customer_support_access_token_permissions(org_perm)

                    # Get the expiry dates for the access and refresh tokens.
                    access_end = tokenizer.new_access_token_expiry_date(org_end)
                    refresh_end = tokenizer.new_refresh_token_expiry_date(intt.zendesk)

                    access_token = tokenizer.create_token(user_id, org_id, user_perm, org_perm, access_end)
                    refresh_token = tokenizer.create_refresh_token(user_id, org_id, intt.zendesk, refresh_end)

                    db_users.save_authentication_tokens(conn, current_time, access_end, user_id, org_id, intt.zendesk,
                                                        access_token, refresh_token, None, None)
                    response_data = {
                        var_names.access_token: access_token,
                        var_names.expires_on: access_end,
                        var_names.refresh_token: refresh_token,
                        var_names.valid_end: refresh_end,
                        var_names.display_name: display_name,
                        var_names.language: user_lang,
                    }
                    return Response(response_data)
                else:
                    return Response(_lt.get_label(errors.err_zendesk_integration_not_found, lang), status=400)
            else:
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
        except (InvalidRequest, KeyError, LookupError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except LockedAccount 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 logout_zendesk_user(request, conn=None):
    '''
    Logs out a user from their TaskCall Zendesk app.
    :param request: Http request
    :param conn: db connection
    :return: Http response -> str
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.access_token]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            token = request.data[var_names.access_token]
            user_id = tokenizer.read_token(token)[0]
            db_users.delete_authentication_tokens(conn, times.get_current_timestamp(), user_id, token)
            return Response(_lt.get_label(info.msg_success, lang))
        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 refresh_zendesk_access_token(request, conn=None):
    '''
    Refreshes access by issuing a new access token for a Zendesk user.
    :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.access_token, var_names.refresh_token]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            refresh_token = request.data[var_names.refresh_token]

            user_id, org_id, access_method = tokenizer.read_refresh_token(request.data[var_names.refresh_token])
            current_time = times.get_current_timestamp()

            if access_method == intt.zendesk:
                org_id, user_perm, org_perm, org_end, display_name, user_lang, photo_url =\
                    db_users.get_user_token_details(conn, current_time, user_id)

                if permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_CUSTOMER_SERVICE_PERMISSION):

                    # Get the expiry date for the access token.
                    access_end = tokenizer.new_access_token_expiry_date(org_end)
                    access_token = tokenizer.create_token(user_id, org_id, user_perm, org_perm, access_end)

                    db_users.save_authentication_tokens(conn, current_time, access_end, user_id, org_id, access_method,
                                                        access_token, refresh_token, None, None)

                    response_data = {var_names.access_token: access_token,
                                     var_names.expires_on: access_end,
                                     var_names.refresh_token: refresh_token}
                    return Response(response_data)
                else:
                    return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            else:
                raise UnauthorizedRequest(errors.err_authorization)
        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 create_instance_from_panel(request, conn=None, cache=None):
    '''
    Create an instance manually from the Zendesk TaskCall panel.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: JSON response
    '''
    if request.method == 'POST':
        expected_fields = [var_names.title, var_names.description, var_names.service_ref_id, var_names.urgency_level,
                           var_names.vendor_id]

        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

            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_INTEGRATION_CUSTOMER_SERVICE_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)

            if not permissions.has_user_permission(user_perm, permissions.USER_INCIDENTS_CREATE_PERMISSION):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            current_time = times.get_current_timestamp()
            title = request.data[var_names.title]
            description = request.data[var_names.description] if var_names.description in request.data else None
            serv_ref_id = request.data[var_names.service_ref_id] if var_names.service_ref_id in request.data else None
            urgency = request.data[var_names.urgency_level]\
                if var_names.urgency_level in request.data else configuration.default_urgency_level
            ticket_id = str(request.data[var_names.vendor_id]) if var_names.vendor_id in request.data else None

            if (serv_ref_id is not None and not isinstance(serv_ref_id, str)) or\
                    (urgency is not None and not isinstance(urgency, int)):
                raise InvalidRequest(errors.err_invalid_request)

            source_payload = request.data
            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=source_payload, service_ref_id=serv_ref_id, dedup_key=intt.zendesk)
            inst_id = Router(conn, cache, payload).run()

            # Link the incident to the ticket
            db_events.link_instance(conn, current_time, org_id, inst_id, intt.zendesk, ticket_id, user_id)
            resp_data = {
                var_names.instance_id: inst_id,
                var_names.timestamp: current_time
            }
            return Response(resp_data)
        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 NoOneIsOnCall as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=412)
        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_incoming_webhook(request, integration_key, conn=None, cache=None):
    '''
    Processes the incoming webhook from Zendesk.
    :param request: Http request
    :param integration_key: integration key passed in the url
    :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

            if zendesk.var_ticket_id in request.data:
                tkt_id = str(request.data[zendesk.var_ticket_id])
                tkt_title = request.data[zendesk.var_title]
                tkt_desc = request.data[zendesk.var_description]
                tkt_state = request.data[zendesk.var_status]
                tkt_priority = request.data[zendesk.var_priority].lower()

                current_time = times.get_current_timestamp()
                org_id, org_perm, serv_id, integ_id, integ_type_id, integ_details =\
                    db_integrations.get_integration_details(conn, current_time, integration_key, intt.zendesk)

                synced_insts = db_integrations.get_vendor_synced_open_instance_ids(
                    conn, current_time, org_id, integ_id, integ_type_id, tkt_id)

                # internalize attributes sent in the webhook
                urgency = constants.critical_urgency
                if var_names.urgency_level in integ_details:
                    urg_map = integ_details[var_names.urgency_level]
                    if urg_map is not None:
                        for key in urg_map:
                            if urg_map[key] == tkt_priority:
                                urgency = int(key)
                                break

                status = constants.open_state
                if var_names.status in integ_details:
                    stat_map = integ_details[var_names.status]
                    if stat_map is not None:
                        for key in stat_map:
                            if stat_map[key] == tkt_state:
                                status = key
                                break

                # Create a new task if it is a new issue.
                if len(synced_insts) == 0:
                    payload = TaskPayload(
                        current_time, org_id, current_time.date(), tkt_title, configuration.standard_timezone,
                        current_time.time(), text_msg=tkt_desc, urgency_level=urgency,
                        trigger_method=constants.integrations_api, trigger_info=request.data, integration_id=integ_id,
                        service_id=serv_id
                    )
                    inst_id = Router(conn, cache, payload).run()

                    event = CustomActionEvent(
                        inst_id, current_time, constants.internal, integration_id=integ_id,
                        integration_type_id=integ_type_id, vendor_id=tkt_id, vendor_url=None, 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)

                    db_events.link_instance(conn, current_time, org_id, inst_id, intt.zendesk, tkt_id)
                else:
                    for inst_id, task_id, in_sync in synced_insts:
                        if status == constants.acknowledged_state:
                            event = AcknowledgeEvent(inst_id, current_time, constants.integrations_api, None)
                            syncer_task_instances.acknowledge(conn, cache, event, org_id, is_sys_action=True)
                        elif status == constants.resolved_state:
                            event = ResolveEvent(inst_id, current_time, constants.integrations_api)
                            syncer_task_instances.resolve(conn, cache, event, org_id, is_sys_action=True)
                        else:
                            msg = 'Already synced Zendesk ticket received with state - ' + tkt_state +\
                                  '. No action taken.'
                            logging.info(msg)

                return Response(info.msg_internal_success)
            else:
                return Response(_lt.get_label(errors.err_invalid_request, lang), status=400)
        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)
