# By: Riasat Ullah
# Contributor: Md. Fahim Bin Amin

from data_syncers import syncer_task_instances
from dbqueries import db_events
from integrations import autotask, custom_endpoint, freshdesk, freshservice, google_chat, haloitsm, jira_cloud, jira_server, \
    microsoft_teams as ms_teams, monday, rundeck, servicenow, slack, solarwinds_servicedesk, team_dynamix, zendesk
from objects.events import CustomActionEvent
from translators import label_translator as _lt
from utils import constants, errors, integration_type_names as intt, helpers, permissions, var_names
import configuration as configs
import json


def execute_custom_action(conn, cache, timestamp, org_id, org_perm, instance_id, inst_obj, integ_details, integ_key,
                          user_id=None, run_manually=True, dedup_key=None, lang=constants.lang_en, has_comp_perm=False,
                          has_team_perm=False, is_sys_action=False, trigger_info=None):
    '''
    Execute a custom action.
    :param conn: db connection
    :param cache: cache client
    :param timestamp: timestamp when the action is being executed
    :param org_id: ID of the organization the instance belongs to
    :param org_perm: organization level permissions
    :param instance_id: ID of the instance the action is being executed for
    :param inst_obj: InstanceState object
    :param integ_details: (dict) -> details of the integration
    :param integ_key: integration key of the custom action
    :param user_id: ID of the user who is executing the action
    :param run_manually: (boolean) True if the custom actions are being executed manually; False otherwise
    :param dedup_key: dedup key
    :param lang: language to send error messages in
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :param is_sys_action: (boolean) is this action being automatically performed by the system or not
    :param trigger_info: (dict) trigger_info from the task details source
    '''
    integ_type = integ_details[var_names.integration_type]
    addn_info = integ_details[var_names.additional_info]
    action_type = integ_details[var_names.integration_type_id]\
        if var_names.integration_type_id in integ_details else None
    is_manual = addn_info[var_names.is_manual] if addn_info is not None and var_names.is_manual in addn_info else True

    # If integration is for a custom action, then confirm that the account has the correct permission
    if (integ_type in [intt.custom_endpoint, intt.rundeck] and not permissions.has_org_permission(
            org_perm, permissions.ORG_CUSTOM_ACTION_PERMISSION)) or \
            (integ_type in [intt.freshdesk, intt.zendesk] and not permissions.has_org_permission(
                org_perm, permissions.ORG_INTEGRATION_CUSTOMER_SERVICE_PERMISSION)) or \
            (integ_type in [intt.autotask, intt.freshservice, intt.servicenow, intt.team_dynamix]
             and not permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_ITSM_PERMISSION)) or \
            (integ_type not in configs.supported_chat_integrations
             and not permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_SECONDARY_PERMISSION)):
        raise PermissionError(_lt.get_label(errors.err_subscription_rights, lang))

    # do the actual syncing here
    vendor_id, vendor_url, is_synced, exe_status, exe_output = None, None, True, None, None

    # Autotask
    if integ_type == intt.autotask:
        min_urgency = addn_info[var_names.min_urgency] \
            if addn_info is not None and var_names.min_urgency in addn_info else None

        qlf_manual = run_manually and is_manual
        if not run_manually and min_urgency is not None and inst_obj.task.urgency_level() >= int(min_urgency) \
                and (dedup_key is None or (dedup_key is not None and dedup_key != intt.autotask)):
            qlf_auto = True
        else:
            qlf_auto = False

        group_id = None
        if trigger_info is not None and var_names.source_payload in trigger_info and \
            trigger_info[var_names.source_payload] is not None and \
                var_names.group_id in trigger_info[var_names.source_payload]:
            group_id = trigger_info[var_names.source_payload][var_names.group_id]

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = autotask.create_autotask_ticket(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.organization_instance_id,
                inst_obj.task.title(), inst_obj.task.text_msg(), inst_obj.status,
                inst_obj.task.urgency_level(), group_id=group_id
            )

    # Freshdesk
    elif integ_type == intt.freshdesk:
        min_urgency = addn_info[var_names.min_urgency]\
            if addn_info is not None and var_names.min_urgency in addn_info else None

        qlf_manual = run_manually and is_manual
        if not run_manually and min_urgency is not None and inst_obj.task.urgency_level() >= int(min_urgency)\
                and (dedup_key is None or (dedup_key is not None and dedup_key != intt.freshdesk)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = freshdesk.create_freshdesk_ticket(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.task.title(),
                inst_obj.task.text_msg(), inst_obj.status, inst_obj.task.urgency_level()
            )
            # Link the incident to the ticket
            db_events.link_instance(conn, timestamp, org_id, instance_id, intt.freshdesk, vendor_id, user_id)

    # Freshservice
    elif integ_type == intt.freshservice:
        min_urgency = addn_info[var_names.min_urgency]\
            if addn_info is not None and var_names.min_urgency in addn_info else None

        qlf_manual = run_manually and is_manual
        if not run_manually and min_urgency is not None and inst_obj.task.urgency_level() >= int(min_urgency)\
                and (dedup_key is None or (dedup_key is not None and dedup_key != intt.freshservice)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = freshservice.create_freshservice_ticket(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.task.title(),
                inst_obj.task.text_msg(), inst_obj.status, inst_obj.task.urgency_level()
            )

    # Google Chat
    elif integ_type == intt.google_chat:
        inst_conf = inst_obj.conference_bridges[0]\
            if inst_obj.conference_bridges is not None and len(inst_obj.conference_bridges) > 0 else None
        google_chat.GoogleChatIncidentDispatcher(
            addn_info[var_names.channel_id], inst_obj.instance_id, inst_obj.organization_instance_id,
            inst_obj.task.title(), inst_obj.task.text_msg(), inst_obj.task.service_name(),
            inst_obj.get_assignee_names(), inst_obj.status, inst_obj.task.urgency_level(),
            inst_obj.task.get_snapshots(), lang=lang, cache=cache, conference_bridge=inst_conf
        ).send_message()
        is_synced = False

    #HaloITSM
    elif integ_type == intt.haloitsm:
        min_urgency = addn_info[var_names.min_urgency]\
            if addn_info is not None and var_names.min_urgency in addn_info else None

        qlf_manual = run_manually and is_manual
        if not run_manually and min_urgency is not None and inst_obj.task.urgency_level() >= int(min_urgency)\
                and (dedup_key is None or (dedup_key is not None and dedup_key != intt.freshservice)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = haloitsm.create_haloitsm_ticket(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.task.title(),
                inst_obj.task.text_msg(), inst_obj.status, inst_obj.task.urgency_level()
            )

    # Jira Cloud
    elif integ_type == intt.jira_cloud:
        qlf_manual = run_manually and is_manual
        if not is_manual and (dedup_key is None or (dedup_key is not None and intt.jira_cloud in dedup_key)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = jira_cloud.create_jira_issue(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.organization_instance_id,
                inst_obj.task.title(), inst_obj.task.text_msg(), inst_obj.status,
                inst_obj.task.urgency_level()
            )
            if vendor_id is None:
                raise ConnectionRefusedError(_lt.get_label(errors.err_jira_cloud_access_expired, lang))

    # Jira Server
    elif integ_type == intt.jira_server:
        qlf_manual = run_manually and is_manual
        if not is_manual and (dedup_key is None or (dedup_key is not None and intt.jira_server in dedup_key)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = jira_server.create_jira_issue(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.organization_instance_id,
                inst_obj.task.title(), inst_obj.task.text_msg(), inst_obj.status,
                inst_obj.task.urgency_level()
            )

    # Microsoft Teams
    elif integ_type == intt.microsoft_teams:
        inst_conf = inst_obj.conference_bridges[0]\
            if inst_obj.conference_bridges is not None and len(inst_obj.conference_bridges) > 0 else None
        ms_teams_creds = ms_teams.get_ms_teams_credentials()
        ms_teams.MsTeamsIncidentDispatcher(
            ms_teams_creds[var_names.access_token], integ_details[var_names.vendor_endpoint],
            ms_teams_creds[var_names.bot_id], addn_info[var_names.channel_id], inst_obj.instance_id,
            inst_obj.organization_instance_id, inst_obj.task.title(), inst_obj.task.text_msg(),
            inst_obj.task.service_name(), inst_obj.get_assignee_names(), inst_obj.task.urgency_level(),
            inst_obj.task.get_snapshots(), lang=lang, cache=cache, conference_bridge=inst_conf
        ).send_message()
        is_synced = False

    # monday.com
    elif integ_type == intt.monday:
        qlf_manual = run_manually and is_manual
        if not is_manual and (dedup_key is None or (dedup_key is not None and intt.monday in dedup_key)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = monday.create_monday_item(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.task.title(),
                inst_obj.task.text_msg(), inst_obj.status, inst_obj.task.urgency_level()
            )

    # Rundeck
    elif integ_type == intt.rundeck:
        if run_manually and is_manual and\
                permissions.has_org_permission(org_perm, permissions.ORG_CUSTOM_ACTION_PERMISSION):
            vendor_id, exe_status, exe_output = rundeck.send_rundeck_instructions(integ_details)
            is_synced = False

    # Custom Action Integration
    elif integ_type == intt.custom_endpoint:
        if run_manually:
            payload = None
            if var_names.payload in integ_details[var_names.additional_info] and \
                    integ_details[var_names.additional_info][var_names.payload] is not None:
                payload = json.loads(integ_details[var_names.additional_info][var_names.payload])
                for key in payload:
                    payload[key] = helpers.replace_internal_data_fields(payload[key], inst_obj, lang)
            exe_status, exe_output = custom_endpoint.send_instructions_to_custom_endpoint(integ_details, payload)
            if exe_status != 200:
                raise RuntimeError(_lt.get_label(errors.err_processing_failed, lang))
            is_synced = False

    # ServiceNow
    elif integ_type == intt.servicenow:
        if run_manually:
            vendor_id, exe_status, exe_output = servicenow.create_servicenow_incident(
                conn, timestamp, org_id, integ_key, addn_info, instance_id,
                inst_obj.organization_instance_id, inst_obj.task.title(), inst_obj.task.text_msg(),
                inst_obj.status, inst_obj.task.urgency_level(), inst_obj.notes
            )

    # Slack
    elif integ_type == intt.slack:
        channel_id = addn_info[var_names.channel_id]\
            if addn_info is not None and var_names.channel_id in addn_info else None
        inst_conf = inst_obj.conference_bridges[0]\
            if inst_obj.conference_bridges is not None and len(inst_obj.conference_bridges) > 0 else None

        vendor_id, exe_status, exe_output = slack.SlackIncidentDispatcher(
            org_id, instance_id, inst_obj.organization_instance_id, inst_obj.task.title(), inst_obj.task.text_msg(),
            inst_obj.task.service_name(), inst_obj.get_assignee_names(), inst_obj.status, inst_obj.task.urgency_level(),
            inst_obj.task.get_snapshots(), integ_details[var_names.vendor_endpoint], channel_id,
            integ_details[var_names.access_token], lang=lang, cache=cache, conference_bridge=inst_conf
        ).send_message()
        is_synced = False

    # TeamDynamix
    elif integ_type == intt.team_dynamix:
        min_urgency = addn_info[var_names.min_urgency] \
            if addn_info is not None and var_names.min_urgency in addn_info else None

        qlf_manual = run_manually and is_manual
        if not run_manually and min_urgency is not None and inst_obj.task.urgency_level() >= int(min_urgency) \
                and (dedup_key is None or (dedup_key is not None and dedup_key != intt.team_dynamix)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            vendor_id, exe_status, exe_output = team_dynamix.create_team_dynamix_ticket(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.organization_instance_id,
                inst_obj.task.title(), inst_obj.task.text_msg(), inst_obj.status,
                inst_obj.task.urgency_level()
            )

    # SolarWinds ServiceDesk
    elif integ_type == intt.solarwinds_servicedesk:
        # create ticket (manual action)
        if run_manually:
            min_urgency = addn_info[var_names.min_urgency] \
                if addn_info is not None and var_names.min_urgency in addn_info else None

            is_manual_allowed = addn_info.get(var_names.is_manual, True)
            qlf_manual = is_manual_allowed

            qlf_auto = False  # no automatic creation here

            if qlf_manual:
                vendor_id, exe_status, exe_output = solarwinds_servicedesk.create_servicedesk_ticket(
                    conn, timestamp, org_id, integ_key, addn_info,
                    inst_obj.organization_instance_id,
                    inst_obj.task.title(), inst_obj.task.text_msg(),
                    inst_obj.status, inst_obj.task.urgency_level()
                )

                # Save vendor mapping for bidirectional sync
                db_events.link_instance(
                    conn, timestamp, org_id, instance_id,
                    intt.solarwinds_servicedesk, vendor_id, user_id
                )
        # update ticket (automatic events)
        else:
            vendor_id = None

            # Find previously synced vendor ID
            if inst_obj.synced_vendors:
                for v in inst_obj.synced_vendors:
                    if v[var_names.synced_with] == intt.solarwinds_servicedesk:
                        vendor_id = v[var_names.vendor_id]
                        break

            # If not found, nothing to update
            if vendor_id:
                new_state = inst_obj.status
                new_urgency = inst_obj.task.urgency_level()
                new_note = inst_obj.get_last_note()

                voice_url = None
                vm = inst_obj.task.details.get(
                    var_names.voice_messages) if hasattr(inst_obj.task,
                                                         "details") else None
                if vm and len(vm) > 0:
                    voice_url = vm[0]

                vendor_id, exe_status, exe_output = solarwinds_servicedesk.update_servicedesk_ticket(
                    conn, timestamp, org_id, integ_key, addn_info,
                    vendor_id,
                    new_state=new_state,
                    new_urgency=new_urgency,
                    new_note=new_note,
                    voice_url=voice_url
                )

    # Zendesk
    elif integ_type == intt.zendesk:
        min_urgency = addn_info[var_names.min_urgency]\
            if addn_info is not None and var_names.min_urgency in addn_info else None

        qlf_manual = run_manually and is_manual
        if not run_manually and min_urgency is not None and inst_obj.task.urgency_level() >= int(min_urgency)\
                and (dedup_key is None or (dedup_key is not None and dedup_key != intt.zendesk)):
            qlf_auto = True
        else:
            qlf_auto = False

        if qlf_manual or qlf_auto:
            if var_names.voice_messages in inst_obj.task.details and\
                inst_obj.task.details[var_names.voice_messages] is not None and\
                    len(inst_obj.task.details[var_names.voice_messages]) > 0:
                voice_msg_url = inst_obj.task.details[var_names.voice_messages][0]
            else:
                voice_msg_url = None
            vendor_id, exe_status, exe_output = zendesk.create_zendesk_ticket(
                conn, timestamp, org_id, integ_key, addn_info, inst_obj.task.title(), inst_obj.task.text_msg(),
                inst_obj.status, inst_obj.task.urgency_level(), voice_url=voice_msg_url
            )
            # Link the incident to the ticket
            db_events.link_instance(conn, timestamp, org_id, instance_id, intt.zendesk, vendor_id, user_id)

    if (is_synced and vendor_id is not None) or not is_synced:
        conf_name = None
        if addn_info is not None and var_names.configuration_name in addn_info:
            conf_name = addn_info[var_names.configuration_name]

        event = CustomActionEvent(
            instance_id, timestamp, constants.internal, integration_id=integ_details[var_names.integration_id],
            integration_type_id=action_type, vendor_id=vendor_id, vendor_url=vendor_url,
            additional_info=exe_output, event_by=user_id, is_synced=is_synced, configuration_name=conf_name
        )

        syncer_task_instances.execute_custom_action(
            conn, cache, event, org_id=org_id, has_comp_perm=has_comp_perm,
            has_team_perm=has_team_perm, is_sys_action=is_sys_action
        )

    return exe_status, exe_output
