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

from data_syncers import syncer_policies
from dbqueries import db_accounts, db_routines
from exceptions.user_exceptions import DependencyFound, InvalidRequest, UnauthorizedRequest
from modules import on_call_manager
from rest_framework.decorators import api_view
from rest_framework.response import Response
from taskcallrest import settings
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 create_routine(request, conn=None):
    '''
    Create a new routine.
    :param request: HttpRequest
    :param conn: db connection
    :return: HttpResponse -> str
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.routine_name, var_names.timezone, var_names.routine_layers]
        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 permissions.has_user_permission(user_perm, permissions.USER_COMPONENTS_EDIT_PERMISSION):
                routine_name = request.data[var_names.routine_name]
                routine_timezone = request.data[var_names.timezone]
                routine_layers = request.data[var_names.routine_layers]
                current_time = times.get_current_timestamp()

                if db_routines.routine_name_is_unique(conn, current_time, org_id, routine_name):
                    db_routines.create_routine(conn, current_time, org_id, routine_name,
                                               routine_timezone, routine_layers)
                    return Response(_lt.get_label(info.msg_routine_created, lang))
                else:
                    return Response(_lt.get_label(errors.err_routine_name, lang), status=400)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except PermissionError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=403)
        except (InvalidRequest, ValueError) 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 edit_routine(request, conn=None, cache=None):
    '''
    Edits the details of an existing routine.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.routine_ref_id, var_names.routine_name,
                           var_names.timezone, var_names.routine_layers]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.can_user_edit_components(user_perm, org_perm):
                do_adv_check, do_comp_check, do_team_check =\
                    permissions.get_user_advanced_check_status(user_perm, org_perm)

                routine_ref_id = request.data[var_names.routine_ref_id]
                routine_name = request.data[var_names.routine_name]
                routine_timezone = request.data[var_names.timezone]
                routine_layers = request.data[var_names.routine_layers]
                current_time = times.get_current_timestamp()

                if db_routines.routine_name_is_unique(conn, current_time, org_id, routine_name,
                                                      ignore_routine=routine_ref_id):

                    pol_ids = []
                    if settings.CACHE_ON:
                        pol_ids = db_routines.get_routine_associated_policy_ids(conn, current_time, routine_ref_id)

                    db_routines.edit_routine(conn, current_time, user_id, org_id, routine_ref_id,
                                             routine_name, routine_timezone, routine_layers,
                                             check_adv_perm=do_adv_check,
                                             has_comp_perm=do_comp_check,
                                             has_team_perm=do_team_check)

                    if len(pol_ids) > 0:
                        syncer_policies.update_routine_associated_policies(conn, cache, current_time, pol_ids)

                    return Response(_lt.get_label(info.msg_routine_edited, lang))
                else:
                    return Response(_lt.get_label(errors.err_routine_name, lang), status=400)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except PermissionError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=403)
        except (InvalidRequest, ValueError) 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 delete_routine(request, conn=None, cache=None):
    '''
    Deletes a group.
    :param request: HttpRequest
    :param conn: db connection
    :param cache: cache client
    :return: HttpResponse -> str
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.routine_ref_id]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.can_user_edit_components(user_perm, org_perm):
                do_adv_check, do_comp_check, do_team_check =\
                    permissions.get_user_advanced_check_status(user_perm, org_perm)

                rou_ref_id = request.data[var_names.routine_ref_id]
                current_time = times.get_current_timestamp()

                pol_ids = []
                if settings.CACHE_ON:
                    pol_ids = db_routines.get_routine_associated_policy_ids(conn, current_time, rou_ref_id)

                if len(pol_ids) > 0:
                    raise DependencyFound(errors.err_routine_delete_policy_dependency)
                else:
                    db_routines.delete_routine(conn, current_time, user_id, org_id, rou_ref_id,
                                               check_adv_perm=do_adv_check,
                                               has_comp_perm=do_comp_check,
                                               has_team_perm=do_team_check)

                if len(pol_ids) > 0:
                    syncer_policies.update_routine_associated_policies(conn, cache, current_time, pol_ids)

                return Response(_lt.get_label(info.msg_routine_deleted, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except PermissionError as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=403)
        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 DependencyFound as e:
            logging.exception(e.message + e.additional_message)
            return Response(_lt.get_label(e.message, lang) + e.additional_message, status=400)
        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 add_routine_layer(request, conn=None, cache=None):
    '''
    Add a layer to an existing routine.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.routine_ref_id, var_names.routine_layers]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.can_user_edit_components(user_perm, org_perm):
                do_adv_check, do_comp_check, do_team_check =\
                    permissions.get_user_advanced_check_status(user_perm, org_perm)

                current_time = times.get_current_timestamp()
                rou_ref_id = request.data[var_names.routine_ref_id]
                layer = request.data[var_names.routine_layers]

                if layer[var_names.valid_start] == layer[var_names.valid_end]:
                    layer[var_names.valid_end] = (times.get_date_from_string(layer[var_names.valid_end]) +
                                                  datetime.timedelta(days=1)).strftime(constants.date_hyphen_format)

                db_routines.add_routine_layer(conn, current_time, user_id, org_id, rou_ref_id, layer,
                                              check_adv_perm=do_adv_check,
                                              has_comp_perm=do_comp_check,
                                              has_team_perm=do_team_check)

                asso_pol_ids = db_routines.get_routine_associated_policy_ids(conn, current_time, rou_ref_id)
                syncer_policies.update_routine_associated_policies(conn, cache, current_time, asso_pol_ids)

                return Response(_lt.get_label(info.msg_routine_overridden, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        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_routines_list(request, conn=None):
    '''
    Get the list of routines of an organization along with their basic information.
    :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.assignee_type]
        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)

            if permissions.can_user_view_components(user_perm):
                do_adv_check = permissions.get_user_advanced_check_status(user_perm, org_perm)[0]

                teams_only = False
                if var_names.assignee_type in request.data and\
                        request.data[var_names.assignee_type] == constants.team_assignee:
                    teams_only = True

                routines_list = db_routines.get_routines_list(
                    conn, times.get_current_timestamp(), user_id, org_id, user_teams_only=teams_only,
                    check_adv_perm=do_adv_check
                )
                return Response(routines_list)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        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_routine_details(request, conn=None):
    '''
    Get the details of a specific routine.
    :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.routine_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 permissions.can_user_view_components(user_perm):
                do_adv_check = permissions.get_user_advanced_check_status(user_perm, org_perm)[0]

                rou_ref_id = request.data[var_names.routine_ref_id]
                current_time = times.get_current_timestamp()
                retrieved_routines = db_routines.get_routines(
                    conn, current_time, org_id, routine_ref_id=rou_ref_id,
                    with_user_adv_perm=user_id if do_adv_check else None, for_display=True
                )
                if len(retrieved_routines) > 0:
                    requested_routine = list(retrieved_routines.values())[0]
                    for lyr in requested_routine.routine_layers:
                        lyr.localize_valid_times(requested_routine.routine_timezone)
                    serialized_routine = requested_routine.to_dict(basic_info=True)
                    return Response(serialized_routine)
                else:
                    return Response(_lt.get_label(errors.err_unknown_resource, lang), status=404)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        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_multiple_routines(request, conn=None):
    '''
    Get the details of multiple routines together.
    :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.routine_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 permissions.can_user_view_components(user_perm):
                do_adv_check = permissions.get_user_advanced_check_status(user_perm, org_perm)[0]

                ref_ids = request.data[var_names.routine_ref_id]
                current_time = times.get_current_timestamp()
                retrieved_routines = db_routines.get_routines(
                    conn, current_time, org_id, routine_ref_id=ref_ids,
                    with_user_adv_perm=user_id if do_adv_check else None, for_display=True
                )

                serialized_routines = dict()
                for item in list(retrieved_routines.values()):
                    for lyr in item.routine_layers:
                        lyr.localize_valid_times(item.routine_timezone)
                    serialized_routines[item.reference_id] = item.to_dict(basic_info=True)
                return Response(serialized_routines)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        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 routine_shifts(request, conn=None):
    '''
    Get a routine's on-call calendar.
    :param request: Http request
    :param conn: db connection
    :return: Http response -> dict
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.routine_ref_id, var_names.start_date, var_names.end_date]
        optional_fields = [var_names.timezone]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields, optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            rou_ref = request.data[var_names.routine_ref_id]
            start_date = times.get_date_from_string(request.data[var_names.start_date])
            end_date = times.get_date_from_string(request.data[var_names.end_date])
            current_time = times.get_current_timestamp()

            if permissions.can_user_view_components(user_perm):
                rou_dict = db_routines.get_routines(conn, current_time, org_id, routine_ref_id=rou_ref)
                if len(rou_dict) == 0:
                    raise LookupError(errors.err_unknown_resource)

                rou_id = list(rou_dict.keys())[0]
                rou_obj = rou_dict[rou_id]
                historical_rou_data = db_routines.get_historical_routines(conn, start_date, end_date, org_id, rou_id)
                rou_calendar, rou_shifts = on_call_manager.prepare_routine_calendar(
                    historical_rou_data, rou_obj.routine_timezone, start_date, end_date)

                details = {
                    var_names.display_name: rou_obj.routine_name,
                    var_names.routine_ref_id: rou_ref,
                    var_names.timezone: rou_obj.routine_timezone,
                    var_names.data: rou_calendar,
                    var_names.on_call_shifts: rou_shifts
                }
                return Response(details)
            else:
                return Response(_lt.get_label(errors.err_user_rights, 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 routine_webcal_info(request, conn=None):
    '''
    Get the information needed to form a WebCal URL. This will be sent to the web server to form the actual url.
    :param request: Http request
    :param conn: db connection
    :return: Http response -> dict
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.routine_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)
            current_time = times.get_current_timestamp()

            if permissions.can_user_view_components(user_perm):
                rou_ref = request.data[var_names.routine_ref_id]
                key_manager.unmask_reference_key(rou_ref)
                subdomain, acc_id, usr_pref_name = db_accounts.get_org_info_for_webcal(conn, current_time, user_id)
                details = {
                    var_names.routine_ref_id: rou_ref,
                    var_names.subdomain: subdomain,
                    var_names.account_id: acc_id
                }
                return Response(details)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except LookupError as e:
            logging.info(str(e))
            return Response(_lt.get_label(errors.err_unknown_resource, 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)
