#    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.

from pecan import hooks

from neutron.api import api_common
from neutron import manager
from neutron.pecan_wsgi.hooks import policy_enforcement
from neutron.pecan_wsgi.hooks import utils


# TODO(blogan): ideally it'd be nice to get the pagination and sorting
# helpers from the controller but since the controllers are
# instantiated at startup and not on request, it would cause race
# conditions because we need a new instantiation of a pagination
# and sorting helper per request/response flow.  As a result, we're forced to
# pass them through the request context.

def _get_pagination_helper(request, controller):
    if 'pagination_helper' in request.context:
        return request.context['pagination_helper']
    if not controller.allow_pagination:
        helper = api_common.NoPaginationHelper(request, controller.primary_key)
    elif controller.native_pagination:
        helper = api_common.PaginationNativeHelper(request,
                                                   controller.primary_key)
    else:
        helper = api_common.PaginationEmulatedHelper(request,
                                                     controller.primary_key)
    request.context['pagination_helper'] = helper
    return helper


def _get_sorting_helper(request, controller):
    if 'sorting_helper' in request.context:
        return request.context['sorting_helper']
    if not controller.allow_sorting:
        helper = api_common.NoSortingHelper(request, controller.resource_info)
    elif controller.native_sorting:
        helper = api_common.SortingNativeHelper(request,
                                                controller.resource_info)
    else:
        helper = api_common.SortingEmulatedHelper(request,
                                                  controller.resource_info)
    request.context['sorting_helper'] = helper
    return helper


def _listify(thing):
    return thing if isinstance(thing, list) else [thing]


def _set_fields(state, controller):
    params = state.request.params.mixed()
    fields = params.get('fields', [])
    # if only one fields query parameter is passed, pecan will not put
    # that parameter in a list, so we need to convert it into a list
    fields = _listify(fields)
    combined_fields, added_fields = controller.build_field_list(fields)
    state.request.context['query_params']['fields'] = combined_fields
    state.request.context['added_fields'] = added_fields
    return combined_fields, added_fields


def _set_filters(state, controller):
    params = state.request.params.mixed()
    filters = api_common.get_filters_from_dict(
        {k: _listify(v) for k, v in params.items()},
        controller.resource_info,
        skips=['fields', 'sort_key', 'sort_dir',
               'limit', 'marker', 'page_reverse'],
        is_filter_validation_supported=controller.filter_validation)
    return filters


class QueryParametersHook(hooks.PecanHook):

    # NOTE(blogan): needs to be run after the priority hook.  after methods
    # are run in reverse priority order.
    priority = policy_enforcement.PolicyHook.priority - 1

    def before(self, state):
        self._process_if_match_headers(state)
        state.request.context['query_params'] = {}
        if state.request.method != 'GET':
            return
        collection = state.request.context.get('collection')
        if not collection:
            return
        controller = utils.get_controller(state)
        combined_fields, added_fields = _set_fields(state, controller)
        filters = _set_filters(state, controller)
        query_params = {'fields': combined_fields, 'filters': filters}
        pagination_helper = _get_pagination_helper(state.request, controller)
        sorting_helper = _get_sorting_helper(state.request, controller)
        sorting_helper.update_args(query_params)
        sorting_helper.update_fields(query_params.get('fields', []),
                                     added_fields)
        pagination_helper.update_args(query_params)
        pagination_helper.update_fields(query_params.get('fields', []),
                                        added_fields)
        state.request.context['query_params'] = query_params

    def _process_if_match_headers(self, state):
        collection = state.request.context.get('collection')
        if not collection:
            return
        # add in if-match criterion to the context if present
        revision_number = api_common.check_request_for_revision_constraint(
            state.request)
        if revision_number is None:
            return
        state.request.context['neutron_context'].set_transaction_constraint(
            collection, state.request.context['resource_id'], revision_number)

    def after(self, state):
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        # NOTE(blogan): don't paginate extension list or non-GET requests
        if (not resource or resource == 'extension' or
                state.request.method != 'GET'):
            return
        try:
            data = state.response.json
        except ValueError:
            return
        # Do not attempt to paginate if the body is not a list of entities
        if not data or resource in data or collection not in data:
            return
        controller = manager.NeutronManager.get_controller_for_resource(
            collection)
        sorting_helper = _get_sorting_helper(state.request, controller)
        pagination_helper = _get_pagination_helper(state.request, controller)
        obj_list = sorting_helper.sort(data[collection])
        obj_list = pagination_helper.paginate(obj_list)
        resp_body = {collection: obj_list}
        pagination_links = pagination_helper.get_links(obj_list)
        if pagination_links:
            resp_body['_'.join([collection, 'links'])] = pagination_links
        state.response.json = resp_body
