# By: Riasat Ullah
# This file contains views related to the jira integration.

from data_syncers import syncer_task_instances
from dbqueries import db_integrations
from dbqueries.integrations import db_jira
from exceptions.user_exceptions import InvalidRequest, UnauthorizedRequest
from integrations import jira_cloud
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 update_jira_account_tokens(request, conn=None):
    '''
    Checks if a given Jira account is already mapped to a given TaskCall organization or not.
    This is an internal check. Only TaskCall web server should have access to this process.
    :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.external_id, 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)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_SECONDARY_PERMISSION):
                if permissions.is_user_admin(user_perm):
                    return Response(db_jira.update_jira_account_tokens(
                        conn, times.get_current_timestamp(), org_id, request.data[var_names.external_id],
                        request.data[var_names.access_token], request.data[var_names.refresh_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 get_jira_meta_data(request, conn=None):
    '''
    Get the the available Jira projects and issue types.
    :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.external_id, var_names.access_token, var_names.refresh_token,
                           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)

            cloud_id = request.data[var_names.external_id] if var_names.external_id in request.data else None
            access_token = request.data[var_names.access_token] if var_names.access_token in request.data else None
            refresh_token = request.data[var_names.refresh_token] if var_names.refresh_token in request.data else None
            integ_key = request.data[var_names.integration_key] if var_names.integration_key in request.data else None

            if cloud_id 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_SECONDARY_PERMISSION):
                if permissions.is_user_admin(user_perm):
                    if integ_key is not None or\
                            (cloud_id is not None and (access_token is None or refresh_token is None)):
                        jira_req_det = db_jira.get_jira_request_acceptance_details(
                            conn, current_time, org_id, integration_key=integ_key, cloud_id=cloud_id)
                        cloud_id = jira_req_det[var_names.external_id]
                        access_token = jira_req_det[var_names.access_token]
                        refresh_token = jira_req_det[var_names.refresh_token]

                    fmt_projects, fmt_issue_types, fmt_statuses = [], dict(), dict()

                    # get the projects
                    pro_sts, avl_projects = jira_cloud.jira_get_api_request(
                        conn, current_time, org_id, cloud_id, jira_cloud.url_projects_search.format(cloud_id),
                        access_token, refresh_token
                    )
                    if pro_sts == 200:
                        for item in avl_projects['values']:
                            fmt_projects.append({var_names.name: item['name'], var_names.item_id: item['id']})

                    # get the issue types
                    iss_sts, avl_issues = jira_cloud.jira_get_api_request(
                        conn, current_time, org_id, cloud_id, jira_cloud.url_issue_types.format(cloud_id),
                        access_token, refresh_token
                    )
                    if iss_sts == 200:
                        for item in avl_issues:
                            if 'scope' in item and 'type' in item['scope'] and item['scope']['type'] == 'PROJECT':
                                key = item['scope']['project']['id']
                            else:
                                key = var_names.default

                            if key not in fmt_issue_types:
                                fmt_issue_types[key] = []

                            if item['hierarchyLevel'] >= 0:
                                fmt_issue_types[key].append({
                                    var_names.name: item['name'],
                                    var_names.item_id: item['id'],
                                    var_names.url: item['iconUrl'],
                                    var_names.issue_type_hierarchy: item['hierarchyLevel']
                                })

                    # get the statuses
                    sta_sts, avl_statuses = jira_cloud.jira_get_api_request(
                        conn, current_time, org_id, cloud_id, jira_cloud.url_status_list.format(cloud_id),
                        access_token, refresh_token
                    )
                    if sta_sts == 200:
                        for item in avl_statuses:
                            if 'scope' in item and 'type' in item['scope'] and item['scope']['type'] == 'PROJECT':
                                key = item['scope']['project']['id']
                            else:
                                key = var_names.default

                            if key not in fmt_statuses:
                                fmt_statuses[key] = []

                            fmt_statuses[key].append({
                                var_names.name: item['name'],
                                var_names.item_id: item['id']
                            })

                    return Response([fmt_projects, fmt_issue_types, fmt_statuses])
                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, jira_cloud_id, conn=None, cache=None):
    '''
    Processes the incoming webhook from Jira.
    :param request: Http request
    :param jira_cloud_id: Jira cloud ID
    :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

            # alert identifier keys
            issue_body = request.data['issue']
            issue_id = issue_body['id']
            issue_key = issue_body['key']
            issue_type = issue_body['fields']['issuetype']['name']
            project_name = issue_body['fields']['project']['name']
            issue_priority = issue_body['fields']['priority']['id']
            issue_status = issue_body['fields']['status']['id']
            issue_summary = issue_body['fields']['summary']
            issue_description = issue_body['fields']['description']
            webhook_id = request.data['matchedWebhookIds'][0]

            # create the issue payload to be saved in TaskCall
            issue_payload = {
                'issue': {
                    'id': issue_id,
                    'key': issue_key,
                    'issue_type': issue_type,
                    'project_name': project_name,
                    'priority': issue_priority,
                    'status': issue_status,
                    'summary': issue_summary,
                    'description': issue_description
                }
            }

            # get the integration details
            current_time = times.get_current_timestamp()
            org_id, serv_id, integ_id, integ_type_id, integ_details = db_jira.get_jira_webhook_details(
                conn, current_time, jira_cloud_id, webhook_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]
                for key in urg_map:
                    if urg_map[key] == issue_priority:
                        urgency = int(key)
                        break

            status = constants.open_state
            if var_names.status in integ_details:
                stat_map = integ_details[var_names.status]
                for key in stat_map:
                    if stat_map[key] == issue_status:
                        status = key
                        break

            inc_title = issue_type + ': ' + issue_summary + '(' + project_name + ')'
            synced_insts = db_integrations.get_vendor_synced_open_instance_ids(
                conn, current_time, org_id, integ_id, integ_type_id, issue_key)

            # Create a new task if it is a new issue.
            if len(synced_insts) == 0:
                payload = TaskPayload(
                    current_time, org_id, current_time.date(), inc_title, configuration.standard_timezone,
                    current_time.time(), text_msg=issue_description, urgency_level=urgency,
                    trigger_method=constants.integrations_api, trigger_info=issue_payload, 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=issue_key, 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)
            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 Jira issue received with state - ' + issue_status + '. No action taken.'
                        logging.info(msg)

            return Response(info.msg_internal_success)
        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)


@api_view(['POST'])
def process_incoming_webhook_with_integration_key(request, integration_key, conn=None, cache=None):
    '''
    Processes the incoming webhook from Jira.
    :param request: Http request
    :param integration_key: the unique key associated to the integration
    :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

            # alert identifier keys
            issue_body = request.data['issue']
            issue_id = issue_body['id']
            issue_key = issue_body['key']
            issue_type = issue_body['fields']['issuetype']['name']
            project_name = issue_body['fields']['project']['name']
            issue_priority = issue_body['fields']['priority']['id']
            issue_status = issue_body['fields']['status']['id']
            issue_summary = issue_body['fields']['summary']
            issue_description = issue_body['fields']['description']

            # create the issue payload to be saved in TaskCall
            issue_payload = {
                'issue': {
                    'id': issue_id,
                    'key': issue_key,
                    'issue_type': issue_type,
                    'project_name': project_name,
                    'priority': issue_priority,
                    'status': issue_status,
                    'summary': issue_summary,
                    'description': issue_description
                }
            }

            # get the integration details
            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.jira_cloud)

            # 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]
                for key in urg_map:
                    if urg_map[key] == issue_priority:
                        urgency = int(key)
                        break

            status = constants.open_state
            if var_names.status in integ_details:
                stat_map = integ_details[var_names.status]
                for key in stat_map:
                    if stat_map[key] == issue_status:
                        status = key
                        break

            inc_title = issue_type + ': ' + issue_summary + '(' + project_name + ')'
            synced_insts = db_integrations.get_vendor_synced_open_instance_ids(
                conn, current_time, org_id, integ_id, integ_type_id, issue_key)

            # Create a new task if it is a new issue.
            if len(synced_insts) == 0:
                dedup_key = intt.jira_cloud + '_' + str(issue_id)
                payload = TaskPayload(
                    current_time, org_id, current_time.date(), inc_title, configuration.standard_timezone,
                    current_time.time(), text_msg=issue_description, urgency_level=urgency,
                    trigger_method=constants.integrations_api, trigger_info=issue_payload, integration_id=integ_id,
                    service_id=serv_id, dedup_key=dedup_key
                )
                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=issue_key, 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)
            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 Jira issue received with state - ' + issue_status + '. No action taken.'
                        logging.info(msg)

            return Response(info.msg_internal_success)
        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)
