"""
Supplies the environment variables necessary to set up Local Lambda runtime
"""

import sys
from enum import IntEnum


class Python(IntEnum):
    TWO = 2


class EnvironmentVariables:
    """
    Use this class to get the environment variables necessary to run the Lambda function. It returns the AWS specific
    variables (credentials, regions, etc) along with any environment variables configured on the function.

    Customers define the name of the environment variables along with default values, if any, when creating the
    function. In order to test the function with different scenarios, customers can override values for some of the
    variables. This class supports three mechanisms of providing values to environment variables.
    If a variable is given a value through all the three mechanisms, then the value from higher priority will be used:

    Priority (Highest to Lowest):
        - Override Values - User specified these
        - Shell Environment Values - Came from the shell environment
        - Default Values - Hard coded values

    If a variable does *not* get a value from either of the above mechanisms, it is given a value of "" (empty string).
    If the value of a variable is an intrinsic function dict/list, then it is given a value of "" (empty string).

    If real AWS Credentials were supplied, this class will expose them through appropriate environment variables.
    If not, this class will provide the following placeholder values to AWS Credentials:
        region = "us-east-1"
        key = "defaultkey"
        secret = "defaultsecret"
    """

    _BLANK_VALUE = ""
    _DEFAULT_AWS_CREDS = {"region": "us-east-1", "key": "defaultkey", "secret": "defaultsecret"}

    def __init__(
        self,
        function_name=None,
        function_memory=None,
        function_timeout=None,
        function_handler=None,
        variables=None,
        shell_env_values=None,
        override_values=None,
        aws_creds=None,
    ):
        """
        Initializes this class. It takes in two sets of properties:
            a) (Required) Function information
            b) (Optional) Environment variable configured on the function

        :param str function_name: The name of the function
        :param integer function_memory: Memory size of the function in megabytes
        :param integer function_timeout: Function's timeout in seconds
        :param string function_handler: Handler of the function
        :param dict variables: Optional. Dict whose key is the environment variable names and value is the default
            values for the variable.
        :param dict shell_env_values: Optional. Dict containing values for the variables grabbed from the shell's
            environment.
        :param dict override_values: Optional. Dict containing values for the variables that will override the values
            from ``default_values`` and ``shell_env_values``.
        :param dict aws_creds: Optional. Dictionary containing AWS credentials passed to the Lambda runtime through
            environment variables. It should contain "key", "secret", "region" and optional "sessiontoken" keys
        """

        self._function = {
            "memory": function_memory,
            "timeout": function_timeout,
            "handler": function_handler,
            "name": function_name,
        }

        self.variables = variables or {}
        self.shell_env_values = shell_env_values or {}
        self.override_values = override_values or {}
        self.aws_creds = aws_creds or {}

    def resolve(self):
        """
        Resolves the values from different sources and returns a dict of environment variables to use when running
        the function locally.

        :return dict: Dict where key is the variable name and value is the value of the variable. Both key and values
            are strings
        """

        # AWS_* variables must always be passed to the function, but user has the choice to override them
        result = self._get_aws_variables()

        # Default value for the variable gets lowest priority
        for name, value in self.variables.items():

            # Shell environment values, second priority
            if name in self.shell_env_values:
                value = self.shell_env_values[name]

            # Overridden values, highest priority
            if name in self.override_values:
                value = self.override_values[name]

            # Any value must be a string when passed to Lambda runtime.
            # Runtime expects a Map<String, String> for environment variables
            result[name] = self._stringify_value(value)

        return result

    def add_lambda_event_body(self, value):
        """
        Adds the value of AWS_LAMBDA_EVENT_BODY environment variable.
        """
        self.variables["AWS_LAMBDA_EVENT_BODY"] = value

    @property
    def timeout(self):
        return self._function["timeout"]

    @timeout.setter
    def timeout(self, value):
        self._function["timeout"] = value

    @property
    def memory(self):
        return self._function["memory"]

    @memory.setter
    def memory(self, value):
        self._function["memory"] = value

    @property
    def handler(self):
        return self._function["handler"]

    @handler.setter
    def handler(self, value):
        self._function["handler"] = value

    @property
    def name(self):
        return self._function["name"]

    @name.setter
    def name(self, value):
        self._function["name"] = value

    def _get_aws_variables(self):
        """
        Returns the AWS specific environment variables that should be available in the Lambda runtime.
        They are prefixed it "AWS_*".

        :return dict: Name and value of AWS environment variable
        """

        result = {
            # Variable that says this function is running in Local Lambda
            "AWS_SAM_LOCAL": "true",
            # Function configuration
            "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": str(self.memory),
            "AWS_LAMBDA_FUNCTION_TIMEOUT": str(self.timeout),
            "AWS_LAMBDA_FUNCTION_HANDLER": self._function["handler"],
            "AWS_LAMBDA_FUNCTION_NAME": str(self.name),
            "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST",
            "AWS_LAMBDA_LOG_GROUP_NAME": f"aws/lambda/{self.name}",
            "AWS_LAMBDA_LOG_STREAM_NAME": "$LATEST",
            # AWS Credentials - Use the input credentials or use the defaults
            "AWS_REGION": self.aws_creds.get("region", self._DEFAULT_AWS_CREDS["region"]),
            "AWS_DEFAULT_REGION": self.aws_creds.get("region", self._DEFAULT_AWS_CREDS["region"]),
            "AWS_ACCESS_KEY_ID": self.aws_creds.get("key", self._DEFAULT_AWS_CREDS["key"]),
            "AWS_SECRET_ACCESS_KEY": self.aws_creds.get("secret", self._DEFAULT_AWS_CREDS["secret"]),
            "AWS_ACCOUNT_ID": "123456789012",
        }

        # Session Token should be added **only** if the input creds have a token and the value is not empty.
        if self.aws_creds.get("sessiontoken"):
            result["AWS_SESSION_TOKEN"] = self.aws_creds.get("sessiontoken")

        return result

    def _stringify_value(self, value):
        """
        This method stringifies values of environment variables. If the value of the method is a list or dictionary,
        then this method will replace it with empty string. Values of environment variables in Lambda must be a string.
        List or dictionary usually means they are intrinsic functions which have not been resolved.

        :param value: Value to stringify
        :return string: Stringified value
        """

        # List/dict/None values are replaced with a blank
        if isinstance(value, (dict, list, tuple)) or value is None:
            result = self._BLANK_VALUE

        # str(True) will output "True". To maintain backwards compatibility we need to output "true" or "false"
        elif value is True:
            result = "true"
        elif value is False:  # pylint: disable=compare-to-zero
            result = "false"

        # value is a scalar type like int, str which can be stringified
        # do not stringify unicode in Py2, Py3 str supports unicode
        elif sys.version_info.major > Python.TWO:
            result = str(value)
        elif not isinstance(value, unicode):  # noqa: F821 pylint: disable=undefined-variable
            result = str(value)
        else:
            result = value

        return result

    def __eq__(self, other):
        if not isinstance(other, EnvironmentVariables):
            return False
        return self.resolve() == other.resolve()
