# By: Riasat Ullah
# This file contains all constants and functions related to the Autotask integration.

from dbqueries.integrations import db_autotask
from utils import errors, logging, url_paths, var_names
import json
import requests


# Autotask variable names
str_id = 'id'
str_description = 'description'
str_priority = 'priority'
str_status = 'status'
str_title = 'title'

# Autotask URL paths
url_companies = 'https://{0}/atservicesrest/v1.0/Companies/query'
url_ticket = 'https://{0}/atservicesrest/v1.0/Tickets'
url_ticket_fields = 'https://{0}/atservicesrest/v1.0/tickets/entityinformation/fields'
url_ticket_notes = 'https://{0}/atservicesrest/v1.0/Tickets/{1}/notes'


def get_standard_headers(username, password, tracking_id):
    '''
    Get the standard header params.
    :param username: API user
    :param password: API secret
    :param tracking_id: API user tracking ID
    :return: (dict) of header params
    '''
    return {
        'Content-Type': 'application/json',
        'UserName': username,
        'Secret': password,
        'ApiIntegrationCode': tracking_id
    }


def extract_active_field_values(field_data):
    '''
    Extract the label and values from the pick list of a field.
    :param field_data: (dict) details of the field
    '''
    return [{var_names.name: x['label'], var_names.item_id: x['value']}
            for x in field_data['picklistValues'] if x['isActive']]


def get_global_metadata(acc_domain, api_user, api_password, api_tracking_id):
    '''
    Get global metadata for an Autotask account - ticket categories, ticket types, statuses, priorities, companies.
    :param acc_domain: account domain
    :param api_user: API user
    :param api_password: API secret
    :param api_tracking_id: API user tracking ID
    :return: (list of) list of dict -> [ [{item_id: , name: }, ...], ... ]
    '''
    metadata = get_entity_fields(acc_domain, api_user, api_password, api_tracking_id)
    companies = get_companies(acc_domain, api_user, api_password, api_tracking_id)
    if metadata is not None and companies is not None:
        metadata.append(companies)
        return metadata
    else:
        return None


def get_entity_fields(acc_domain, api_user, api_password, api_tracking_id):
    '''
    Get the label and value for ticket categories, ticket types, statuses and priorities.
    :param acc_domain: account domain
    :param api_user: API user
    :param api_password: API secret
    :param api_tracking_id: API user tracking ID
    :return: (list of dict) -> [{item_id: , name: }, ...]
    '''
    resp = requests.get(url_ticket_fields.format(acc_domain),
                        headers=get_standard_headers(api_user, api_password, api_tracking_id))

    ticket_categories, ticket_types, ticket_queues, statuses, priorities = [], [], [], [], []
    if resp.status_code == 200:
        resp_json = resp.json()
        fields = resp_json['fields']

        for entity in fields:
            if entity['name'] == 'ticketCategory':
                ticket_categories = extract_active_field_values(entity)
            elif entity['name'] == 'ticketType':
                ticket_types = extract_active_field_values(entity)
            elif entity['name'] == 'queueID':
                ticket_queues = extract_active_field_values(entity)
            elif entity['name'] == 'status':
                statuses = extract_active_field_values(entity)
            elif entity['name'] == 'priority':
                priorities = extract_active_field_values(entity)

        return [ticket_categories, ticket_types, ticket_queues, statuses, priorities]
    else:
        return None


def get_companies(acc_domain, api_user, api_password, api_tracking_id):
    '''
    Get the name and ID of companies.
    :param acc_domain: account domain
    :param api_user: API user
    :param api_password: API secret
    :param api_tracking_id: API user tracking ID
    :return: (list of dict) -> [{item_id: , name: }, ...]
    '''
    resp = requests.post(url_companies.format(acc_domain),
                         headers=get_standard_headers(api_user, api_password, api_tracking_id),
                         data=json.dumps({"Filter": [{"field": "Id", "op": "gte", "value": 0}]}))
    if resp.status_code == 200:
        resp_json = resp.json()
        companies = [{var_names.name: x['companyName'], var_names.item_id: x['id']} for x in resp_json['items']]
        return companies
    else:
        return None


def create_autotask_ticket(conn, timestamp, org_id, integ_key, integ_info, org_instance_id, task_title,
                           text_msg, instance_state, urgency, group_id=None):
    '''
    Creates a TeamDynamix ticket.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param org_id: organization ID
    :param integ_key: the integration key
    :param integ_info: additional info of this integration
    :param org_instance_id: organization instance ID
    :param task_title: instance task title that will be used as the summary of the issue
    :param text_msg: instance text details that will be used as the description of the issue
    :param instance_state: the current state of the instance
    :param urgency: the urgency level of the instance
    :param group_id: ID of the group that created the incident
    :return: (str) -> ticket ID (None if ticket cannot be created)
    '''
    au_tsk_det = db_autotask.get_autotask_account_details(conn, timestamp, org_id, integration_key=integ_key)
    if len(au_tsk_det) == 0:
        raise LookupError(errors.err_integration_not_found)

    acc_domain = au_tsk_det[var_names.vendor_endpoint_name]
    api_user = au_tsk_det[var_names.username]
    api_pwd = au_tsk_det[var_names.password]
    api_trk_id = au_tsk_det[var_names.secret_token]

    tkt_category = integ_info[var_names.ticket_category]
    tkt_type = integ_info[var_names.ticket_type]
    tkt_queue_id = integ_info[var_names.queue_id]

    sync_sts_val = get_outgoing_status(instance_state, integ_info)
    tkt_status_id = int(integ_info[var_names.default_status]) if sync_sts_val is None else sync_sts_val

    sync_urg_val = get_outgoing_urgency(urgency, integ_info)
    tkt_priority_id = int(integ_info[var_names.default_urgency]) if sync_urg_val is None else sync_urg_val

    if group_id is not None and var_names.vendors in integ_info and \
            integ_info[var_names.vendors] is not None and group_id in integ_info[var_names.vendors]:
        vnd_id = integ_info[var_names.vendors][group_id]
    else:
        vnd_id = integ_info[var_names.default_vendor]

    inc_url = url_paths.web_incidents_details + '/' + str(org_instance_id)
    desc = 'TaskCall URL: ' + inc_url
    if text_msg is not None:
        desc = desc + ' | ' + text_msg

    body = {
        "title": task_title,
        "description": desc,
        "priority": tkt_priority_id,
        "status": tkt_status_id,
        "ticketCategory": tkt_category,
        "ticketType": tkt_type,
        "queueID": tkt_queue_id,
        "companyID": vnd_id
    }

    ticket_id, display_id, exe_status, exe_output = None, None, 400, errors.err_processing_failed
    try:
        response = requests.post(url_ticket.format(acc_domain),
                                 headers=get_standard_headers(api_user, api_pwd, api_trk_id),
                                 data=json.dumps(body))
        exe_status = response.status_code
        exe_output = response.json()
        if exe_status in [200, 201]:
            ticket_id = str(exe_output['itemId'])
            ticket_details = get_autotask_ticket(acc_domain, api_user, api_pwd, api_trk_id, ticket_id)
            display_id = ticket_details['ticketNumber']
        else:
            logging.exception('Failed to create Autotask ticket')
            logging.exception(exe_output)
    except Exception as e:
        logging.exception('Failed to create Autotask ticket')
        logging.exception(str(e))
    finally:
        return ticket_id, exe_status, {**{var_names.display_name: display_id}, **exe_output}


def update_autotask_ticket(conn, timestamp, org_id, integ_key, integ_info, ticket_id, new_state=None,
                           new_urgency=None, new_note=None, voice_url=None):
    '''
    Update an Autotask ticket.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param org_id: organization ID
    :param integ_key: the integration key
    :param integ_info: additional info of this integration
    :param ticket_id: ID of the ticket
    :param new_state: new state of the instance
    :param new_urgency: new urgency level of the instance
    :param new_note: new note added to the instance
    :param voice_url: the URL of the voice message
    '''
    au_tsk_det = db_autotask.get_autotask_account_details(conn, timestamp, org_id, integration_key=integ_key)
    if len(au_tsk_det) == 0:
        raise LookupError(errors.err_integration_not_found)

    acc_domain = au_tsk_det[var_names.vendor_endpoint_name]
    api_user = au_tsk_det[var_names.username]
    api_pwd = au_tsk_det[var_names.password]
    api_trk_id = au_tsk_det[var_names.secret_token]

    headers = get_standard_headers(api_user, api_pwd, api_trk_id)

    sync_sts_val = get_outgoing_status(new_state, integ_info)
    sync_urg_val = get_outgoing_urgency(new_urgency, integ_info)

    if sync_sts_val is not None or sync_urg_val is not None:
        body = {'id': ticket_id}
        if sync_sts_val is not None:
            body['status'] = sync_sts_val
        if sync_urg_val is not None:
            body['priority'] = sync_urg_val
        try:
            response = requests.patch(url_ticket.format(acc_domain), headers=headers, data=json.dumps(body))
            if response.status_code not in [200, 201]:
                logging.error('Failed - Autotask ticket ' + ticket_id + ' update - ' + str(body))
                logging.exception(response.json())
        except Exception as e:
            logging.exception('Failed to update Autotask ticket')
            logging.exception(str(e))

    if new_note is not None and integ_info[var_names.to_sync_notes]:
        add_autotask_note(acc_domain, api_user, api_pwd, api_trk_id, ticket_id, new_note)

    if voice_url is not None:
        message = 'TaskCall: Voice message has been left'
        add_autotask_note(acc_domain, api_user, api_pwd, api_trk_id, ticket_id, message)


def add_autotask_note(acc_domain, api_user, api_pwd, api_trk_id, ticket_id, new_note):
    '''
    Add a note to an Autotask ticket.
    :param acc_domain: domain of the autotask account
    :param api_user: API user
    :param api_pwd: API secret
    :param api_trk_id: API user tracking ID
    :param ticket_id: ID of the ticket
    :param new_note: new note added to the instance
    '''
    headers = get_standard_headers(api_user, api_pwd, api_trk_id)
    body = {'title': new_note[:50], 'description': new_note, 'noteType': 1, 'publish': 1}
    try:
        response = requests.post(url_ticket_notes.format(acc_domain, ticket_id), headers=headers,
                                 data=json.dumps(body))
        if response.status_code not in [200, 201]:
            logging.error('Failed - Autotask ticket ' + ticket_id + ' add note - ' + str(body))
            logging.exception(response.json())
    except Exception as e:
        logging.exception('Failed to add note to Autotask ticket')
        logging.exception(str(e))


def get_autotask_ticket(acc_domain, api_user, api_pwd, api_trk_id, ticket_id):
    '''
    Get an Autotask ticket given the ticket ID.
    :param acc_domain: domain of the autotask account
    :param api_user: API user
    :param api_pwd: API secret
    :param api_trk_id: API user tracking ID
    :param ticket_id: ID of the ticket
    :return: (dict) of autotask details
    '''
    headers = get_standard_headers(api_user, api_pwd, api_trk_id)
    try:
        response = requests.get(url_ticket.format(acc_domain) + '/' + str(ticket_id), headers=headers)
        if response.status_code in [200, 201]:
            return response.json()['item']
        else:
            logging.exception('Failed to fetch Autotask ticket')
            logging.exception(response.json())
    except Exception as e:
        logging.exception('Failed to fetch Autotask ticket')
        logging.exception(str(e))


def get_outgoing_status(sts_val, integ_info):
    '''
    Get the outgoing status value from the stored details.
    :param sts_val: the status to check for
    :param integ_info: integration additional details
    :return: (int) Autotask status value
    '''
    if sts_val is not None and integ_info[var_names.status] is not None and \
            sts_val in integ_info[var_names.status][var_names.outgoing]:
        return int(integ_info[var_names.status][var_names.outgoing][sts_val])
    return None


def get_outgoing_urgency(urg_val, integ_info):
    '''
    Get the outgoing status value from the stored details.
    :param urg_val: the urgency to check for
    :param integ_info: integration additional details
    :return: (int) Autotask priority value
    '''
    if urg_val is not None and integ_info[var_names.urgency_level] is not None and \
            str(urg_val) in integ_info[var_names.urgency_level][var_names.outgoing]:
        return int(integ_info[var_names.urgency_level][var_names.outgoing][str(urg_val)])
    return None
