import os

from backend.server.common.config.base_config import BaseConfig
from backend.common.errors import ConfigurationError
from backend.server.common.config import get_secret_key
from backend.common.errors import SecretKeyRetrievalError
from backend.common.utils.type_conversion_utils import convert_string_to_value


class ExternalConfig(BaseConfig):
    """Manages the config attribute associated with external configuration sources, such as
    environment variables or the AWS Secrets Manager."""

    def __init__(self, app_config, default_config):
        super().__init__(app_config, default_config)
        try:
            self.environment = default_config["environment"]
            self.aws_secrets_manager__region = default_config["aws_secrets_manager"]["region"]
            self.aws_secrets_manager__secrets = default_config["aws_secrets_manager"]["secrets"]

        except KeyError as e:
            raise ConfigurationError(f"Unexpected config: {str(e)}")

    def complete_config(self, context):
        self.handle_environment(context)
        self.handle_aws_secrets_manager(context)

    def handle_environment(self, context):
        """For each environment variable defined, get the value (if it is set),
        and set the specified config parameter"""
        self.validate_correct_type_of_configuration_attribute("environment", list)
        for envdict in self.environment:
            name = envdict.get("name")
            if name is None:
                raise ConfigurationError("environment: 'name' is missing")
            required = envdict.get("required", False)
            if type(required) != bool:
                raise ConfigurationError("environment: 'required' must be a bool")
            path = envdict.get("path")
            if path is None:
                raise ConfigurationError("environment: 'path' is missing")

            value = os.environ.get(name)
            if value is None:
                if required:
                    raise ConfigurationError(f"required environment variable '{name}' not set")
            else:
                value = convert_string_to_value(value)
                self.app_config.update_single_config_from_path_and_value(path, value)

    def handle_aws_secrets_manager(self, context):
        """For each aws secret defined, get the key/values, and set the specified config parameter"""
        self.validate_correct_type_of_configuration_attribute("aws_secrets_manager__region", (type(None), str))
        self.validate_correct_type_of_configuration_attribute("aws_secrets_manager__secrets", list)

        if not self.aws_secrets_manager__secrets:
            return

        self.validate_correct_type_of_configuration_attribute("aws_secrets_manager__region", str)

        for secret in self.aws_secrets_manager__secrets:
            secret_name = secret.get("name")
            if secret_name is None:
                raise ConfigurationError("aws_secrets_manager: 'name' is missing")
            if not isinstance(secret_name, str):
                raise ConfigurationError("aws_secrets_manager: 'name' must be a string")

            try:
                secret_dict = get_secret_key(self.aws_secrets_manager__region, secret_name)
            except SecretKeyRetrievalError as e:
                raise ConfigurationError(f"Unable to retrieve secret {secret_name}: {str(e)}")

            values = secret.get("values")
            if values is None:
                raise ConfigurationError("aws_secrets_manager: 'values' is missing")
            if not isinstance(values, list):
                raise ConfigurationError("aws_secrets_manager: 'values' must be a list")

            for value in values:
                key = value.get("key")
                if key is None:
                    raise ConfigurationError(f"missing 'key' in secret values: {secret_name}")
                path = value.get("path")
                if path is None:
                    raise ConfigurationError(f"missing 'path' in secret values: {secret_name}")
                required = value.get("required", False)
                if type(required) != bool:
                    raise ConfigurationError(f"wrong type for 'required' in secret values: {secret_name}")

                secret_value = secret_dict.get(key)
                if secret_value is None:
                    if required:
                        raise ConfigurationError(f"required secret '{secret_name}:{key}' not set")
                else:
                    secret_value = convert_string_to_value(secret_value)
                    self.app_config.update_single_config_from_path_and_value(path, secret_value)
