# Copyright 2013 OpenStack Foundation.
# 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. 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
import itertools
import os

from neutron_lib.db import constants as db_const
from neutron_lib.db import utils as db_utils
from neutron_lib import exceptions as n_exc
from oslo_config import cfg
from oslo_log import log as logging
from oslo_log import versionutils
import stevedore

from neutron._i18n import _
from neutron.conf.services import provider_configuration as prov_config

LOG = logging.getLogger(__name__)

SERVICE_PROVIDERS = 'neutron.service_providers'

# TODO(HenryG): use MovedGlobals to deprecate this.
serviceprovider_opts = prov_config.serviceprovider_opts

prov_config.register_service_provider_opts()


class NeutronModule(object):
    """A Neutron extension module."""

    def __init__(self, service_module):
        self.module_name = service_module
        self.repo = {
            'mod': self._import_or_none(),
            'ini': None
        }

    def _import_or_none(self):
        try:
            return importlib.import_module(self.module_name)
        except ImportError:
            return None

    def installed(self):
        LOG.debug("NeutronModule installed = %s", self.module_name)
        return self.module_name

    def module(self):
        return self.repo['mod']

    # Return an INI parser for the child module
    def ini(self, neutron_dir=None):
        if self.repo['ini'] is None:
            ini_file = cfg.ConfigOpts()
            prov_config.register_service_provider_opts(ini_file)

            if neutron_dir is not None:
                neutron_dirs = [neutron_dir]
            else:
                try:
                    neutron_dirs = cfg.CONF.config_dirs
                except cfg.NoSuchOptError:
                    neutron_dirs = None
                if not neutron_dirs:
                    neutron_dirs = ['/etc/neutron']

            # load configuration from all matching files to reflect oslo.config
            # behaviour
            config_files = []
            for neutron_dir in neutron_dirs:
                ini_path = os.path.join(neutron_dir,
                                        '%s.conf' % self.module_name)
                if os.path.exists(ini_path):
                    config_files.append(ini_path)

            # NOTE(ihrachys): we could pass project=self.module_name instead to
            # rely on oslo.config to find configuration files for us, but:
            # 1. that would render neutron_dir argument ineffective;
            # 2. that would break loading configuration file from under
            # /etc/neutron in case no --config-dir is passed.
            # That's why we need to explicitly construct CLI here.
            ini_file(args=list(itertools.chain.from_iterable(
                ['--config-file', file_] for file_ in config_files
            )))
            self.repo['ini'] = ini_file

        return self.repo['ini']

    def service_providers(self):
        """Return the service providers for the extension module."""
        providers = []
        # Attempt to read the config from cfg.CONF first; when passing
        # --config-dir, the option is merged from all the definitions
        # made across all the imported config files
        try:
            providers = cfg.CONF.service_providers.service_provider
        except cfg.NoSuchOptError:
            pass

        # Alternatively, if the option is not available, try to load
        # it from the provider module's config file; this may be
        # necessary, if modules are loaded on the fly (DevStack may
        # be an example)
        if not providers:
            providers = self.ini().service_providers.service_provider

            if providers:
                versionutils.report_deprecated_feature(
                    LOG,
                    'Implicit loading of service providers from '
                    'neutron_*.conf files is deprecated and will be '
                    'removed in Ocata release.')

        return providers


# global scope function that should be used in service APIs
def normalize_provider_name(name):
    return name.lower()


def get_provider_driver_class(driver, namespace=SERVICE_PROVIDERS):
    """Return path to provider driver class

    In order to keep backward compatibility with configs < Kilo, we need to
    translate driver class paths after advanced services split. This is done by
    defining old class path as entry point in neutron package.
    """
    try:
        driver_manager = stevedore.driver.DriverManager(
            namespace, driver).driver
    except ImportError:
        return driver
    except RuntimeError:
        return driver
    new_driver = "%s.%s" % (driver_manager.__module__,
                            driver_manager.__name__)
    LOG.warning(
        "The configured driver %(driver)s has been moved, automatically "
        "using %(new_driver)s instead. Please update your config files, "
        "as this automatic fixup will be removed in a future release.",
        {'driver': driver, 'new_driver': new_driver})
    return new_driver


def parse_service_provider_opt(service_module='neutron', service_type=None):

    """Parse service definition opts and returns result."""
    def validate_name(name):
        if len(name) > db_const.NAME_FIELD_SIZE:
            raise n_exc.Invalid(
                _("Provider name %(name)s is limited by %(len)s characters")
                % {'name': name, 'len': db_const.NAME_FIELD_SIZE})

    neutron_mod = NeutronModule(service_module)
    svc_providers_opt = neutron_mod.service_providers()

    LOG.debug("Service providers = %s", svc_providers_opt)

    res = []
    for prov_def in svc_providers_opt:
        split = prov_def.split(':')
        try:
            svc_type, name, driver = split[:3]
            if service_type and service_type != svc_type:
                continue
        except ValueError:
            raise n_exc.Invalid(_("Invalid service provider format"))
        validate_name(name)