# By: Riasat Ullah
# This class represents a monitor check.

from utils import constants, key_manager, times, var_names
from taskcallrest import settings
import datetime
import uuid


class Check(object):

    def __init__(self, check_id, organization_id, check_ref_id, check_type, check_name, description, is_enabled,
                 ping_type, url, email, interval, grace_period, service_id, task_title, text_msg,
                 urgency_level, tags=None, ip_address=None, email_from=None, packet_sizes=None, degraded_timeout=None,
                 fail_timeout=None, check_ssl_expiry=None, port=None, additional_info=None, is_healthy=None,
                 last_run=None, next_run=None, events=None, incidents=None):
        '''
        Check object.
        :param check_id: ID of the check
        :param organization_id: ID of the organization
        :param check_ref_id: reference iD of the check
        :param check_type: type of check
        :param check_name: name of the check
        :param description: short description for the check
        :param is_enabled: True if the check is enabled; False otherwise
        :param ping_type: type of ping (HTTP, EMAIL)
        :param url: url endpoint for the check
        :param email: email endpoint for the check
        :param interval: interval between checks
        :param grace_period: extra time to allow for a check to pass (accounting for minor delay)
        :param service_id: ID of the service the check is for
        :param task_title: title of the incident to be created if the check fails
        :param text_msg: description of the incident
        :param urgency_level: urgency level of the incident
        :param tags: tags to attach to the incident
        :param ip_address: IP whitelist
        :param email_from: email sender whitelist
        :param packet_sizes: sizes of packets to send for certain checks
        :param degraded_timeout: response time after which a check is said to be degraded even if it passes
        :param fail_timeout: time to wait for response before which a check is considered to have failed
        :param check_ssl_expiry: check if SSL is expiring
        :param port: port to run check on
        :param additional_info: any additional information that might be necessary
        :param is_healthy: True if the check is healthy; False otherwise
        :param last_run: last time the check ran
        :param next_run: next time the check is scheduled to run
        :param events: logs of events for this check that is yet to be synced up in the database from the cache
        :param incidents: (list) of IDs of ongoing incidents
        '''
        self.check_id = check_id
        self.organization_id = organization_id
        self.check_ref_id = check_ref_id
        self.check_type = check_type
        self.check_name = check_name
        self.description = description
        self.is_enabled = is_enabled
        self.ping_type = ping_type
        self.url = url
        self.email = email
        self.interval = interval
        self.grace_period = grace_period
        self.service_id = service_id
        self.task_title = task_title
        self.text_msg = text_msg
        self.urgency_level = urgency_level
        self.tags = tags
        self.ip_address = ip_address
        self.email_from = email_from
        self.packet_sizes = packet_sizes
        self.degraded_timeout = degraded_timeout
        self.fail_timeout = fail_timeout
        self.check_ssl_expiry = check_ssl_expiry
        self.port = port
        self.additional_info = additional_info
        self.is_healthy = is_healthy
        self.last_run = last_run
        self.next_run = next_run
        self.events = events
        self.incidents = incidents

    @staticmethod
    def create_check(details):
        '''
        Creates a Check object from a dict of check details.
        :param details: (dict) of check details
        :return: Check object
        '''
        return Check(
            details[var_names.check_id], details[var_names.organization_id],
            uuid.UUID(details[var_names.check_ref_id]), details[var_names.check_type],
            details[var_names.check_name], details[var_names.description],
            details[var_names.is_enabled], details[var_names.ping_type],
            details[var_names.url], details[var_names.email],
            details[var_names.interval], details[var_names.grace_period],
            details[var_names.service_id], details[var_names.task_title],
            details[var_names.text_msg], details[var_names.urgency_level],
            tags=details[var_names.tags] if var_names.tags in details else None,
            ip_address=details[var_names.ip_address] if var_names.ip_address in details else None,
            email_from=details[var_names.email_from] if var_names.email_from in details else None,
            packet_sizes=details[var_names.packet_sizes] if var_names.packet_sizes in details else None,
            degraded_timeout=details[var_names.degraded_timeout] if var_names.degraded_timeout in details else None,
            fail_timeout=details[var_names.fail_timeout] if var_names.fail_timeout in details else None,
            check_ssl_expiry=details[var_names.check_ssl_expiry] if var_names.check_ssl_expiry in details else None,
            port=details[var_names.port] if var_names.port in details else None,
            additional_info=details[var_names.additional_info] if var_names.additional_info in details else None,
            is_healthy=details[var_names.is_healthy] if var_names.is_healthy in details else None,
            last_run=times.get_timestamp_from_string(details[var_names.last_run])
            if var_names.last_run in details and details[var_names.last_run] is not None else None,
            next_run=times.get_timestamp_from_string(details[var_names.next_run])
            if var_names.next_run in details and details[var_names.next_run] is not None else None,
            events=details[var_names.events] if var_names.events in details else None,
            incidents=details[var_names.incidents] if var_names.incidents in details else None
        )

    @staticmethod
    def get_incoming_ping_url(check_ref_key):
        '''
        Get the URL for incoming HTTP ping of monitor checks like heartbeats.
        :param check_ref_key: (concealed) reference key of the monitor check
        :return: (str) ping url
        '''
        return '/'.join([settings.INTEGRATIONS_API_BASE_URL, 'checks', check_ref_key])

    @staticmethod
    def get_incoming_ping_email(check_ref_key):
        '''
        Get the email address for incoming email ping of monitor checks like heartbeats.
        :param check_ref_key: (concealed) reference key of the monitor check
        :return: (str) ping email
        '''
        # Add a 'h' to the front of the email address. This will avoid the email address
        # being marked as illegal in case it starts with a hyphen or underscore.
        if settings.TEST_SERVER:
            domain = constants.regional_test_server_urls[settings.REGION]['domain']
        else:
            domain = constants.regional_urls[settings.REGION]['domain']
        return 'h' + check_ref_key + '@' + '.'.join(['checks', domain])

    @staticmethod
    def extract_check_ref_id_from_ping_email_address(email_addr):
        '''
        Extract the reference key of a check from an email address.
        :param email_addr: email address
        :return: (concealed) reference ID of the check (None if the email address is not a valid ping endpoint)
        '''
        # Strip the h from the first part of the email address. This was added to avoid illegal email addresses.
        if not isinstance(email_addr, list):
            email_addr = [email_addr]
        for addr in email_addr:
            if addr.split('@')[1].split('.')[0] == 'checks':
                return addr.split('@')[0][1:]
        return None

    def ping_is_from_valid_source(self, received_ping_type, sender_ip=None, sender_email=None):
        '''
        Checks if a ping has been received from a valid source or not.
        :param received_ping_type: type of ping received
        :param sender_ip: IP address of the sender
        :param sender_email: email address of the sender
        :return: (boolean) True if ping received from valid source; False otherwise
        '''
        if self.ping_type == received_ping_type:
            if self.ping_type == constants.http:
                if self.ip_address is None:
                    return True
                else:
                    return True if sender_ip in self.ip_address else False
            elif self.ping_type == constants.email:
                if self.email_from is None:
                    return True
                else:
                    return True if sender_email in self.email_from else False

        return False

    def max_allowed_run_timestamp(self):
        if self.grace_period is None:
            return self.next_run
        else:
            if self.next_run is not None:
                return self.next_run + datetime.timedelta(self.grace_period)
        return None

    def has_heartbeat_check_failed(self, timestamp):
        '''
        Checks if a heartbeat check has failed or not.
        :param timestamp: timestamp when uptime is being tested
        :return: (boolean) True if the check has failed; False otherwise
        '''
        if self.check_type == constants.heartbeat:
            max_allowed_timestamp = self.max_allowed_run_timestamp()
            if max_allowed_timestamp is not None and timestamp > max_allowed_timestamp:
                return True
        return False

    def update_status(self, timestamp, has_passed=True, response_time=None, is_degraded=None,
                      instance_id=None, run_log=None):
        '''
        Set the status of a check and update the last and next run timestamps.
        :param timestamp: timestamp when the check passed
        :param has_passed: True if the check has passed; False otherwise
        :param response_time: (for some checks) response time from external ping server
        :param is_degraded: (for some checks) True if the check passed with a degraded status; False otherwise
        :param instance_id: (for failed checks) ID of the instance
        :param run_log: (for some checks) any log from the run
        :return: (dict) details of the check
        '''
        new_event = {
            var_names.scheduled_timestamp: self.next_run,
            var_names.run_timestamp: timestamp,
            var_names.response_time: response_time,
            var_names.passed: has_passed,
            var_names.degraded: is_degraded,
            var_names.instance_id: instance_id,
            var_names.check_log: run_log
        }
        if self.events is None:
            self.events = []
        self.events.append(new_event)

        self.is_healthy = has_passed
        self.last_run = timestamp
        self.next_run = timestamp + datetime.timedelta(seconds=self.interval)

        if instance_id is not None:
            self.add_incident(instance_id)
        if has_passed:
            self.incidents = None

    def add_incident(self, instance_id):
        '''
        Add an incident ID to the list of ongoing incidents.
        :param instance_id: ID of the incident
        '''
        if self.incidents is None:
            self.incidents = []
        self.incidents.append(instance_id)

    def get_incident_alert_body(self):
        '''
        Get the alert body that will be used as the source payload.
        :return: (dict) of alert body
        '''
        return {
            var_names.task_title: self.task_title,
            var_names.text_msg: self.text_msg,
            var_names.urgency_level: self.urgency_level,
            var_names.tags: self.tags,
            var_names.check_type: self.check_type,
            var_names.check_ref_id: key_manager.conceal_reference_key(self.check_ref_id)
        }

    def to_dict(self):
        '''
        Gets the dict of the Check object.
        :return: dict of Check
        '''
        data = {
            var_names.check_id: self.check_id,
            var_names.organization_id: self.organization_id,
            var_names.check_ref_id: self.check_ref_id,
            var_names.check_type: self.check_type,
            var_names.check_name: self.check_name,
            var_names.description: self.description,
            var_names.is_enabled: self.is_enabled,
            var_names.ping_type: self.ping_type,
            var_names.url: self.url,
            var_names.email: self.email,
            var_names.interval: self.interval,
            var_names.grace_period: self.grace_period,
            var_names.service_id: self.service_id,
            var_names.task_title: self.task_title,
            var_names.text_msg: self.text_msg,
            var_names.urgency_level: self.urgency_level,
            var_names.tags: self.tags,
            var_names.ip_address: self.ip_address,
            var_names.email_from: self.email_from,
            var_names.packet_sizes: self.packet_sizes,
            var_names.degraded_timeout: self.degraded_timeout,
            var_names.fail_timeout: self.fail_timeout,
            var_names.check_ssl_expiry: self.check_ssl_expiry,
            var_names.port: self.port,
            var_names.additional_info: self.additional_info,
            var_names.is_healthy: self.is_healthy,
            var_names.last_run: self.last_run,
            var_names.next_run: self.next_run,
            var_names.events: self.events,
            var_names.incidents: self.incidents
        }
        return data
