# By: Riasat Ullah
# This file contains all the views related to organization members.

from data_syncers import syncer_organizations
from dbqueries import db_members, db_organizations, db_users, db_workflows
from exceptions.user_exceptions import DependencyFound, InvalidPassword, InvalidRequest, NotUniqueValue,\
    UnauthorizedRequest
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, informational_content, key_manager, logging, mail, permissions, roles,\
    times, tokenizer, var_names
from utils.db_connection import CACHE_CLIENT, CONN_POOL
from validations import request_validator
import jwt


@api_view(['POST'])
def register_organization_member(request, conn=None):
    '''
    Registers an organization member user account.
    :param request: Http request
    :param conn: db connection
    :return: Http response -> str
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.email, var_names.first_name, var_names.last_name,
                           var_names.password, var_names.iso_country_code, var_names.phone,
                           var_names.preferred_username, var_names.timezone, var_names.language,
                           var_names.token, var_names.ip_address, var_names.profile_picture]
        optional_fields = [var_names.subdomain]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields, optional_fields)

            current_time = times.get_current_timestamp()
            req_email = request.data[var_names.email]

            # make sure that the request contains a system generated valid registration token
            tk_email, tk_ip = tokenizer.read_registration_token(request.data[var_names.token])
            if not (tk_email == req_email and tk_ip == request.data[var_names.ip_address]):
                raise InvalidRequest(errors.err_invalid_request)

            # If subdomain is present in the request then treat it as auto provisioning through SSO.
            if var_names.subdomain in request.data:
                org_id, org_perm, role_id = db_organizations.get_organization_account_auto_provision_info(
                    conn, current_time, request.data[var_names.subdomain])
                job_title = None
            else:
                org_id, org_perm, role_id, role_name, job_title =\
                    db_organizations.get_requested_user_account_info(conn, current_time, req_email)

            # Do not allow a user to register as Owner.
            if role_id == roles.owner_role_id:
                raise InvalidRequest(errors.err_requested_user_owner_role)

            # Checks if the member role (including stakeholder roles) is allowed in the organization or not
            if not permissions.is_user_role_allowed(org_perm, role_id):
                raise InvalidRequest(errors.err_user_role_invalid)

            onb_flow_id = None
            if role_id in roles.get_stakeholder_user_role_ids():
                db_members.register_stakeholder(
                    conn, current_time, org_id, org_perm, role_id, job_title, req_email,
                    request.data[var_names.first_name].title(), request.data[var_names.last_name].title(),
                    request.data[var_names.password], request.data[var_names.iso_country_code],
                    request.data[var_names.phone], request.data[var_names.preferred_username],
                    request.data[var_names.timezone], request.data[var_names.language],
                    request.data[var_names.profile_picture]
                )
            else:
                onb_flow_id = key_manager.generate_reference_key()
                db_members.register_standard_member(
                    conn, current_time, org_id, org_perm, role_id, job_title, req_email,
                    request.data[var_names.first_name].title(), request.data[var_names.last_name].title(),
                    request.data[var_names.password], request.data[var_names.iso_country_code],
                    request.data[var_names.phone], request.data[var_names.preferred_username],
                    request.data[var_names.timezone], request.data[var_names.language],
                    request.data[var_names.profile_picture], onb_flow_id
                )

            # send a welcome email to the user
            subject, message = informational_content.welcome_on_board_content()
            mail.AmazonSesDispatcher(subject, message, req_email).start()

            if onb_flow_id is not None:
                onb_flow_id = str(onb_flow_id)

            return Response(onb_flow_id)
        except (InvalidRequest, InvalidPassword, LookupError, NotUniqueValue) 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_invalid_request, 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_organization_member(request, conn=None, cache=None):
    '''
    Deletes a member of an organization.
    :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.member_name]
        optional_fields = [var_names.transfer_to]
        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, optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            member_name = request.data[var_names.member_name]
            replacing_member = request.data[var_names.transfer_to] if var_names.transfer_to in request.data else None
            current_time = times.get_current_timestamp()

            # get the user ids of the member and the replacing member
            pref_names_list = [member_name]
            if replacing_member is not None:
                pref_names_list.append(replacing_member)

            pref_name_user_id_map = db_users.get_user_ids_from_preferred_usernames(conn, current_time, org_id,
                                                                                   pref_names_list, as_dict=True)
            mem_id = pref_name_user_id_map[member_name]
            replacing_mem_id = pref_name_user_id_map[replacing_member] if replacing_member is not None else None

            if permissions.is_user_admin(user_perm):

                # Allow the operation only if the member being removed is not the owner.
                current_role_id = db_members.get_member_role_id(conn, current_time, org_id, mem_id)
                if current_role_id == roles.owner_role_id:
                    raise ValueError(errors.err_org_owner_removal)

                if current_role_id in roles.get_stakeholder_user_role_ids():
                    switched_workflows = db_workflows.check_component_in_workflow(
                        conn, current_time, org_id, user_id=mem_id
                    )
                    if len(switched_workflows) == 0:
                        switched_workflows = None
                    db_members.delete_stakeholder(conn, current_time, org_id, mem_id,
                                                  updated_workflows=switched_workflows)
                else:
                    if replacing_mem_id is None:
                        raise InvalidRequest(errors.err_deleting_user_role_transfer_missing)
                    if mem_id == replacing_mem_id:
                        raise InvalidRequest(errors.err_deleting_user_role_transfer)
                    syncer_organizations.delete_standard_member(conn, cache, current_time, org_id,
                                                                mem_id, replacing_mem_id)
                return Response(_lt.get_label(info.msg_user_deleted, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, 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_member_role(request, conn=None):
    '''
    Edits the role of a member within the organization.
    :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.preferred_username, var_names.user_role]
        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()
            pref_name = request.data[var_names.preferred_username]
            new_role = request.data[var_names.user_role]

            member_id = db_users.get_user_ids_from_preferred_usernames(conn, current_time, org_id, [pref_name])[0]
            new_role_id = roles.get_role_id_from_name(new_role)
            curr_role_id = db_members.get_member_role_id(conn, current_time, org_id, member_id)

            # Only 1 owner is allowed in the organization. Ensure the new requested role is not that of the owner.
            if new_role_id == roles.owner_role_id:
                raise PermissionError(errors.err_org_owner_transfer)

            # The owner's role cannot be changed here. In order to change the owner's
            # role the owner must transfer ownership herself.
            if curr_role_id == roles.owner_role_id:
                raise PermissionError(errors.err_org_owner_removal)

            # ensure that the requested user role is allowed in the organization
            if not permissions.is_user_role_allowed(org_perm, new_role_id):
                raise InvalidRequest(errors.err_user_role_invalid)

            if permissions.is_user_admin(user_perm):
                if roles.is_upgrading_from_stakeholder(curr_role_id, new_role_id):
                    db_members.upgrade_to_standard_user(conn, current_time, org_id, org_perm, member_id, new_role_id)
                elif roles.is_downgrading_to_stakeholder(curr_role_id, new_role_id):
                    db_members.downgrade_to_stakeholder(conn, current_time, org_id, org_perm, member_id, new_role_id)
                else:
                    db_members.update_member_role(conn, current_time, org_id, org_perm, member_id, new_role_id)
                return Response(_lt.get_label(info.msg_user_role_updated, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (InvalidRequest, ValueError) 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(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 unlock_member(request, conn=None):
    '''
    Unlocks an organization member.
    :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.preferred_username]
        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()
            pref_name = request.data[var_names.preferred_username]
            member_id = db_users.get_user_ids_from_preferred_usernames(conn, current_time, org_id, [pref_name])[0]

            # The user unlocking needs to be an admin and may not unlock himself.
            if permissions.is_user_admin(user_perm) and member_id != user_id:
                db_members.unlock_member(conn, current_time, org_id, member_id, user_id)
                return Response(_lt.get_label(info.msg_user_unlocked, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (InvalidRequest, ValueError) 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(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 transfer_ownership(request, conn=None):
    '''
    Edits the role of a member within the organization.
    :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.member_name, var_names.user_role]
        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)

            curr_owner_new_role_id = roles.get_role_id_from_name(request.data[var_names.user_role])
            new_owner_name = request.data[var_names.member_name]
            current_time = times.get_current_timestamp()

            new_owner_id = db_users.get_user_ids_from_preferred_usernames(conn, current_time, org_id,
                                                                          [new_owner_name])[0]

            if not permissions.is_user_role_allowed(org_perm, curr_owner_new_role_id):
                raise InvalidRequest(errors.err_user_role_invalid)

            if permissions.is_user_owner(user_perm):
                if user_id != new_owner_id:
                    db_members.transfer_account_ownership(conn, current_time, org_id, org_perm, user_id,
                                                          curr_owner_new_role_id, new_owner_id)
                return Response(_lt.get_label(info.msg_org_owner_transferred, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (InvalidRequest, ValueError, PermissionError) 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 organization_members(request, conn=None):
    '''
    Gets the list of members of an organization.
    :param request: Http request
    :param conn: db connection
    :return: Http response -> (list of tuple)
        [ (preferred username, display_name, email, role name, job title), ... ]
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        optional_fields = [var_names.assignee_type, var_names.keywords, var_names.row_limit, var_names.row_offset]
        try:
            request_validator.validate_fields(request, [], optional_fields)
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            team_member_id = user_id if var_names.assignee_type in request.data and \
                request.data[var_names.assignee_type] == constants.team_assignee else None
            keywords = request.data[var_names.keywords] if var_names.keywords 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
            current_time = times.get_current_timestamp()

            if permissions.has_user_permission(user_perm, permissions.USER_ORG_MEMBERS_VIEW_PERMISSION):
                members = db_members.get_members_list(
                    conn, current_time, org_id, team_member_id,
                    row_limit=row_limit, row_offset=row_offset, search_words=keywords
                )
                return Response(members)
            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)
