"""
Addition of CLS privileges after upgrade that were missing from the source version
Also add these privileges to the CLS Admin role

This will be invoked only for Linux. Windows is no longer supported.
"""
import os
import sys

import warnings

vmware_python_path = os.getenv('VMWARE_PYTHON_PATH')
if vmware_python_path and os.path.exists(vmware_python_path):
    sys.path.append(vmware_python_path)
CONTENT_LIBRARY_HOME = "/usr/lib/vmware-content-library"
# Prefer the files in the directory of this file when importing. This is important during patching
# as the VC can have older copies of other files in /usr/lib/vmware-content-library
sys.path.insert(0, os.path.dirname(__file__))
sys.path.append(os.path.join(CONTENT_LIBRARY_HOME, "install_lib"))
# The comment 'noqa: E402' gets rid of the pep8 warning - Module level import not at the top of the file

from cis.cisreglib import (AuthzClient, CisregOptionParser, EP_AUTHZ_PROTOCOL, EP_AUTHZ_TYPE_ID,  # noqa: E402
                           LookupServiceClient, SsoClient, AUTHZ_TYPE_ID, _get_syscfg_info)
from cis.utils import log  # noqa: E402

from install_constants import (CLS_ADMIN_NAME_PREFIX, CLS_ADMIN_NAME_ID_PREFIX,  # noqa: E402
                               CLS_REGISTRY_ADMIN_ROLE_NAME_PREFIX, CLS_REGISTRY_ADMIN_ROLE_NAME_ID,
                               CLS_SERVICE_ID_KEY,
                               CONTENT_LIBRARY_CONFIG_HOME,
                               CONTENT_LIBRARY_HOME, CONTENT_LIBRARY_SERVICE_REGISTER_SPEC,
                               CONTENT_LIBRARY_SERVICE_SPEC, KEY_STORE_NAME_MACHINE,
                               MANAGE_CLUSTER_REGISTRY_PRIVILEGE, REVERSE_PROXY_ADDRESS_KEY,
                               REVERSE_PROXY_CONNECTION_TYPE_KEY, REVERSE_PROXY_CONNECTION_TYPE_LOCAL,
                               SOLUTION_USER_NAME_KEY, SOLUTION_USER_ID_KEY,
                               WCP_KUBERNETES_MANAGER_ROLE_NAME, WCP_VMOP_ROLE_NAME, DESIRED_VMOP_PRIVILEGES)


sys.path.append('/usr/lib/vmware-vmafd/lib64')

# Suppress warnings coming from vmafd and identity imports.
with warnings.catch_warnings():
    warnings.simplefilter('ignore', RuntimeWarning)
    from identity.vmkeystore import VmKeyStore

__author__ = 'VMware, Inc.'
__copyright__ = 'Copyright 2019 VMware, Inc. All rights reserved.'


sso_client = None
authz_client = None


def init_authz():
    global sso_client
    global authz_client
    ls_url, domain_name = _get_syscfg_info()
    log("Connecting to Lookup Service using %s for domain %s" % (ls_url, domain_name))
    ls_client = LookupServiceClient(ls_url, retry_count=1)
    sts_url, sts_cert_data = ls_client.get_sts_endpoint_data()

    with VmKeyStore('VKS') as ks:
        ks.load(KEY_STORE_NAME_MACHINE)
        cert = ks.get_certificate(KEY_STORE_NAME_MACHINE)
        key = ks.get_key(KEY_STORE_NAME_MACHINE)

    sso_client = SsoClient(sts_url, sts_cert_data, None, None, cert=cert, key=key)
    ls_client.set_sso_client(sso_client)
    authz_endpoints = ls_client.get_service_endpoints(
                       AUTHZ_TYPE_ID, ep_protocol=EP_AUTHZ_PROTOCOL,
                       ep_type=EP_AUTHZ_TYPE_ID, local_nodeid=None)
    authz_client = None
    # Try to connect to all the authz endpoints, as not all of them may
    # be available. Once the connection is successful, use authz server at
    # that endpoint
    for authz_endpoint in authz_endpoints:
        # We need to refresh the sso_client at every iteration, as after failed
        # connection attempts, the sso_client may not be valid any more
        sso_client = SsoClient(sts_url, sts_cert_data, None, None, cert=cert, key=key)
        try_authz_client = AuthzClient(authz_endpoint.url, sso_client)
        log("Trying to connect to authz endpoint {0}".format(authz_endpoint.url))
        # Validate this authz_client, use if validated
        try:
            try_authz_client._authz_service.GetRoles()
            log("Connection to authz endpoint {0} successful".format(authz_endpoint.url))
            authz_client = try_authz_client
            break
        except Exception:
            exc_type, exc_value, _ = sys.exc_info()
            log("Error: {0} {1}".format(exc_type, exc_value))
            log("Connection to authz endpoint {0} failed".format(authz_endpoint.url))
    if not authz_client:
        # raise an error
        authz_server_connect_error = "Failed to connect to authz server while" \
                                      " trying to add Content Library privileges"
        log(authz_server_connect_error)
        raise Exception(authz_server_connect_error)


def get_role(role_name_prefix, role_id_prefix):
    for role_obj in authz_client._authz_service.GetRoles():
        log("role.name = %s, role.id = %s" % (role_obj.name, role_obj.id))
        # The role name is not always mapped from id to prefix, so look for both
        if role_obj.name.startswith(role_name_prefix) \
                or role_obj.name.startswith(role_id_prefix):
            log("%s role: %s" % (role_name_prefix, role_obj))
            return role_obj
    log("Role %s not found" % role_name_prefix)
    return None


def register_cls():
    sys.path.append(CONTENT_LIBRARY_HOME)

    from cis_register import RegCIS

    service_register_properties = {}
    with open(CONTENT_LIBRARY_SERVICE_REGISTER_SPEC) as f:
        for line in f.readlines():
            if "=" in line:
                key, value = line.rstrip("\n").split("=")
                service_register_properties[key.strip()] = value.strip()

    solution_user_name = None
    solution_user_id = None
    reverse_proxy_address = None
    service_id = None
    for key, value in service_register_properties.items():
        if key == SOLUTION_USER_NAME_KEY:
            solution_user_name = value
        elif key == SOLUTION_USER_ID_KEY:
            solution_user_id = value
        elif key == REVERSE_PROXY_ADDRESS_KEY:
            reverse_proxy_address = value
        elif key == REVERSE_PROXY_CONNECTION_TYPE_KEY:
            reverse_proxy_connection_type = value
            if reverse_proxy_connection_type != REVERSE_PROXY_CONNECTION_TYPE_LOCAL:
                raise Exception("proxy connection type should be local in a patch setup!")
        elif key == CLS_SERVICE_ID_KEY:
            service_id = value

    log("Re-registering CL service (ID: %s) with lookup service" %(service_id))
    log("Using solution user name: %s, solution user id: %s, reverse proxy address: %s" %
        (solution_user_name, solution_user_id, reverse_proxy_address))

    # Note: This is a re-registration by specifying is_patch as true, and reverse proxy address for
    #       local connection is the port only
    reg_info = RegCIS(CONTENT_LIBRARY_HOME, services_spec=CONTENT_LIBRARY_SERVICE_SPEC,
                      vdc_cfg_dir=CONTENT_LIBRARY_CONFIG_HOME,
                      service_http_port=reverse_proxy_address, is_patch=True,
                      key_store_name=KEY_STORE_NAME_MACHINE)
    reg_info.registerAll(solution_user_name, solution_user_id, service_id=service_id)
    log("Reregistration successful")


def add_new_privileges(vdc_config_dir):
    global authz_client
    if authz_client is None:
        init_authz()

    cisreg_opt_parser = get_cisreg_opt_parser(vdc_config_dir)

    # Add any missing privileges to the system
    add_new_privileges_to_vc(authz_client, cisreg_opt_parser)
    # Grant any missing CLS privileges to the CLS Admin user
    cls_admin_role = get_role(CLS_ADMIN_NAME_PREFIX, CLS_ADMIN_NAME_ID_PREFIX)
    grant_new_privileges_to_role(authz_client, cisreg_opt_parser, cls_admin_role,
                                 CLS_ADMIN_NAME_ID_PREFIX)
    # Grant any missing CLS privileges to the CLS Registry Admin user
    cls_registry_admin_role = get_role(CLS_REGISTRY_ADMIN_ROLE_NAME_PREFIX, CLS_REGISTRY_ADMIN_ROLE_NAME_ID)
    grant_new_privileges_to_role(authz_client, cisreg_opt_parser, cls_registry_admin_role,
                                 CLS_REGISTRY_ADMIN_ROLE_NAME_ID)
    # Add privileges to the WCP role
    add_cluster_privilege_to_wcp_role(authz_client)
    # Add privileges to the VMoperator role
    add_content_library_privileges_to_vmop_role(authz_client)


def add_cluster_privilege_to_wcp_role(authz_client):
    # Grant missing ContentLibrary.ManageClusterRegistryResource privilege to the vSphereKubernetesManager role
    # The role name and id for the vSphereKubernetesManager role is the same
    wcp_kubernetes_manager_role = get_role(WCP_KUBERNETES_MANAGER_ROLE_NAME, WCP_KUBERNETES_MANAGER_ROLE_NAME)
    if wcp_kubernetes_manager_role:
        wcp_kubernetes_manager_role_privileges = wcp_kubernetes_manager_role.privilegeId
        log("Role vSphereKubernetesManager privileges {0}".format(wcp_kubernetes_manager_role_privileges))
        if MANAGE_CLUSTER_REGISTRY_PRIVILEGE not in wcp_kubernetes_manager_role_privileges:
            wcp_kubernetes_manager_role_privileges.append(MANAGE_CLUSTER_REGISTRY_PRIVILEGE)
            authz_client.update_role(wcp_kubernetes_manager_role.id, wcp_kubernetes_manager_role_privileges)
            log("Granted following privileges to the role %s: %s" % (wcp_kubernetes_manager_role.name,
                                                                     wcp_kubernetes_manager_role_privileges))
        else:
            log("%s privilege is already granted to the role %s" % (MANAGE_CLUSTER_REGISTRY_PRIVILEGE,
                                                                    wcp_kubernetes_manager_role.name))
    else:
        log("No %s role found, skipping granting of %s privileges" % (WCP_KUBERNETES_MANAGER_ROLE_NAME,
                                                                      MANAGE_CLUSTER_REGISTRY_PRIVILEGE))

def add_content_library_privileges_to_vmop_role(authz_client):
    # Grant missing content library permissions to the VMoperator controller
    # role.
    wcp_vmop_controller_global_role = get_role(WCP_VMOP_ROLE_NAME, WCP_VMOP_ROLE_NAME)
    if wcp_vmop_controller_global_role:
        wcp_vmop_controller_global_role_privileges = wcp_vmop_controller_global_role.privilegeId
        log("Role VMOperatorControllerGlobal privileges {0}".format(wcp_vmop_controller_global_role_privileges))
        for desired_privilege in DESIRED_VMOP_PRIVILEGES:
            if desired_privilege not in wcp_vmop_controller_global_role_privileges:
                wcp_vmop_controller_global_role_privileges.append(desired_privilege)
                authz_client.update_role(wcp_vmop_controller_global_role.id, wcp_vmop_controller_global_role_privileges)
                log("Granted following privileges to the role %s: %s" % (wcp_vmop_controller_global_role.name,
                                                                         wcp_vmop_controller_global_role_privileges))
            else:
                log("%s privilege is already granted to the role %s" % (desired_privilege,
                                                                        wcp_vmop_controller_global_role.name))
    else:
        log("No %s role found, skipping granting of %s privileges" % (WCP_VMOP_ROLE_NAME,
                                                                      DESIRED_VMOP_PRIVILEGES))


def get_cisreg_opt_parser(vdc_config_dir):
    cls_privileges_xml_file = os.path.join(vdc_config_dir, "metadata", "cls_privilege.xml")
    cls_admin_privileges_xml_file = os.path.join(vdc_config_dir, "metadata", "cls_role.xml")
    cisreg_options = {'permission.newPrivilege': cls_privileges_xml_file,
                      'permission.newRole': cls_admin_privileges_xml_file}
    cisreg_opt_parser = CisregOptionParser(cisreg_options)
    return cisreg_opt_parser


def get_all_cls_privileges(cisreg_opt_parser):
    return cisreg_opt_parser.get_privs()


def get_role_privileges(cisreg_opt_parser, role_name_id_prefix):
    role_list = cisreg_opt_parser.get_roles()
    role_in_a_list = [role for role in role_list if role.get('name').startswith(role_name_id_prefix)]
    if not role_in_a_list:
        log("Error: {0} role not found in metadata".format(role_name_id_prefix))
        return None
    else:
        return role_in_a_list[0].get('priv_ids')


def add_new_privileges_to_vc(authz_client, cisreg_opt_parser):
    # Get the list of CLS privileges from the cls metadata file
    privileges_list = get_all_cls_privileges(cisreg_opt_parser)
    existing_privileges = authz_client._authz_service.GetPrivileges()
    existing_privilege_ids = [p.id for p in existing_privileges]
    privileges_to_add = []
    for privilege in privileges_list:
        if privilege.id not in existing_privilege_ids:
            privileges_to_add.append(privilege)
    if privileges_to_add:
        log("Adding new CLS privileges [%s]" % ' '.join(priv.id for priv in privileges_to_add))
        authz_client.load_privs(privileges_to_add)
    else:
        log("No privileges to add")


def grant_new_privileges_to_role(authz_client, cisreg_opt_parser, role, role_name_id_prefix):
    if role:
        all_role_privileges = get_role_privileges(cisreg_opt_parser, role_name_id_prefix)
        if not all_role_privileges:
            # Unlikely, this is coming from a config file that's pretty much like source code
            log("Error: {0} role not found in metadata".format(role_name_id_prefix))
            log("Error: {0} may not have all necessary privileges".format(role_name_id_prefix))
            return
        granted_to_role_privilege_id_set = set(role.privilegeId)
        all_privileges_id_set = set(all_role_privileges)

        privilege_ids_to_be_granted \
            = all_privileges_id_set - granted_to_role_privilege_id_set

        if privilege_ids_to_be_granted:
            # There are some privileges we need to grant to the specified admin role
            log("Granting privileges [%s] to role %s"
                % (privilege_ids_to_be_granted, role.name))

            privileges_for_role \
                = list(privilege_ids_to_be_granted | granted_to_role_privilege_id_set)

            authz_client.update_role(role.id, privileges_for_role)
        else:
            log("No new privileges to grant to %s" % role.name)
    else:
        log("No %s role found, skipping granting of newly added privileges" % role_name_id_prefix)
