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

"""
Node endpoint for Senlin v1 REST API.
"""
from webob import exc

from senlin.api.common import util
from senlin.api.common import version_request as vr
from senlin.api.common import wsgi
from senlin.common import consts
from senlin.common.i18n import _


class NodeController(wsgi.Controller):
    """WSGI controller for nodes resource in Senlin v1 API."""

    REQUEST_SCOPE = 'nodes'

    SUPPORTED_ACTIONS = (
        NODE_CHECK, NODE_RECOVER
    ) = (
        'check', 'recover'
    )

    def _remove_tainted(self, req, obj):
        if req.version_request > vr.APIVersionRequest("1.12"):
            return obj

        if 'tainted' in obj:
            obj.pop('tainted')

        return obj

    @util.policy_enforce
    def index(self, req):
        whitelist = {
            consts.NODE_CLUSTER_ID: 'single',
            consts.NODE_NAME: 'mixed',
            consts.NODE_STATUS: 'mixed',
            consts.PARAM_LIMIT: 'single',
            consts.PARAM_MARKER: 'single',
            consts.PARAM_SORT: 'single',
            consts.PARAM_GLOBAL_PROJECT: 'single'
        }
        for key in req.params.keys():
            if key not in whitelist.keys():
                raise exc.HTTPBadRequest(_('Invalid parameter %s') % key)
        params = util.get_allowed_params(req.params, whitelist)

        project_safe = not util.parse_bool_param(
            consts.PARAM_GLOBAL_PROJECT,
            params.pop(consts.PARAM_GLOBAL_PROJECT, False))
        params['project_safe'] = project_safe

        obj = util.parse_request('NodeListRequest', req, params)
        nodes = self.rpc_client.call(req.context, 'node_list', obj)

        nodes = [self._remove_tainted(req, n) for n in nodes]
        return {'nodes': nodes}

    @util.policy_enforce
    def create(self, req, body):
        """Create a new node."""
        obj = util.parse_request('NodeCreateRequest', req, body, 'node')
        node = self.rpc_client.call(req.context, 'node_create',
                                    obj.node)

        node = self._remove_tainted(req, node)

        action_id = node.pop('action')
        result = {
            'node': node,
            'location': '/actions/%s' % action_id,
        }
        return result

    @wsgi.Controller.api_version('1.7')
    @util.policy_enforce
    def adopt(self, req, body):
        """Adopt a node for management."""
        obj = util.parse_request('NodeAdoptRequest', req, body)
        node = self.rpc_client.call(req.context, 'node_adopt', obj)

        node = self._remove_tainted(req, node)
        return {'node': node}

    @wsgi.Controller.api_version('1.7')
    @util.policy_enforce
    def adopt_preview(self, req, body):
        """Preview a node adoption."""
        # make sure we will fall into the preview path
        obj = util.parse_request('NodeAdoptPreviewRequest', req, body)
        node = self.rpc_client.call(req.context, 'node_adopt_preview', obj)
        return {'node_profile': node}

    @util.policy_enforce
    def get(self, req, node_id):
        params = {'identity': node_id}
        key = consts.PARAM_SHOW_DETAILS
        if key in req.params:
            params['show_details'] = util.parse_bool_param(
                key, req.params[key])

        obj = util.parse_request('NodeGetRequest', req, params)
        node = self.rpc_client.call(req.context, 'node_get', obj)

        node = self._remove_tainted(req, node)
        return {'node': node}

    @util.policy_enforce
    def update(self, req, node_id, body):
        data = body.get('node')
        if data is None:
            raise exc.HTTPBadRequest(_("Malformed request data, missing "
                                       "'node' key in request body."))
        params = data
        params['identity'] = node_id

        obj = util.parse_request('NodeUpdateRequest', req, params)
        node = self.rpc_client.call(req.context, 'node_update', obj)

        node = self._remove_tainted(req, node)

        action_id = node.pop('action')
        result = {
            'node': node,
            'location': '/actions/%s' % action_id,
        }
        return result

    @util.policy_enforce
    def delete(self, req, node_id, body=None):
        if body:
            force = body.get('force')
        else:
            force = False

        if force is not None:
            force = util.parse_bool_param(consts.NODE_DELETE_FORCE, force)

        params = {'identity': node_id, 'force': force}

        obj = util.parse_request('NodeDeleteRequest', req, params)
        res = self.rpc_client.call(req.context, 'node_delete', obj)
        action_id = res.pop('action')
        result = {'location': '/actions/%s' % action_id}
        return result

    @util.policy_enforce
    def action(self, req, node_id, body=None):
        """Perform specified action on a node."""

        body = body or {}
        if len(body) == 0:
            raise exc.HTTPBadRequest(_('No action specified.'))

        if len(body) > 1:
            raise exc.HTTPBadRequest(_('Multiple actions specified.'))

        this_action = list(body.keys())[0]
        if this_action not in self.SUPPORTED_ACTIONS:
            msg = _("Unrecognized action '%s' specified") % this_action
            raise exc.HTTPBadRequest(msg)

        params = body.get(this_action)
        if this_action == self.NODE_CHECK:
            res = self._do_check(req, node_id, params)
        else:    # self.NODE_RECOVER
            res = self._do_recover(req, node_id, params)

        location = {'location': '/actions/%s' % res['action']}
        res.update(location)
        return res

    def _do_check(self, req, node_id, params):
        if not isinstance(params, dict):
            msg = _("The params provided is not a map.")
            raise exc.HTTPBadRequest(msg)

        kwargs = {
            'identity': node_id,
            'params': params
        }

        obj = util.parse_request('NodeCheckRequest', req, kwargs)
        res = self.rpc_client.call(req.context, 'node_check', obj)

        return res

    def _do_recover(self, req, node_id, params):
        if not isinstance(params, dict):
            msg = _("The params provided is not a map.")
            raise exc.HTTPBadRequest(msg)

        kwargs = {
            'identity': node_id,
            'params': params
        }

        obj = util.parse_request('NodeRecoverRequest', req, kwargs)
        res = self.rpc_client.call(req.context, 'node_recover', obj)

        return res

    @wsgi.Controller.api_version('1.4')
    @util.policy_enforce
    def operation(self, req, node_id, body=None):
        """Perform the specified operation on the specified node."""

        body = body or {}
        if len(body) == 0:
            raise exc.HTTPBadRequest(_('No operation specified.'))

        if len(body) > 1:
            raise exc.HTTPBadRequest(_('Multiple operations specified.'))

        operation = list(body.keys())[0]
        params = {
            'identity': node_id,
            'operation': operation,
            'params': body.get(operation),
        }

        obj = util.parse_request('NodeOperationRequest', req, params)
        node = self.rpc_client.call(req.context, 'node_op', obj)

        action_id = node.pop('action')
        result = {
            'location': '/actions/%s' % action_id,
            'action': action_id
        }
        return result
