# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.

import importlib.metadata

from oslo_config import cfg
from oslo_log import log as logging
import pbr

from keystonemiddleware import exceptions
from keystonemiddleware.i18n import _

CONF = cfg.CONF
_NOT_SET = object()
_LOG = logging.getLogger(__name__)


def _conf_values_type_convert(group_name, all_options, conf):
    """Convert conf values into correct type."""
    if not conf:
        return {}

    opts = {}
    opt_types = {}

    for group, options in all_options:
        # only accept paste overrides for the primary group
        if group != group_name:
            continue

        for o in options:
            type_dest = (getattr(o, 'type', str), o.dest)
            opt_types[o.dest] = type_dest
            # Also add the deprecated name with the same type and dest.
            for d_o in o.deprecated_opts:
                opt_types[d_o.name] = type_dest

        break

    for k, v in conf.items():
        dest = k
        try:
            # 'here' and '__file__' come from paste.deploy
            # 'configkey' is added by panko and gnocchi
            if v is not None and k not in ['here', '__file__', 'configkey']:
                type_, dest = opt_types[k]
                v = type_(v)
        except KeyError:  # nosec
            _LOG.warning(
                'The option "%s" is not known to keystonemiddleware', k)
        except ValueError as e:
            raise exceptions.ConfigurationError(
                _('Unable to convert the value of option "%(key)s" into '
                  'correct type: %(ex)s') % {'key': k, 'ex': e})
        opts[dest] = v

    return opts


class Config(object):

    def __init__(self, name, group_name, all_options, conf):
        local_oslo_config = conf.pop('oslo_config_config', None)
        local_config_project = conf.pop('oslo_config_project', None)
        local_config_file = conf.pop('oslo_config_file', None)

        # NOTE(wanghong): If options are set in paste file, all the option
        # values passed into conf are string type. So, we should convert the
        # conf value into correct type.
        self.paste_overrides = _conf_values_type_convert(group_name,
                                                         all_options,
                                                         conf)

        # NOTE(sileht, cdent): If we don't want to use oslo.config global
        # object there are two options: set "oslo_config_project" in
        # paste.ini and the middleware will load the configuration with a
        # local oslo.config object or the caller which instantiates
        # AuthProtocol can pass in an existing oslo.config as the
        # value of the "oslo_config_config" key in conf. If both are
        # set "oslo_config_config" is used.
        if local_config_project and not local_oslo_config:
            config_files = [local_config_file] if local_config_file else None

            local_oslo_config = cfg.ConfigOpts()
            local_oslo_config([],
                              project=local_config_project,
                              default_config_files=config_files,
                              validate_default_values=True)

        if local_oslo_config:
            for group, opts in all_options:
                local_oslo_config.register_opts(opts, group=group)

        self.name = name
        self.oslo_conf_obj = local_oslo_config or cfg.CONF
        self.group_name = group_name
        self._user_agent = None

    def get(self, name, group=_NOT_SET):
        # try config from paste-deploy first
        try:
            return self.paste_overrides[name]
        except KeyError:
            if group is _NOT_SET:
                group = self.group_name

            return self.oslo_conf_obj[group][name]

    @property
    def project(self):
        """Determine a project name from all available config sources.

        The sources are checked in the following order:

          1. The paste-deploy config for auth_token middleware
          2. The keystone_authtoken or base group in the project's config
          3. The oslo.config CONF.project property

        """
        try:
            return self.get('project', group=self.group_name)
        except cfg.NoSuchOptError:
            try:
                # CONF.project will exist only if the service uses
                # oslo.config. It will only be set when the project
                # calls CONF(...) and when not set oslo.config oddly
                # raises a NoSuchOptError exception.
                return self.oslo_conf_obj.project
            except cfg.NoSuchOptError:
                return None

    @property
    def user_agent(self):
        if not self._user_agent:
            project = self.project or ''

            if project:
                try:
                    version = importlib.metadata.version(project)
                except importlib.metadata.PackageNotFoundError:
                    version = "unknown"

                project = "%s/%s " % (project, version)

            self._user_agent = "%skeystonemiddleware.%s/%s" % (
                project,
                self.name,
                pbr.version.VersionInfo('keystonemiddleware').version_string())

        return self._user_agent
