# By: Riasat Ullah
# This class syncs up service data stored in db with that in cache. In the case of retrievals, first attempt
# is made at the cache and then falls back to the db for cache misses.

from cache_queries import cache_email_assignees, cache_integration_keys, cache_services, cache_task_instances
from dbqueries import db_live_call_routing, db_organizations, db_routing, db_services, db_task_instances, db_users, \
    db_workflows
from dbqueries.checks import db_checks
from exceptions.user_exceptions import DependencyFound
from integrations import autotask, datadog, freshdesk, freshservice, google_chat, haloitsm, jira_cloud, jira_server, \
    logic_monitor, microsoft_teams as ms_teams, monday, servicenow, slack, solarwinds_servicedesk, team_dynamix, zendesk
from taskcallrest import settings
from threading import Thread
from translators import label_translator as _lt
from utils import cdn_enablers, constants, errors, integration_type_names as intt, key_manager, label_names as lnm, \
    permissions, var_names
from uuid import UUID
import configuration as configs


def get_single_service(conn, client, timestamp, service_id=None, service_ref_id=None, store_misses=True):
    '''
    Gets a single service.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp this request is being made on
    :param service_id: (int or list) ID of the service(s) being checked
    :param service_ref_id: reference ID of the service (unmasked)
    :param store_misses: (boolean) True if cache misses that had to be retrieved
            from the db must be stored in cache afterwards; False otherwise
    :return: Service object
    '''
    serv = None
    if settings.CACHE_ON:
        serv = cache_services.get_single_service(client, service_id, service_ref_id)

    if serv is None:
        serv_dict = db_services.get_service(conn, timestamp, service_id, service_ref_id)
        if len(serv_dict) > 0:
            serv = list(serv_dict.values())[0]
            if store_misses and settings.CACHE_ON:
                cache_services.store_service(client, serv)

    return serv


def get_multiple_services(conn, client, timestamp, service_ids, store_misses=True):
    '''
    Gets Service objects given their IDs. Tries the cache first, and then tries the db if there are cache misses.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp the request is being made on
    :param service_ids: (list of int) of service IDs
    :param store_misses: (boolean) True if cache misses should be stored in cache after retrieval from db
    :return: (dict) -> {service ID: Service object, ...}
    '''
    retrieved_services = dict()
    cache_miss_ids = []

    if settings.CACHE_ON:
        retrieved_services = cache_services.get_multiple_services(client, service_ids)
        if len(retrieved_services) != len(service_ids):
            cache_miss_ids = list(set(service_ids).difference(set(retrieved_services.keys())))
    else:
        cache_miss_ids = service_ids

    if len(cache_miss_ids) > 0:
        services_from_db = db_services.get_service(conn, timestamp, service_id=service_ids)
        retrieved_services = {**retrieved_services, **services_from_db}

        if store_misses and settings.CACHE_ON:
            cache_services.store_multiple_services(client, services_from_db)

    return retrieved_services


def edit_service(conn, client, timestamp, service_ref_id, organization_id, user_id, service_name, description,
                 for_policy_ref_id, re_trigger_minutes=None, support_days=None, support_start=None, support_end=None,
                 support_timezone=None, de_prioritize=None, re_prioritize=None, allow_grouping=None,
                 check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Edits a service
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made on
    :param service_ref_id: reference ID of the service that is being edited
    :param organization_id: ID of the organization
    :param user_id: user_id of the user creating the service
    :param service_name: name of the service
    :param description: brief description of what the service is for
    :param for_policy_ref_id: reference ID of the policy that incidents triggered on this service should be forwarded to
    :param re_trigger_minutes: number of minutes after which incidents should be re-triggered in ack state
    :param support_days: (list of int) of days the service is active
    :param support_start: (list) time the service is active from
    :param support_end: (list) time the service is active till
    :param support_timezone: timezone the service is in; this will determine the UTC support hours
    :param de_prioritize: (boolean) to de-prioritize incidents during off hours or not
    :param re_prioritize: (boolean) to re-prioritize incidents that were de-prioritized when support hours start or not
    :param allow_grouping: (boolean) to allow incidents to be grouped on this service or not
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :return: (int) service ID
    :errors: AssertionError, DatabaseError, LookupError, ValueError
    '''
    serv_id = db_services.edit_service(
        conn, timestamp, service_ref_id, organization_id, user_id, service_name, description,
        for_policy_ref_id, re_trigger_minutes, support_days, support_start, support_end, support_timezone,
        de_prioritize, re_prioritize, allow_grouping, check_adv_perm, has_comp_perm, has_team_perm
    )
    if not settings.TEST_SERVER and settings.CACHE_ON:
        if cache_services.is_service_in_cache(client, serv_id):
            serv_obj = list(db_services.get_service(conn, timestamp, service_id=serv_id).values())[0]
            cache_services.store_service(client, serv_obj)

        # Update the email mapping values in cache for this service. This ensures that when the policy associated
        # to the service changes, mappings of email integrations are rightfully updated. Otherwise, stale values
        # will be shown resulting in incorrect behaviour.
        serv_email_integ_assignees = db_organizations.get_email_assignees(conn, timestamp, service_id=serv_id)
        if len(serv_email_integ_assignees) > 0:
            cache_email_assignees.store_email_assignees(client, serv_email_integ_assignees)


def enable_service(conn, client, timestamp, service_ref_id, organization_id, user_id, to_enable,
                   check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Enables or disables a service.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made on
    :param service_ref_id: reference ID of the service that is being edited
    :param organization_id: ID of the organization
    :param user_id: user_id of the user creating the service
    :param to_enable: (boolean) True if the service should be enabled; False otherwise
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :errors: AssertionError, DatabaseError, LookupError, PermissionError
    '''
    serv_id = db_services.enable_service(conn, timestamp, service_ref_id, organization_id, user_id, to_enable,
                                         check_adv_perm, has_comp_perm, has_team_perm)
    if not to_enable and settings.CACHE_ON:
        cache_services.remove_services(client, [serv_id])


def delete_service(conn, client, timestamp, organization_id, user_id, service_ref_id, check_adv_perm=False,
                   has_comp_perm=False, has_team_perm=False):
    '''
    Delete a service.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made on
    :param service_ref_id: reference ID of the service that is to be deleted
    :param organization_id: ID of the organization the service belongs to
    :param user_id: ID of the user deleting the service
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :return: (int) the service ID
    :errors: AssertionError, DatabaseError, LookupError
    '''
    # Don't allow a service to be removed if there is an open instance or a manual task assigned to the service.
    active_task_titles = db_services.service_associated_task_titles(conn, timestamp, service_ref_id)
    if len(active_task_titles) > 0:
        raise DependencyFound(errors.err_service_delete_incident_dependency, ' - ' + "; ".join(active_task_titles))

    # check to see if any conditional routing is set to re-route to t
    conflicting_routings = db_routing.get_service_associated_conditional_routes(
        conn, timestamp, organization_id, service_ref_id)
    if len(conflicting_routings) > 0:
        raise DependencyFound(errors.err_service_delete_conditional_routing_dependency,
                              ' - (' + str(len(conflicting_routings)) + ') ' +
                              '; '.join([item[1] for item in conflicting_routings]))

    conflicting_live_call_routings = db_live_call_routing.get_service_associated_live_call_routes(
        conn, timestamp, organization_id, service_ref_id)
    if len(conflicting_live_call_routings) > 0:
        raise DependencyFound(errors.err_service_delete_live_call_routing_dependency,
                              ' - (' + str(len(conflicting_routings)) + ') ' +
                              '; '.join(conflicting_live_call_routings))

    conflicting_checks = db_checks.get_service_associated_checks(conn, timestamp, organization_id, service_ref_id)
    if len(conflicting_checks) > 0:
        raise DependencyFound(errors.err_service_delete_automation_check_dependency,
                              ' - (' + str(len(conflicting_checks)) + ') ' +
                              '; '.join(conflicting_checks))

    # check to see if any workflow has this policy as a new responder
    srv_id, tz = db_services.get_service_id_and_timezone(conn, timestamp, organization_id,
                                                         key_manager.unmask_reference_key(service_ref_id))
    conflicting_workflows = db_workflows.check_component_in_workflow(conn, timestamp, organization_id, srv_id=srv_id)
    if len(conflicting_workflows) > 0:
        raise DependencyFound(errors.err_workflow_dependency_service_delete,
                              ' - ' + '; '.join([item[var_names.workflow_name] for item in conflicting_workflows]))

    serv_integs = db_services.get_service_integrations(conn, timestamp, organization_id, service_ref_id)

    serv_id = db_services.delete_service(conn, timestamp, organization_id, user_id, service_ref_id, check_adv_perm,
                                         has_comp_perm, has_team_perm)
    if settings.CACHE_ON:
        cache_services.remove_services(client, [serv_id])

        integ_keys_to_del = []
        integ_emails_to_del = []
        for item in serv_integs:
            integ_keys_to_del.append(item[var_names.integration_key])
            if item[var_names.email] is not None:
                integ_emails_to_del.append(item[var_names.email])

        if len(integ_keys_to_del) > 0:
            cache_integration_keys.remove_integration_keys(client, integ_keys_to_del)
        if len(integ_emails_to_del) > 0:
            cache_email_assignees.remove_email_assignees(client, integ_emails_to_del)


def add_service_integration(conn, client, timestamp, organization_id, service_ref_id, integ_type, integ_key,
                            integ_name, integ_email=None, integ_url=None, access_token=None, secret_token=None,
                            vendor_endpoint=None, vendor_endpoint_name=None, vendor_account_name=None,
                            incoming_events=None, outgoing_events=None, conditions_map=None, payload_map=None,
                            public_access=None, additional_info=None, external_id=None, external_info=None,
                            updated_by=None, check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Adds an internal endpoint integration to a service. Internal endpoint integrations are those that are done
    by allocating a separate internal endpoint to receive data from the external vendor.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param service_ref_id: reference ID of the service
    :param integ_type: (str) the type of the integration
    :param integ_key: (unmasked) new and unique integration key
    :param integ_name: user given name of the integration
    :param integ_email: first part of the email address of the integration if this is an email integration
    :param integ_url: url issued by TaskCall to receive integration messages into
    :param access_token: access token needed to exchange information for this integration
    :param secret_token: secret token needed to exchange information for this integration
    :param vendor_endpoint: url issued by the vendor for TaskCall to send messages to the vendor
    :param vendor_endpoint_name: name of the vendor's endpoint
    :param vendor_account_name: name of the organization's account at the vendor's
    :param incoming_events: (list) of incoming events that can be accepted through this integration
    :param outgoing_events: (list) of outgoing events that can be sent out through this integration
    :param conditions_map: (dict) of conditions that must be met before accepting messages
    :param payload_map: (dict) that maps the vendor fields and constants to TaskCall's internal variables
    :param public_access: (bool) that states if this integration can be connected to publicly
    :param additional_info: (dict) any extra information
    :param external_id: (str) ID of the organization on the vendor's side
    :param external_info: (dict) of external details of the organization
    :param updated_by: user_id of the user who created/updated the details
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :errors: AssertionError, DatabaseError
    '''
    integ_id, pol_id, serv_id = db_services.add_service_integration(
        conn, timestamp, organization_id, service_ref_id, integ_key, integ_type, integ_name,
        integ_email, integ_url, access_token, secret_token,
        vendor_endpoint, vendor_endpoint_name, vendor_account_name,
        incoming_events, outgoing_events, conditions_map, payload_map,
        public_access, additional_info, external_id, external_info, updated_by,
        check_adv_perm, has_comp_perm, has_team_perm
    )
    if settings.CACHE_ON:
        # We are only storing non-email integrations here because email integrations will automatically be stored
        # on the cache when the first email is received on the assigned email address.
        # Check get_email_assignee_details function in syncer_email_assignees.py file.
        if integ_email is None:
            info_to_store = [integ_id, serv_id, organization_id]
            cache_integration_keys.store_single_integration_key(client, integ_key, info_to_store)

        if cache_services.get_single_service(client, serv_id) is not None:
            # Update the service in the cache to reflect the new integration
            serv_obj = list(db_services.get_service(conn, timestamp, service_id=serv_id).values())[0]
            cache_services.store_service(client, serv_obj)


def edit_service_integration(conn, client, timestamp, organization_id, service_ref_id, updated_by, integ_key,
                             integ_name, additional_info=None, check_adv_perm=False, has_comp_perm=False,
                             has_team_perm=False):
    '''
    Edit an internal endpoint integration.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param service_ref_id: (unmasked) reference ID of the service
    :param updated_by: user_id of the user who created/updated the details
    :param integ_key: (concealed) integration key
    :param integ_name: user given name of the integration
    :param additional_info: (dict) any extra information
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :return: (str) -> concealed integration key
    :errors: AssertionError, DatabaseError
    '''
    db_services.edit_service_integration(conn, timestamp, organization_id, updated_by, integ_key, integ_name,
                                         additional_info=additional_info,
                                         check_adv_perm=check_adv_perm,
                                         has_comp_perm=has_comp_perm,
                                         has_team_perm=has_team_perm)

    if settings.CACHE_ON and cache_services.get_single_service(client, serv_ref_id=service_ref_id) is not None:
        # Update the service in the cache to reflect the new integration
        serv_obj = list(db_services.get_service(conn, timestamp, service_ref_id=service_ref_id).values())[0]
        cache_services.store_service(client, serv_obj)


def transfer_service_integration(conn, client, timestamp, organization_id, updated_by, integ_key, new_serv_ref_id,
                                 check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Transfers an integration from one service to another.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param updated_by: user_id of the user who created/updated the details
    :param integ_key: UUID -> the integration key that uniquely identifies this integration
    :param new_serv_ref_id: the reference ID of the new service the integration is being transferred to
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :return: (dict) -> {integration_id: , integration_email: , service_id: , for_policy_id: }
    :errors: AssertionError, DatabaseError
    '''
    new_details = db_services.transfer_service_integration(conn, timestamp, organization_id, updated_by, integ_key,
                                                           new_serv_ref_id, check_adv_perm,
                                                           has_comp_perm, has_team_perm)
    if settings.CACHE_ON:
        integ_email = new_details[var_names.integration_email]

        if integ_email is None:
            info_to_store = [new_details[var_names.integration_id], new_details[var_names.service_id], organization_id]
            cache_integration_keys.store_single_integration_key(client, integ_key, info_to_store)
        else:
            info_to_store = {integ_email: [organization_id, new_details[var_names.for_policyid],
                                           new_details[var_names.service_id]]}
            cache_email_assignees.store_email_assignees(client, info_to_store)


def delete_service_integration(conn, client, timestamp, organization_id, user_id, integ_key, check_adv_perm=False,
                               has_comp_perm=False, has_team_perm=False, cdn_key_info=None):
    '''
    Deletes a service integration.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization
    :param user_id: ID of the user deleting the integration
    :param integ_key: UUID -> the integration key that uniquely identifies this integration
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :param cdn_key_info: (tuple) -> (integration type, cache key) if cdn_key_info is not None then we should check
        for its presence in the cdn external id mapping file and remove it
    :errors: AssertionError, DatabaseError
    '''
    integ_email = db_services.delete_service_integration(conn, timestamp, organization_id, user_id, integ_key,
                                                         check_adv_perm, has_comp_perm, has_team_perm)

    if settings.CACHE_ON:
        cache_integration_keys.remove_integration_keys(client, integ_key)
        if integ_email is not None:
            cache_email_assignees.remove_email_assignees(client, integ_email)

    if cdn_key_info is not None and settings.REGION == constants.aws_us_ohio:
        cdn_enablers.delete_external_account_map_from_file(cdn_key_info[0], cdn_key_info[1])


def get_integration_key_details(conn, client, timestamp, integration_key, store_misses=True):
    '''
    Gets the basic details associated to an integration key.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param integration_key: (UUID) the integration key
    :param store_misses: (boolean) True if cache misses should be stored in cache after retrieval from db
    :return: (list) -> [integration ID, service ID, organization ID] or None
    '''
    details = None
    if settings.CACHE_ON:
        details = cache_integration_keys.get_integration_key_details(client, integration_key)

    if details is None:
        details = db_services.get_service_integration_key_details(conn, timestamp, integration_key)
        if details is not None and settings.CACHE_ON and store_misses:
            cache_integration_keys.store_single_integration_key(client, integration_key, details)

    return details


def add_service_maintenance(conn, client, timestamp, service_ref_id, organization_id, user_id,
                            maintenance_start, maintenance_end, maintenance_timezone, reason=None,
                            check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Add a service maintenance schedule.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param service_ref_id: reference ID of the service the maintenance is for
    :param organization_id: ID of the organization the service belongs to
    :param user_id: user_id of the user adding the maintenance
    :param maintenance_start: (datetime.datetime) when the maintenance should start
    :param maintenance_end: (datetime.datetime) when the maintenance should end
    :param maintenance_timezone: (str) timezone the maintenance times are in
    :param reason: (str) reason for this maintenance
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :errors: AssertionError, DatabaseError, PermissionError, Redis Errors
    '''
    serv_id = db_services.add_service_maintenance(conn, timestamp, service_ref_id, organization_id, user_id,
                                                  maintenance_start, maintenance_end, maintenance_timezone, reason,
                                                  check_adv_perm, has_comp_perm, has_team_perm)
    if settings.CACHE_ON:
        if cache_services.is_service_in_cache(client, serv_id):
            serv_obj = db_services.get_service(
                conn, timestamp, service_id=serv_id, organization_id=organization_id)[serv_id]
            cache_services.store_service(client, serv_obj)


def delete_service_maintenance(conn, client, timestamp, organization_id, user_id, service_ref_id, maintenance_id,
                               check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Deletes a scheduled service maintenance that is yet to expire.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization the service belongs to
    :param user_id: user_id of the user deleting the maintenance
    :param service_ref_id: reference ID of the service the maintenance is for
    :param maintenance_id: the unique maintenance ID
    :param check_adv_perm: (boolean) should advanced permissions be checked
    :param has_comp_perm: (boolean) does the organization have advanced permissions
    :param has_team_perm: (boolean) does the organization have team permissions
    :errors: AssertionError, DatabaseError, LookupError, PermissionError
    '''
    serv_id = db_services.delete_service_maintenance(conn, timestamp, organization_id, user_id, service_ref_id,
                                                     maintenance_id, check_adv_perm, has_comp_perm, has_team_perm)
    if settings.CACHE_ON:
        if cache_services.is_service_in_cache(client, serv_id):
            serv_obj = db_services.get_service(
                conn, timestamp, service_id=serv_id, organization_id=organization_id)[serv_id]
            cache_services.store_service(client, serv_obj)


def get_synced_instance_integrations(conn, cache, timestamp, instance_id):
    '''
    Get the list of the details of the integrations that have been used to sync an instance.
    :param conn: db connection
    :param cache: cache client
    :param timestamp: timestamp when this request is being made
    :param instance_id: ID of the task instance to check for syncing
    :return: (list of dict) of integration details
    '''
    synced_integrations = []
    inst = cache_task_instances.get_single_instance(cache, instance_id)
    if inst is None:
        inst = db_task_instances.get_instances(conn, timestamp, instance_id)
        if len(inst) > 0:
            inst = inst[instance_id]

    if inst is not None and inst.synced_vendors is not None:
        all_integs = []
        synced_types = [x[var_names.synced_with] for x in inst.synced_vendors]

        if inst.task.service_id() is not None:
            serv = get_single_service(conn, cache, timestamp, service_id=inst.task.service_id())
            if serv.service_integrations is not None:
                all_integs += serv.service_integrations

        if set(synced_types).issubset(set(configs.allowed_account_wide_integrations)):
            all_integs += db_services.get_service_integrations_for_internal_use(
                conn, timestamp, inst.organization_id, account_wide=True)

        if len(all_integs) > 0:
            for synced in inst.synced_vendors:
                synced_integrations += [[synced[var_names.vendor_id], x] for x in all_integs
                                        if x[var_names.integration_id] == synced[var_names.integration_id]]

    return synced_integrations


def run_integration_syncer(conn, client, timestamp, org_id, instance_id, event_by=None, synced_integs=None,
                           skip_syncing=None, new_status=None, new_urgency=None, new_note=None, voice_url=None,
                           from_num=None, to_num=None, org_perm=None, lang=constants.lang_en, new_status_update=None):
    '''
    Runs all the functions needed to sync up an instance with vendor tickets/issues.
    :param conn: db connection
    :param client: cache client
    :param timestamp: timestamp when this request is being made
    :param org_id: ID of the organization the instance belongs to
    :param instance_id: ID of the instance
    :param event_by: the ID of the user who made the change that triggered the integration syncer
    :param synced_integs: (list) of integrations that the instance has been synced with
    :param skip_syncing: (list) of integrations that should not be synced. Needed to avoid eternal loops
    :param new_status: 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: URL of the voice note (only for live call routing incidents)
    :param from_num: caller's number (only for live call routing incidents)
    :param to_num: the live call routing number (only for live call routing incidents)
    :param org_perm: permissions the organization has
    :param lang: language content should be sent in
    :param new_status_update: new status update added to the instance
    '''
    synced_integs = get_synced_instance_integrations(conn, client, timestamp, instance_id)\
        if synced_integs is None else synced_integs
    skip_syncing = [] if skip_syncing is None else skip_syncing

    usr_obj = None
    if event_by is not None:
        usr_dt = db_users.get_user(conn, timestamp, user_id=event_by)
        if usr_dt is not None and len(usr_dt) > 0:
            usr_obj = usr_dt[event_by]

    for synced_id, item in synced_integs:
        integ_type = item[var_names.integration_type]
        if integ_type not in configs.supported_chat_integrations and org_perm is not None and \
                not permissions.has_org_permission(org_perm, permissions.ORG_INTEGRATION_SECONDARY_PERMISSION):
            return

        if integ_type not in skip_syncing:
            concealed_key = key_manager.conceal_reference_key(UUID(item[var_names.integration_key]))

            if integ_type == intt.autotask:
                if new_note is not None and usr_obj is not None:
                    astk_new_note = usr_obj.first_name + ' ' + usr_obj.last_name + ': ' + new_note
                else:
                    astk_new_note = new_note
                Thread(target=autotask.update_autotask_ticket,
                       args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                             synced_id, new_status, new_urgency, astk_new_note, voice_url)).start()

            elif integ_type == intt.datadog:
                if new_status is not None:
                    Thread(target=datadog.update_datadog_status,
                           args=(conn, timestamp, org_id, concealed_key, synced_id, new_status)).start()

                if new_urgency is not None:
                    Thread(target=datadog.update_datadog_severity,
                           args=(conn, timestamp, org_id, concealed_key, synced_id, new_urgency)).start()

            elif integ_type == intt.freshdesk:
                Thread(target=freshdesk.update_freshdesk_ticket,
                       args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                             synced_id, new_status, new_urgency, new_note, voice_url)).start()

            elif integ_type == intt.freshservice:
                Thread(target=freshservice.update_freshservice_ticket,
                       args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                             synced_id, new_status, new_urgency, new_note, voice_url)).start()

            elif integ_type == intt.google_chat:
                if new_status is not None or new_note is not None or new_status_update is not None:
                    addn_info = item[var_names.additional_info]
                    inst_obj = db_task_instances.get_instances(conn, timestamp, instance_id)[instance_id]
                    evn_type, extra_info = None, None
                    if new_status == constants.open_state:
                        evn_type = constants.un_acknowledge_event
                    elif new_status == constants.acknowledged_state:
                        evn_type = constants.acknowledge_event
                    elif new_status == constants.resolved_state:
                        evn_type = constants.resolve_event

                    if new_note is not None:
                        evn_type = constants.notate_event
                        extra_info = new_note
                    elif new_status_update is not None:
                        evn_type = constants.status_update_event
                        extra_info = new_status_update

                    event_by_name = None if usr_obj is None else usr_obj.first_name + ' ' + usr_obj.last_name
                    Thread(target=google_chat.send_event_update,
                           args=(addn_info[var_names.channel_id], evn_type, inst_obj.organization_instance_id,
                                 inst_obj.task.title(), lang, event_by_name, extra_info)).start()

            elif integ_type == intt.haloitsm:                
                Thread(target=haloitsm.update_haloitsm_ticket,
                       args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                             synced_id, new_status, new_urgency, new_note)).start()

            elif integ_type == intt.jira_cloud:
                if new_status is not None:
                    Thread(target=jira_cloud.update_jira_status,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 new_status, synced_id)).start()

                if new_urgency is not None:
                    Thread(target=jira_cloud.update_jira_priority,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 new_urgency, synced_id)).start()

                if new_note is not None:
                    Thread(target=jira_cloud.add_jira_comment,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 synced_id, new_note)).start()

            elif integ_type == intt.jira_server:
                if new_status is not None:
                    Thread(target=jira_server.update_jira_status,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 new_status, synced_id)).start()

                if new_urgency is not None:
                    Thread(target=jira_server.update_jira_priority,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 new_urgency, synced_id)).start()

                if new_note is not None:
                    Thread(target=jira_server.add_jira_comment,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 synced_id, new_note)).start()

            elif integ_type == intt.logic_monitor:
                if new_status is not None and new_status == constants.acknowledged_state:
                    lm_new_note = _lt.get_label(lnm.dsm_acknowledged, lang)
                    if usr_obj is not None:
                        lm_new_note += ': ' + usr_obj.first_name + ' ' + usr_obj.last_name
                    Thread(target=logic_monitor.update_logic_monitor_alert,
                           args=(conn, timestamp, org_id, concealed_key, synced_id, new_status, lm_new_note)).start()
                if new_note is not None:
                    Thread(target=logic_monitor.update_logic_monitor_alert,
                           args=(conn, timestamp, org_id, concealed_key, synced_id, None, new_note)).start()

            elif integ_type == intt.microsoft_teams:
                if new_status is not None or new_note is not None or new_status_update is not None:
                    addn_info = item[var_names.additional_info]
                    inst_obj = db_task_instances.get_instances(conn, timestamp, instance_id)[instance_id]
                    evn_type, extra_info = None, None
                    if new_status == constants.open_state:
                        evn_type = constants.un_acknowledge_event
                    elif new_status == constants.acknowledged_state:
                        evn_type = constants.acknowledge_event
                    elif new_status == constants.resolved_state:
                        evn_type = constants.resolve_event

                    if new_note is not None:
                        evn_type = constants.notate_event
                        extra_info = new_note
                    elif new_status_update is not None:
                        evn_type = constants.status_update_event
                        extra_info = new_status_update

                    event_by_name = None if usr_obj is None else usr_obj.first_name + ' ' + usr_obj.last_name
                    ms_teams_creds = ms_teams.get_ms_teams_credentials()
                    Thread(target=ms_teams.send_event_update,
                           args=(ms_teams_creds[var_names.access_token], item[var_names.vendor_endpoint],
                                 ms_teams_creds[var_names.bot_id], addn_info[var_names.channel_id],
                                 evn_type, inst_obj.organization_instance_id,
                                 inst_obj.task.title(), lang, event_by_name, extra_info)).start()

            elif integ_type == intt.monday:
                if new_status is not None:
                    Thread(target=monday.update_monday_status,
                           args=(conn, timestamp, org_id, new_status, synced_id, item[var_names.additional_info],
                                 concealed_key)).start()

                if new_urgency is not None:
                    Thread(target=monday.update_monday_priority,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 new_urgency, synced_id)).start()

                if new_note is not None:
                    Thread(target=monday.add_monday_update,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 synced_id, new_note)).start()

            elif integ_type == intt.servicenow:
                if new_status is not None:
                    Thread(target=servicenow.update_servicenow_status,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 new_status, synced_id,
                                 usr_obj.email if usr_obj is not None and new_status == constants.acknowledged_state
                                 else None)).start()

                if new_urgency is not None:
                    Thread(target=servicenow.update_servicenow_urgency,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 new_urgency, synced_id)).start()

                if new_note is not None:
                    Thread(target=servicenow.add_servicenow_work_note,
                           args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                                 synced_id, new_note)).start()

            elif integ_type == intt.slack:
                if new_status is not None or new_note is not None or new_status_update is not None:
                    inst_obj = db_task_instances.get_instances(conn, timestamp, instance_id)[instance_id]
                    evn_type, extra_info = None, None
                    if new_status == constants.open_state:
                        evn_type = constants.un_acknowledge_event
                    elif new_status == constants.acknowledged_state:
                        evn_type = constants.acknowledge_event
                    elif new_status == constants.resolved_state:
                        evn_type = constants.resolve_event

                    if new_note is not None:
                        evn_type = constants.notate_event
                        extra_info = new_note
                    elif new_status_update is not None:
                        evn_type = constants.status_update_event
                        extra_info = new_status_update

                    event_by_name = None if usr_obj is None else usr_obj.first_name + ' ' + usr_obj.last_name
                    Thread(target=slack.send_event_update,
                           args=(item[var_names.vendor_endpoint], evn_type, inst_obj.organization_instance_id,
                                 inst_obj.task.title(), lang, event_by_name, extra_info)).start()

            elif integ_type == intt.team_dynamix:
                Thread(target=team_dynamix.update_team_dynamix_ticket,
                       args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                             synced_id, new_status, new_urgency, new_note)).start()

            elif integ_type == intt.zendesk:
                Thread(target=zendesk.update_zendesk_ticket,
                       args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                             synced_id, new_status, new_urgency, new_note, voice_url, from_num, to_num)).start()

            elif integ_type == intt.solarwinds_servicedesk:
                Thread(
                    target=solarwinds_servicedesk.update_solarwinds_servicedesk_ticket,
                    args=(conn, timestamp, org_id, concealed_key, item[var_names.additional_info],
                          synced_id, new_status, new_urgency, new_note,
                          voice_url)).start()

