#    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.
"""Deployment handling for Placmenent API."""

import os

from microversion_parse import middleware as mp_middleware
import oslo_middleware
from oslo_utils import importutils

from placement import auth
from placement.db.sqlalchemy import migration
from placement import db_api
from placement import fault_wrap
from placement import handler
from placement import microversion
from placement.objects import resource_class
from placement.objects import trait
from placement import policy
from placement import requestlog
from placement import util


os_profiler = importutils.try_import('osprofiler.profiler')
os_profiler_web = importutils.try_import('osprofiler.web')


PROFILER_OUTPUT = os.environ.get('OS_WSGI_PROFILER')
if PROFILER_OUTPUT:
    # If werkzeug is not available this raises ImportError and the
    # process will not continue. This is intentional: we do not want
    # to make a permanent dependency on werkzeug.
    from werkzeug.contrib import profiler


def deploy(conf):
    """Assemble the middleware pipeline leading to the placement app."""
    if conf.api.auth_strategy == 'noauth2':
        auth_middleware = auth.NoAuthMiddleware
    else:
        # Do not use 'oslo_config_project' param here as the conf
        # location may have been overridden earlier in the deployment
        # process with OS_PLACEMENT_CONFIG_DIR in wsgi.py.
        auth_middleware = auth.filter_factory(
            {}, oslo_config_config=conf)

    # Conditionally add CORS middleware based on setting 'allowed_origin'
    # in config.
    if conf.cors.allowed_origin:
        cors_middleware = oslo_middleware.CORS.factory(
            {}, **conf.cors)
    else:
        cors_middleware = None

    context_middleware = auth.PlacementKeystoneContext
    microversion_middleware = mp_middleware.MicroversionMiddleware
    fault_middleware = fault_wrap.FaultWrapper
    request_log = requestlog.RequestLog

    if os_profiler_web and 'profiler' in conf and conf.profiler.enabled:
        osprofiler_middleware = os_profiler_web.WsgiMiddleware.factory(
            {}, **conf.profiler)
    else:
        osprofiler_middleware = None

    application = handler.PlacementHandler(config=conf)

    # If PROFILER_OUTPUT is set, generate per request profile reports
    # to the directory named therein.
    if PROFILER_OUTPUT:
        application = profiler.ProfilerMiddleware(
            application, profile_dir=PROFILER_OUTPUT)

    # configure microversion middleware in the old school way
    application = microversion_middleware(
        application, microversion.SERVICE_TYPE, microversion.VERSIONS,
        json_error_formatter=util.json_error_formatter)

    # NOTE(cdent): The ordering here is important. The list is ordered from the
    # inside out. For a single request, request_log is called first (to extract
    # request context information and log the start of the request). If
    # osprofiler_middleware is present (see above), it is first.
    # fault_middleware is last in the stack described below, to wrap unexpected
    # exceptions in the placement application as valid HTTP 500 responses. Then
    # the request is passed to the microversion middleware (configured above)
    # and then finally to the application (the PlacementHandler, further
    # above). At that point the response ascends the middleware in the reverse
    # of the order the request went in. This order ensures that log messages
    # all see the same contextual information including request id and
    # authentication information. An individual piece of middleware is a
    # wrapper around the next and can do work on the way in, the way out, or
    # both. Which can be determined by looking at the `__call__` method in the
    # middleware. "In" activity is done prior to calling the next layer in the
    # stack (often `self.application`). "Out" activity is after, or in a
    # redefinition of the `start_response` method, commonly called
    # `replacement_start_response`.
    for middleware in (fault_middleware,
                       context_middleware,
                       auth_middleware,
                       cors_middleware,
                       request_log,
                       osprofiler_middleware,
                       ):
        if middleware:
            application = middleware(application)

    # NOTE(mriedem): Ignore scope check UserWarnings from oslo.policy.
    if not conf.oslo_policy.enforce_scope:
        import warnings
        warnings.filterwarnings('ignore',
                                message="Policy .* failed scope check",
                                category=UserWarning)

    return application


def update_database(conf):
    """Do any database updates required at process boot time, such as
    updating the traits table.
    """
    if conf.placement_database.sync_on_startup:
        migration.upgrade('head')
    ctx = db_api.DbContext()
    trait.ensure_sync(ctx)
    resource_class.ensure_sync(ctx)


# NOTE(cdent): Althought project_name is no longer used because of the
# resolution of https://bugs.launchpad.net/nova/+bug/1734491, loadapp()
# is considered a public interface for the creation of a placement
# WSGI app so must maintain its interface. The canonical placement WSGI
# app is created by init_application in wsgi.py, but this is not
# required and in fact can be limiting. loadapp() may be used from
# fixtures or arbitrary WSGI frameworks and loaders.
def loadapp(config, project_name=None):
    """WSGI application creator for placement.

    :param config: An oslo_config.cfg.ConfigOpts containing placement
                   configuration.
    :param project_name: oslo_config project name. Ignored, preserved for
                         backwards compatibility
    """
    application = deploy(config)
    policy.init(config)
    update_database(config)
    return application
