# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from awscli.customizations.emr import constants
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import exceptions
from botocore.compat import OrderedDict


CONSISTENT_OPTIONAL_KEYS = ['RetryCount', 'RetryPeriod']
CSE_KMS_REQUIRED_KEYS = ['KMSKeyId']
CSE_CUSTOM_REQUIRED_KEYS = ['CustomProviderLocation', 'CustomProviderClass']
CSE_PROVIDER_TYPES = [constants.EMRFS_KMS, constants.EMRFS_CUSTOM]
ENCRYPTION_TYPES = [constants.EMRFS_CLIENT_SIDE, constants.EMRFS_SERVER_SIDE]

CONSISTENT_OPTION_NAME = "--emrfs Consistent=true/false"
CSE_OPTION_NAME = '--emrfs Encryption=ClientSide'
CSE_KMS_OPTION_NAME = '--emrfs Encryption=ClientSide,ProviderType=KMS'
CSE_CUSTOM_OPTION_NAME = '--emrfs Encryption=ClientSide,ProviderType=Custom'


def build_bootstrap_action_configs(region, emrfs_args):
    bootstrap_actions = []

    _verify_emrfs_args(emrfs_args)

    if _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        # Download custom encryption provider from Amazon S3 to EMR Cluster
        bootstrap_actions.append(
            emrutils.build_bootstrap_action(
                path=constants.EMRFS_CSE_CUSTOM_S3_GET_BA_PATH,
                name=constants.S3_GET_BA_NAME,
                args=[constants.S3_GET_BA_SRC,
                      emrfs_args.get('CustomProviderLocation'),
                      constants.S3_GET_BA_DEST,
                      constants.EMRFS_CUSTOM_DEST_PATH,
                      constants.S3_GET_BA_FORCE]))

    emrfs_setup_ba_args = _build_ba_args_to_setup_emrfs(emrfs_args)
    bootstrap_actions.append(
        emrutils.build_bootstrap_action(
            path=emrutils.build_s3_link(
                relative_path=constants.CONFIG_HADOOP_PATH,
                region=region),
            name=constants.EMRFS_BA_NAME,
            args=emrfs_setup_ba_args))

    return bootstrap_actions


def build_emrfs_confiuration(emrfs_args):
    _verify_emrfs_args(emrfs_args)
    emrfs_properties = _build_emrfs_properties(emrfs_args)

    if _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        emrfs_properties[constants.EMRFS_CSE_CUSTOM_PROVIDER_URI_KEY] = \
            emrfs_args.get('CustomProviderLocation')

    emrfs_configuration = {
        'Classification': constants.EMRFS_SITE,
        'Properties': emrfs_properties}

    return emrfs_configuration


def _verify_emrfs_args(emrfs_args):
    # Encryption should have a valid value
    if 'Encryption' in emrfs_args \
            and emrfs_args['Encryption'].upper() not in ENCRYPTION_TYPES:
        raise exceptions.UnknownEncryptionTypeError(
            encryption=emrfs_args['Encryption'])

    # Only one of SSE and Encryption should be configured
    if 'SSE' in emrfs_args and 'Encryption' in emrfs_args:
        raise exceptions.BothSseAndEncryptionConfiguredError(
            sse=emrfs_args['SSE'], encryption=emrfs_args['Encryption'])

    # CSE should be configured correctly
    # ProviderType should be present and should have valid value
    # Given the type, the required parameters should be present
    if ('Encryption' in emrfs_args and
            emrfs_args['Encryption'].upper() == constants.EMRFS_CLIENT_SIDE):
        if 'ProviderType' not in emrfs_args:
            raise exceptions.MissingParametersError(
                object_name=CSE_OPTION_NAME, missing='ProviderType')
        elif emrfs_args['ProviderType'].upper() not in CSE_PROVIDER_TYPES:
            raise exceptions.UnknownCseProviderTypeError(
                provider_type=emrfs_args['ProviderType'])
        elif emrfs_args['ProviderType'].upper() == 'KMS':
            _verify_required_args(emrfs_args.keys(), CSE_KMS_REQUIRED_KEYS,
                                  CSE_KMS_OPTION_NAME)
        elif emrfs_args['ProviderType'].upper() == 'CUSTOM':
            _verify_required_args(emrfs_args.keys(), CSE_CUSTOM_REQUIRED_KEYS,
                                  CSE_CUSTOM_OPTION_NAME)

    # No child attributes should be present if the parent feature is not
    # configured
    if 'Consistent' not in emrfs_args:
        _verify_child_args(emrfs_args.keys(), CONSISTENT_OPTIONAL_KEYS,
                           CONSISTENT_OPTION_NAME)
    if not _need_to_configure_cse(emrfs_args, 'KMS'):
        _verify_child_args(emrfs_args.keys(), CSE_KMS_REQUIRED_KEYS,
                           CSE_KMS_OPTION_NAME)
    if not _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        _verify_child_args(emrfs_args.keys(), CSE_CUSTOM_REQUIRED_KEYS,
                           CSE_CUSTOM_OPTION_NAME)


def _verify_required_args(actual_keys, required_keys, object_name):
    if any(x not in actual_keys for x in required_keys):
        missing_keys = list(
            sorted(set(required_keys).difference(set(actual_keys))))
        raise exceptions.MissingParametersError(
            object_name=object_name, missing=emrutils.join(missing_keys))


def _verify_child_args(actual_keys, child_keys, parent_object_name):
    if any(x in actual_keys for x in child_keys):
        invalid_keys = list(
            sorted(set(child_keys).intersection(set(actual_keys))))
        raise exceptions.InvalidEmrFsArgumentsError(
            invalid=emrutils.join(invalid_keys),
            parent_object_name=parent_object_name)


def _build_ba_args_to_setup_emrfs(emrfs_args):
    emrfs_properties = _build_emrfs_properties(emrfs_args)

    return _create_ba_args(emrfs_properties)


def _build_emrfs_properties(emrfs_args):
    """
    Assumption: emrfs_args is valid i.e. all required attributes are present
    """
    emrfs_properties = OrderedDict()

    if _need_to_configure_consistent_view(emrfs_args):
        _update_properties_for_consistent_view(emrfs_properties, emrfs_args)

    if _need_to_configure_sse(emrfs_args):
        _update_properties_for_sse(emrfs_properties, emrfs_args)

    if _need_to_configure_cse(emrfs_args, 'KMS'):
        _update_properties_for_cse(emrfs_properties, emrfs_args, 'KMS')

    if _need_to_configure_cse(emrfs_args, 'CUSTOM'):
        _update_properties_for_cse(emrfs_properties, emrfs_args, 'CUSTOM')

    if 'Args' in emrfs_args:
        for arg_value in emrfs_args.get('Args'):
            key, value = emrutils.split_to_key_value(arg_value)
            emrfs_properties[key] = value

    return emrfs_properties


def _need_to_configure_consistent_view(emrfs_args):
    return 'Consistent' in emrfs_args


def _need_to_configure_sse(emrfs_args):
    return 'SSE' in emrfs_args \
        or ('Encryption' in emrfs_args and
            emrfs_args['Encryption'].upper() == constants.EMRFS_SERVER_SIDE)


def _need_to_configure_cse(emrfs_args, cse_type):
    return ('Encryption' in emrfs_args and
            emrfs_args['Encryption'].upper() == constants.EMRFS_CLIENT_SIDE and
            'ProviderType' in emrfs_args and
            emrfs_args['ProviderType'].upper() == cse_type)


def _update_properties_for_consistent_view(emrfs_properties, emrfs_args):
    emrfs_properties[constants.EMRFS_CONSISTENT_KEY] = \
        str(emrfs_args['Consistent']).lower()

    if 'RetryCount' in emrfs_args:
        emrfs_properties[constants.EMRFS_RETRY_COUNT_KEY] = \
            str(emrfs_args['RetryCount'])

    if 'RetryPeriod' in emrfs_args:
        emrfs_properties[constants.EMRFS_RETRY_PERIOD_KEY] = \
            str(emrfs_args['RetryPeriod'])


def _update_properties_for_sse(emrfs_properties, emrfs_args):
    sse_value = emrfs_args['SSE'] if 'SSE' in emrfs_args else True
    # if 'SSE' is not in emrfs_args then 'Encryption' must be 'ServerSide'

    emrfs_properties[constants.EMRFS_SSE_KEY] = str(sse_value).lower()


def _update_properties_for_cse(emrfs_properties, emrfs_args, cse_type):
    emrfs_properties[constants.EMRFS_CSE_KEY] = 'true'
    if cse_type == 'KMS':
        emrfs_properties[
            constants.EMRFS_CSE_ENCRYPTION_MATERIALS_PROVIDER_KEY] = \
            constants.EMRFS_CSE_KMS_PROVIDER_FULL_CLASS_NAME

        emrfs_properties[constants.EMRFS_CSE_KMS_KEY_ID_KEY] =\
            emrfs_args['KMSKeyId']

    elif cse_type == 'CUSTOM':
        emrfs_properties[
            constants.EMRFS_CSE_ENCRYPTION_MATERIALS_PROVIDER_KEY] = \
            emrfs_args['CustomProviderClass']


def _update_emrfs_ba_args(ba_args, key_value):
    ba_args.append(constants.EMRFS_BA_ARG_KEY)
    ba_args.append(key_value)


def _create_ba_args(emrfs_properties):
    ba_args = []
    for key, value in emrfs_properties.items():
        key_value = key
        if value:
            key_value = key_value + "=" + value
        _update_emrfs_ba_args(ba_args, key_value)

    return ba_args
