# By: Riasat Ullah
# This file contains functions related to heartbeat views.

from data_syncers import syncer_checks
from dbqueries import db_task_instances
from dbqueries.checks import db_heartbeats
from exceptions.user_exceptions import InvalidRequest, UnauthorizedRequest
from objects.check import Check
from rest_framework.decorators import api_view
from rest_framework.response import Response
from translators import label_translator as _lt
from utils import constants, errors, info, key_manager, logging, permissions, times, tokenizer, var_names
from utils.db_connection import CACHE_CLIENT, CONN_POOL
from validations import request_validator
import datetime
import jwt


@api_view(['POST'])
def list_heartbeats(request, conn=None):
    '''
    Get the list of heartbeats associated with an organization.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        optional_fields = [var_names.keywords, var_names.is_healthy, var_names.services,
                           var_names.row_limit, var_names.row_offset]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, [], optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            keywords = request.data[var_names.keywords] if var_names.keywords in request.data else None
            services = request.data[var_names.services] if var_names.services in request.data else None
            is_healthy = request.data[var_names.is_healthy] if var_names.is_healthy in request.data else None
            row_limit = request.data[var_names.row_limit] if var_names.row_limit in request.data else None
            row_offset = request.data[var_names.row_offset] if var_names.row_offset in request.data else None

            if not permissions.has_org_permission(org_perm, permissions.ORG_CHECKS_BASIC_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.can_user_view_components(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            do_adv_check = permissions.get_user_advanced_check_status(user_perm, org_perm)[0]
            hbt_list = db_heartbeats.list_heartbeats(
                conn, times.get_current_timestamp(), org_id, user_id,
                is_healthy=is_healthy, service_ref_ids=services, search_words=keywords,
                row_limit=row_limit, row_offset=row_offset, check_adv_perm=do_adv_check
            )
            return Response(hbt_list)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_heartbeat_details(request, conn=None):
    '''
    Get the details of a heartbeat.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.check_ref_id]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_CHECKS_BASIC_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.can_user_view_components(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            do_adv_check = permissions.get_user_advanced_check_status(user_perm, org_perm)[0]
            hbt_det = db_heartbeats.get_heartbeat_details(
                conn, times.get_current_timestamp(), org_id, user_id,
                request.data[var_names.check_ref_id], check_adv_perm=do_adv_check
            )
            return Response(hbt_det)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_heartbeat_endpoints(request, conn=None):
    '''
    Get the ping endpoints of a heartbeat.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, [])
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if not permissions.has_org_permission(org_perm, permissions.ORG_CHECKS_BASIC_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.can_user_edit_components(user_perm, org_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)

            new_check_ref_key = key_manager.conceal_reference_key(key_manager.generate_reference_key())
            output = {
                var_names.check_ref_id: new_check_ref_key,
                var_names.url: Check.get_incoming_ping_url(new_check_ref_key),
                var_names.email: Check.get_incoming_ping_email(new_check_ref_key)
            }
            return Response(output)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except PermissionError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=403)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_heartbeat_metrics(request, conn=None):
    '''
    Get the metrics of a heartbeat.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.check_ref_id, var_names.timezone, var_names.start_date, var_names.end_date]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            check_ref_id = request.data[var_names.check_ref_id]
            local_tz = request.data[var_names.timezone]
            utc_start_date = times.get_timestamp_from_string(request.data[var_names.start_date])
            utc_end_date = times.get_timestamp_from_string(request.data[var_names.end_date])
            current_time = times.get_current_timestamp()

            # adjust the start and end dates to match the graph axis scale
            utc_start_date = utc_start_date.replace(second=0) - datetime.timedelta(
                minutes=utc_start_date.minute - round(utc_start_date.minute / 5) * 5)
            utc_end_date = utc_end_date.replace(second=0) + datetime.timedelta(
                minutes=utc_end_date.minute - round(utc_end_date.minute / 5) * 5)

            # we may need to convert the dates from their local timezone to UTC???

            if not permissions.has_org_permission(org_perm, permissions.ORG_CHECKS_BASIC_PERMISSION):
                return Response(_lt.get_label(errors.err_subscription_rights, lang), status=403)
            if not permissions.can_user_view_components(user_perm):
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
            if (utc_end_date - utc_start_date).days > 31:
                return Response(errors.err_check_metrics_period, status=401)

            do_adv_check = permissions.get_user_advanced_check_status(user_perm, org_perm)[0]
            hbt_det = db_heartbeats.get_heartbeat_metrics(
                conn, current_time, org_id, user_id, check_ref_id, utc_start_date, utc_end_date,
                check_adv_perm=do_adv_check
            )

            start_date = times.utc_to_region_time(utc_start_date, local_tz)
            end_date = times.utc_to_region_time(utc_end_date, local_tz)

            # Calculate some the metrics.
            if len(hbt_det) > 0:
                hbt_events = hbt_det[var_names.events]

                hour_diff = round((end_date - start_date).total_seconds()/(60 * 60))
                min_gap = 1
                if 1 < hour_diff <= 3:
                    min_gap = 5
                if 3 < hour_diff <= 24:
                    min_gap = 30
                elif 24 < hour_diff <= 72:
                    min_gap = 60
                elif 72 < hour_diff <= 168:
                    min_gap = 360
                elif hour_diff > 168:
                    min_gap = 1440

                checks_passed, checks_failed, total_checks, inc_id_list = 0, 0, 0, []
                time_series = []
                marker = start_date
                aggregates_calculated = False
                while marker < end_date:
                    new_marker = marker + datetime.timedelta(minutes=min_gap)
                    data_point = {
                        var_names.period: [marker, new_marker],
                        var_names.labels: marker.strftime('%H:%M') if min_gap < 60 else marker.strftime('%m/%d %H:%M'),
                        var_names.total: 0,
                        var_names.passed: 0,
                        var_names.failed: 0,
                    }
                    for evn in hbt_events:
                        if marker <= times.utc_to_region_time(evn[var_names.run_timestamp], local_tz) < new_marker:
                            data_point[var_names.total] += 1
                            if evn[var_names.passed]:
                                data_point[var_names.passed] += 1
                            else:
                                data_point[var_names.failed] += 1

                        if not aggregates_calculated:
                            if evn[var_names.passed] is not None:
                                total_checks += 1
                                if evn[var_names.passed]:
                                    checks_passed += 1
                                else:
                                    checks_failed += 1
                                    if evn[var_names.instance_id] is not None:
                                        inc_id_list.append(evn[var_names.instance_id])
                    time_series.append(data_point)
                    aggregates_calculated = True
                    marker = new_marker

                hbt_det[var_names.passed] = checks_passed
                hbt_det[var_names.failed] = checks_failed
                hbt_det[var_names.total] = total_checks
                hbt_det[var_names.availability] = round(checks_passed/total_checks * 100) if total_checks > 0 else 100
                hbt_det[var_names.checks] = time_series
                hbt_det[var_names.incidents] = db_task_instances.list_instances(
                    conn, current_time, org_id, user_id, instance_ids=inc_id_list, check_adv_perm=do_adv_check)

            return Response(hbt_det)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['GET', 'POST'])
def process_heartbeat_http_ping(request, check_key, conn=None, cache=None):
    '''
    Processes a heartbeat HTTP ping.
    :param request: Http request
    :param check_key: monitor check key passed in the url
    :param conn: db connection
    :param cache: cache client
    :return: Http response
    '''
    if request.method in ['GET', 'POST']:
        lang = request_validator.get_user_language(request)
        ip_address = request.META.get(constants.ip_address_attribute)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache

            current_time = times.get_current_timestamp()
            syncer_checks.log_heartbeat(conn, cache, current_time, check_key, constants.http, ip_address)
            return Response({var_names.status: info.msg_internal_success, var_names.timestamp: current_time})
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_invalid_request, lang), status=400)
        except LookupError:
            logging.error(errors.err_unknown_resource + ': Heartbeat ping - ' + check_key)
            return Response(_lt.get_label(errors.err_unknown_resource, lang), status=404)
        except PermissionError:
            logging.error(errors.err_authorization + ': Heartbeat IP address disallowed - ' + ip_address)
            return Response(_lt.get_label(errors.err_authorization, lang), status=403)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)
