# By: Riasat Ullah
# This file contains general check related database queries that can be used by all types of checks.

from psycopg2 import errorcodes
from utils import constants, errors, key_manager, permissions, var_names
from validations import configuration_validator
import configuration as configs
import datetime
import json
import psycopg2


def create_check(conn, timestamp, organization_id, user_id, check_ref_id, check_type, check_name, check_description,
                 ping_type, ping_url, ping_email, ping_interval, grace_period, service_ref_id, inc_title,
                 inc_description, urgency_level, tags=None, ip_whitelist=None, email_whitelist=None, packet_sizes=None,
                 degraded_timeout=None, fail_timeout=None, check_ssl_expiry=None, ping_port=None, additional_info=None,
                 check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Create a new monitor check.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization this check is for
    :param user_id: ID of the user who is creating this check
    :param check_ref_id: (concealed) reference ID of the check; only for heartbeat checks this will be pre-generated
                        and provided with the user request
    :param check_type: type of check (HEARTBEAT, etc)
    :param check_name: name for the check
    :param check_description: short description of what the check is for
    :param ping_type: type of ping method that will be used for the check (HTTP, EMAIL)
    :param ping_url: (for HTTP pings) url of the ping endpoint
    :param ping_email: (for email pings) email of the ping endpoint
    :param ping_interval: (integer) interval between pings
    :param grace_period: grace period allowed for successful pings
    :param service_ref_id: reference ID of the service this check is for
    :param inc_title: title of the incident that should be created if the check fails
    :param inc_description: description of the incident that should be created if the check fails
    :param urgency_level: urgency of the incident to be created if the check fails
    :param tags: (list of str) tags to be added to the incident that will be created if the check fails
    :param ip_whitelist: (list of IP addresses) IP addresses that HTTP requests will be allowed from
    :param email_whitelist: (list of email) email addresses that email pings will be allowed from
    :param packet_sizes: (list of int) packet sizes in kilobytes that should be used for the check
    :param degraded_timeout: (int) number of seconds to allow before a ping response is considered degraded
    :param fail_timeout: (int) number of seconds to allow before a ping response is considered to have failed
    :param check_ssl_expiry: (boolean) true if domain SSL expiry should be checked
    :param ping_port: (int) port to ping
    :param additional_info: (dict) additional information necessary for the check
    :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: (concealed) check reference ID
    :errors: AssertionError, DatabaseError, PermissionError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    assert isinstance(user_id, int)
    configuration_validator.validate_check_data(
        check_ref_id, check_type, check_name, check_description, ping_type, ping_url, ping_email, ping_interval,
        grace_period, service_ref_id, inc_title, inc_description, urgency_level, tags, ip_whitelist, email_whitelist,
        packet_sizes, degraded_timeout, fail_timeout, check_ssl_expiry, ping_port, additional_info
    )
    if check_ref_id is None:
        unmasked_check_ref = key_manager.generate_reference_key()
    else:
        unmasked_check_ref = key_manager.unmask_reference_key(check_ref_id)

    unmasked_srv_ref = key_manager.unmask_reference_key(service_ref_id)
    comp_type = configs.service_component_type_id
    adv_perm_type = permissions.COMPONENT_ADVANCED_EDIT_PERMISSION
    if additional_info is not None:
        additional_info = json.dumps(additional_info)

    query = '''
            select create_monitor_check(
                %s, %s, %s, %s::smallint, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s
            );
            '''
    query_params = (check_adv_perm, has_comp_perm, has_team_perm, comp_type, adv_perm_type, organization_id,
                    timestamp, constants.end_timestamp, unmasked_check_ref, check_type, check_name, check_description,
                    True, ping_type, ping_url, ping_email, ping_interval, grace_period,
                    unmasked_srv_ref, inc_title, inc_description, urgency_level, tags, ip_whitelist,
                    email_whitelist, packet_sizes, degraded_timeout, fail_timeout, check_ssl_expiry, ping_port,
                    additional_info, user_id, constants.root_destination,)
    try:
        conn.execute(query, query_params)
        return key_manager.conceal_reference_key(unmasked_check_ref)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.RESTRICT_VIOLATION:
            raise PermissionError(errors.err_user_rights_service)
        else:
            raise
    except psycopg2.DatabaseError:
        raise


def edit_check(conn, timestamp, organization_id, user_id, check_ref_id, check_type, check_name, check_description,
               ping_type, ping_url, ping_email, ping_interval, grace_period, service_ref_id, inc_title,
               inc_description, urgency_level, tags=None, ip_whitelist=None, email_whitelist=None, packet_sizes=None,
               degraded_timeout=None, fail_timeout=None, check_ssl_expiry=None, ping_port=None, additional_info=None,
               check_adv_perm=False, has_comp_perm=False, has_team_perm=False):
    '''
    Edit an existing monitor check.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization this check is for
    :param user_id: ID of the user who is creating this check
    :param check_ref_id: reference ID of the check
    :param check_type: type of check (HEARTBEAT, etc)
    :param check_name: name for the check
    :param check_description: short description of what the check is for
    :param ping_type: type of ping method that will be used for the check (HTTP, EMAIL)
    :param ping_url: (for HTTP pings) url of the ping endpoint
    :param ping_email: (for email pings) email of the ping endpoint
    :param ping_interval: (integer) interval between pings
    :param grace_period: grace period allowed for successful pings
    :param service_ref_id: reference ID of the service this check is for
    :param inc_title: title of the incident that should be created if the check fails
    :param inc_description: description of the incident that should be created if the check fails
    :param urgency_level: urgency of the incident to be created if the check fails
    :param tags: (list of str) tags to be added to the incident that will be created if the check fails
    :param ip_whitelist: (list of IP addresses) IP addresses that HTTP requests will be allowed from
    :param email_whitelist: (list of email) email addresses that email pings will be allowed from
    :param packet_sizes: (list of int) packet sizes in kilobytes that should be used for the check
    :param degraded_timeout: (int) number of seconds to allow before a ping response is considered degraded
    :param fail_timeout: (int) number of seconds to allow before a ping response is considered to have failed
    :param check_ssl_expiry: (boolean) true if domain SSL expiry should be checked
    :param ping_port: (int) port to ping
    :param additional_info: (dict) additional information necessary for the check
    :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
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    assert isinstance(user_id, int)
    configuration_validator.validate_check_data(
        check_ref_id, check_type, check_name, check_description, ping_type, ping_url, ping_email, ping_interval,
        grace_period, service_ref_id, inc_title, inc_description, urgency_level, tags, ip_whitelist, email_whitelist,
        packet_sizes, degraded_timeout, fail_timeout, check_ssl_expiry, ping_port, additional_info
    )

    unmasked_check_ref = key_manager.unmask_reference_key(check_ref_id)
    unmasked_srv_ref = key_manager.unmask_reference_key(service_ref_id)
    comp_type = configs.service_component_type_id
    adv_perm_type = permissions.COMPONENT_ADVANCED_EDIT_PERMISSION
    if additional_info is not None:
        additional_info = json.dumps(additional_info)

    query = '''
            select edit_monitor_check(
                %s, %s, %s, %s::smallint, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s, %s, %s, %s,
                %s, %s, %s
            );
            '''
    query_params = (check_adv_perm, has_comp_perm, has_team_perm, comp_type, adv_perm_type, organization_id,
                    timestamp, constants.end_timestamp, unmasked_check_ref, check_type, check_name, check_description,
                    True, ping_type, ping_url, ping_email, ping_interval, grace_period,
                    unmasked_srv_ref, inc_title, inc_description, urgency_level, tags, ip_whitelist,
                    email_whitelist, packet_sizes, degraded_timeout, fail_timeout, check_ssl_expiry, ping_port,
                    additional_info, user_id, constants.root_destination,)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.RESTRICT_VIOLATION:
            raise PermissionError(errors.err_user_rights_service)
        else:
            raise
    except psycopg2.DatabaseError:
        raise


def delete_check(conn, timestamp, organization_id, user_id, check_ref_id, check_adv_perm=False,
                 has_comp_perm=False, has_team_perm=False):
    '''
    Delete a monitor check.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization this check is for
    :param user_id: ID of the user who is creating this check
    :param check_ref_id: reference ID of the check
    :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
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    assert isinstance(user_id, int)

    unmasked_check_ref = key_manager.unmask_reference_key(check_ref_id)
    comp_type = configs.service_component_type_id
    adv_perm_type = permissions.COMPONENT_ADVANCED_EDIT_PERMISSION

    query = '''
            select delete_monitor_check (
                %s, %s, %s, %s::smallint, %s,
                %s, %s, %s, %s
            )
            '''
    query_params = (check_adv_perm, has_comp_perm, has_team_perm, comp_type, adv_perm_type,
                    organization_id, timestamp, unmasked_check_ref, user_id,)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.RESTRICT_VIOLATION:
            raise PermissionError(errors.err_user_rights_service)
        else:
            raise
    except psycopg2.DatabaseError:
        raise


def enable_check(conn, timestamp, organization_id, user_id, check_ref_id, to_enable, check_adv_perm=False,
                 has_comp_perm=False, has_team_perm=False):
    '''
    Enable/disable a monitor check.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param organization_id: ID of the organization this check is for
    :param user_id: ID of the user who is creating this check
    :param check_ref_id: reference ID of the check
    :param to_enable: True if the check 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, PermissionError
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(organization_id, int)
    assert isinstance(user_id, int)
    assert isinstance(to_enable, bool)

    unmasked_check_ref = key_manager.unmask_reference_key(check_ref_id)
    comp_type = configs.service_component_type_id
    adv_perm_type = permissions.COMPONENT_ADVANCED_EDIT_PERMISSION

    query = '''
            select enable_monitor_check (
                %s, %s, %s, %s::smallint, %s, %s,
                %s, %s, %s, %s, %s
            )
            '''
    query_params = (check_adv_perm, has_comp_perm, has_team_perm, comp_type, adv_perm_type, organization_id,
                    timestamp, constants.end_timestamp, unmasked_check_ref, to_enable, user_id,)
    try:
        conn.execute(query, query_params)
    except psycopg2.IntegrityError as e:
        if e.pgcode == errorcodes.RESTRICT_VIOLATION:
            raise PermissionError(errors.err_user_rights_service)
        else:
            raise
    except psycopg2.DatabaseError:
        raise


def get_service_associated_checks(conn, timestamp, org_id, serv_ref):
    '''
    Get the names of the checks that are associated with a given service.
    :param conn: db connection
    :param timestamp: timestamp when this request is being made
    :param org_id: ID of the organization
    :param serv_ref: (str) concealed service reference ID
    :return: (list) - [check name 1, check name 2, ...]
    '''
    assert isinstance(timestamp, datetime.datetime)
    assert isinstance(org_id, int)
    unmasked_serv_ref = key_manager.unmask_reference_key(serv_ref)
    query = '''
            select check_name from monitor_checks
            where start_timestamp <= %(timestamp)s
                and end_timestamp > %(timestamp)s
                and organization_id = %(org_id)s
                and serviceid in (
                    select serviceid from services
                    where start_timestamp <= %(timestamp)s
                        and end_timestamp > %(timestamp)s
                        and service_ref_id = %(srv_ref)s
                );
            '''
    query_params = {'timestamp': timestamp, 'org_id': org_id, 'srv_ref': unmasked_serv_ref}
    try:
        result = conn.fetch(query, query_params)
        matching_checks = []
        for item in result:
            matching_checks.append(item[0])
        return matching_checks
    except psycopg2.DatabaseError:
        raise
