# Copyright 2011 Justin Santa Barbara
# 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.

"""The volumes api."""

import ast

from oslo_log import log as logging
from oslo_utils import uuidutils
import webob
from webob import exc

from cinder.api import common
from cinder.api.openstack import wsgi
from cinder.i18n import _, _LI
from cinder import utils
from cinder import volume as cinder_volume
from cinder.volume import utils as volume_utils
from cinder.volume import volume_types


LOG = logging.getLogger(__name__)


def _translate_attachment_detail_view(_context, vol):
    """Maps keys for attachment details view."""

    d = _translate_attachment_summary_view(_context, vol)

    # No additional data / lookups at the moment

    return d


def _translate_attachment_summary_view(_context, vol):
    """Maps keys for attachment summary view."""
    d = []
    attachments = vol.volume_attachment
    for attachment in attachments:
        if attachment.get('attach_status') == 'attached':
            a = {'id': attachment.get('volume_id'),
                 'attachment_id': attachment.get('id'),
                 'volume_id': attachment.get('volume_id'),
                 'server_id': attachment.get('instance_uuid'),
                 'host_name': attachment.get('attached_host'),
                 'device': attachment.get('mountpoint'),
                 }
            d.append(a)

    return d


def _translate_volume_detail_view(context, vol, image_id=None):
    """Maps keys for volumes details view."""

    d = _translate_volume_summary_view(context, vol, image_id)

    # No additional data / lookups at the moment

    return d


def _translate_volume_summary_view(context, vol, image_id=None):
    """Maps keys for volumes summary view."""
    d = {}

    d['id'] = vol['id']
    d['status'] = vol['status']
    d['size'] = vol['size']
    d['availability_zone'] = vol['availability_zone']
    d['created_at'] = vol['created_at']

    # Need to form the string true/false explicitly here to
    # maintain our API contract
    if vol['bootable']:
        d['bootable'] = 'true'
    else:
        d['bootable'] = 'false'

    if vol['multiattach']:
        d['multiattach'] = 'true'
    else:
        d['multiattach'] = 'false'

    d['attachments'] = []
    if vol['attach_status'] == 'attached':
        d['attachments'] = _translate_attachment_detail_view(context, vol)

    d['display_name'] = vol['display_name']
    d['display_description'] = vol['display_description']

    if vol['volume_type_id'] and vol.get('volume_type'):
        d['volume_type'] = vol['volume_type']['name']
    else:
        d['volume_type'] = vol['volume_type_id']

    d['snapshot_id'] = vol['snapshot_id']
    d['source_volid'] = vol['source_volid']

    d['encrypted'] = vol['encryption_key_id'] is not None

    if image_id:
        d['image_id'] = image_id

    LOG.info(_LI("vol=%s"), vol)

    if vol.metadata:
        d['metadata'] = vol.metadata
    else:
        d['metadata'] = {}

    return d


class VolumeController(wsgi.Controller):
    """The Volumes API controller for the OpenStack API."""

    def __init__(self, ext_mgr):
        self.volume_api = cinder_volume.API()
        self.ext_mgr = ext_mgr
        super(VolumeController, self).__init__()

    def show(self, req, id):
        """Return data about the given volume."""
        context = req.environ['cinder.context']

        # Not found exception will be handled at the wsgi level
        vol = self.volume_api.get(context, id, viewable_admin_meta=True)
        req.cache_db_volume(vol)

        utils.add_visible_admin_metadata(vol)

        return {'volume': _translate_volume_detail_view(context, vol)}

    def delete(self, req, id):
        """Delete a volume."""
        context = req.environ['cinder.context']

        LOG.info(_LI("Delete volume with id: %s"), id)

        # Not found exception will be handled at the wsgi level
        volume = self.volume_api.get(context, id)
        self.volume_api.delete(context, volume)
        return webob.Response(status_int=202)

    def index(self, req):
        """Returns a summary list of volumes."""
        return self._items(req, entity_maker=_translate_volume_summary_view)

    def detail(self, req):
        """Returns a detailed list of volumes."""
        return self._items(req, entity_maker=_translate_volume_detail_view)

    def _items(self, req, entity_maker):
        """Returns a list of volumes, transformed through entity_maker."""

        # pop out limit and offset , they are not search_opts
        search_opts = req.GET.copy()
        search_opts.pop('limit', None)
        search_opts.pop('offset', None)

        for k, v in search_opts.items():
            try:
                search_opts[k] = ast.literal_eval(v)
            except (ValueError, SyntaxError):
                LOG.debug('Could not evaluate value %s, assuming string', v)

        context = req.environ['cinder.context']
        utils.remove_invalid_filter_options(context,
                                            search_opts,
                                            self._get_volume_search_options())

        volumes = self.volume_api.get_all(context, marker=None, limit=None,
                                          sort_keys=['created_at'],
                                          sort_dirs=['desc'],
                                          filters=search_opts,
                                          viewable_admin_meta=True)

        for volume in volumes:
            utils.add_visible_admin_metadata(volume)

        limited_list = common.limited(volumes.objects, req)
        req.cache_db_volumes(limited_list)

        res = [entity_maker(context, vol) for vol in limited_list]
        return {'volumes': res}

    def _image_uuid_from_href(self, image_href):
        # If the image href was generated by nova api, strip image_href
        # down to an id.
        try:
            image_uuid = image_href.split('/').pop()
        except (TypeError, AttributeError):
            msg = _("Invalid imageRef provided.")
            raise exc.HTTPBadRequest(explanation=msg)

        if not uuidutils.is_uuid_like(image_uuid):
            msg = _("Invalid imageRef provided.")
            raise exc.HTTPBadRequest(explanation=msg)

        return image_uuid

    def create(self, req, body):
        """Creates a new volume."""
        if not self.is_valid_body(body, 'volume'):
            raise exc.HTTPUnprocessableEntity()

        LOG.debug('Create volume request body: %s', body)
        context = req.environ['cinder.context']
        volume = body['volume']

        kwargs = {}

        req_volume_type = volume.get('volume_type', None)
        if req_volume_type:
            # Not found exception will be handled at the wsgi level
            if not uuidutils.is_uuid_like(req_volume_type):
                kwargs['volume_type'] = \
                    volume_types.get_volume_type_by_name(
                        context, req_volume_type)
            else:
                kwargs['volume_type'] = volume_types.get_volume_type(
                    context, req_volume_type)

        kwargs['metadata'] = volume.get('metadata', None)

        snapshot_id = volume.get('snapshot_id')
        if snapshot_id is not None:
            # Not found exception will be handled at the wsgi level
            kwargs['snapshot'] = self.volume_api.get_snapshot(context,
                                                              snapshot_id)
        else:
            kwargs['snapshot'] = None

        source_volid = volume.get('source_volid')
        if source_volid is not None:
            # Not found exception will be handled at the wsgi level
            kwargs['source_volume'] = self.volume_api.get_volume(context,
                                                                 source_volid)
        else:
            kwargs['source_volume'] = None

        size = volume.get('size', None)
        if size is None and kwargs['snapshot'] is not None:
            size = kwargs['snapshot']['volume_size']
        elif size is None and kwargs['source_volume'] is not None:
            size = kwargs['source_volume']['size']

        LOG.info(_LI("Create volume of %s GB"), size)
        multiattach = volume.get('multiattach', False)
        kwargs['multiattach'] = multiattach

        image_href = None
        image_uuid = None
        if self.ext_mgr.is_loaded('os-image-create'):
            # NOTE(jdg): misleading name "imageRef" as it's an image-id
            image_href = volume.get('imageRef')
            if image_href is not None:
                image_uuid = self._image_uuid_from_href(image_href)
                kwargs['image_id'] = image_uuid

        kwargs['availability_zone'] = volume.get('availability_zone', None)

        new_volume = self.volume_api.create(context,
                                            size,
                                            volume.get('display_name'),
                                            volume.get('display_description'),
                                            **kwargs)

        retval = _translate_volume_detail_view(context, new_volume, image_uuid)

        return {'volume': retval}

    def _get_volume_search_options(self):
        """Return volume search options allowed by non-admin."""
        return ('display_name', 'status', 'metadata')

    def update(self, req, id, body):
        """Update a volume."""
        context = req.environ['cinder.context']

        if not body:
            raise exc.HTTPUnprocessableEntity()

        if 'volume' not in body:
            raise exc.HTTPUnprocessableEntity()

        volume = body['volume']
        update_dict = {}

        valid_update_keys = (
            'display_name',
            'display_description',
            'metadata',
        )

        for key in valid_update_keys:
            if key in volume:
                update_dict[key] = volume[key]

        # Not found exception will be handled at the wsgi level
        volume = self.volume_api.get(context, id, viewable_admin_meta=True)
        volume_utils.notify_about_volume_usage(context, volume,
                                               'update.start')
        self.volume_api.update(context, volume, update_dict)

        volume.update(update_dict)

        utils.add_visible_admin_metadata(volume)

        volume_utils.notify_about_volume_usage(context, volume,
                                               'update.end')

        return {'volume': _translate_volume_detail_view(context, volume)}


def create_resource(ext_mgr):
    return wsgi.Resource(VolumeController(ext_mgr))
