# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
# Copyright (c) 2014 TrilioData, Inc
# Copyright (c) 2015 EMC Corporation
# 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 backups api."""
from http import HTTPStatus

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

from cinder.api import api_utils
from cinder.api import common
from cinder.api import extensions
from cinder.api import microversions as mv
from cinder.api.openstack import wsgi
from cinder.api.schemas import backups as schema
from cinder.api import validation
from cinder.api.views import backups as backup_views
from cinder import backup as backupAPI
from cinder import exception
from cinder import utils
from cinder import volume as volumeAPI

LOG = logging.getLogger(__name__)


class BackupsController(wsgi.Controller):
    """The Backups API controller for the OpenStack API."""

    _view_builder_class = backup_views.ViewBuilder

    def __init__(self):
        self.backup_api = backupAPI.API()
        self.volume_api = volumeAPI.API()
        super(BackupsController, self).__init__()

    def show(self, req, id):
        """Return data about the given backup."""
        LOG.debug('Show backup with id: %s.', id)
        context = req.environ['cinder.context']

        # Not found exception will be handled at the wsgi level
        backup = self.backup_api.get(context, backup_id=id)
        req.cache_db_backup(backup)

        return self._view_builder.detail(req, backup)

    @wsgi.response(HTTPStatus.ACCEPTED)
    def delete(self, req, id):
        """Delete a backup."""
        context = req.environ['cinder.context']

        LOG.info('Delete backup with id: %s.', id)

        try:
            backup = self.backup_api.get(context, id)
            self.backup_api.delete(context, backup)
        # Not found exception will be handled at the wsgi level
        except exception.InvalidBackup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)

    def index(self, req):
        """Returns a summary list of backups."""
        return self._get_backups(req, is_detail=False)

    def detail(self, req):
        """Returns a detailed list of backups."""
        return self._get_backups(req, is_detail=True)

    @staticmethod
    def _get_backup_filter_options():
        """Return volume search options allowed by non-admin."""
        return ('name', 'status', 'volume_id')

    @common.process_general_filtering('backup')
    def _process_backup_filtering(self, context=None, filters=None,
                                  req_version=None):
        api_utils.remove_invalid_filter_options(
            context,
            filters,
            self._get_backup_filter_options())

    def _convert_sort_name(self, req_version, sort_keys):
        """Convert sort key "name" to "display_name". """
        pass

    def _get_backups(self, req, is_detail):
        """Returns a list of backups, transformed through view builder."""
        context = req.environ['cinder.context']
        filters = req.params.copy()
        req_version = req.api_version_request
        marker, limit, offset = common.get_pagination_params(filters)
        sort_keys, sort_dirs = common.get_sort_params(filters)

        show_count = False
        if req_version.matches(
                mv.SUPPORT_COUNT_INFO) and 'with_count' in filters:
            show_count = utils.get_bool_param('with_count', filters)
            filters.pop('with_count')
        self._convert_sort_name(req_version, sort_keys)
        self._process_backup_filtering(context=context, filters=filters,
                                       req_version=req_version)

        if 'name' in filters:
            filters['display_name'] = filters.pop('name')

        backups = self.backup_api.get_all(context, search_opts=filters.copy(),
                                          marker=marker,
                                          limit=limit,
                                          offset=offset,
                                          sort_keys=sort_keys,
                                          sort_dirs=sort_dirs,
                                          )

        total_count = None
        if show_count:
            total_count = self.volume_api.calculate_resource_count(
                context, 'backup', filters)
        req.cache_db_backups(backups.objects)

        if is_detail:
            backups = self._view_builder.detail_list(req, backups.objects,
                                                     total_count)
        else:
            backups = self._view_builder.summary_list(req, backups.objects,
                                                      total_count)
        return backups

    # TODO(frankm): Add some checks here including
    # - whether requested volume_id exists so we can return some errors
    #   immediately
    # - maybe also do validation of swift container name
    @wsgi.response(HTTPStatus.ACCEPTED)
    @validation.schema(schema.create, mv.BASE_VERSION,
                       mv.get_prior_version(mv.BACKUP_METADATA))
    @validation.schema(schema.create_backup_v343, mv.BACKUP_METADATA,
                       mv.get_prior_version(mv.BACKUP_AZ))
    @validation.schema(schema.create_backup_v351, mv.BACKUP_AZ)
    def create(self, req, body):
        """Create a new backup."""
        LOG.debug('Creating new backup %s', body)

        context = req.environ['cinder.context']
        req_version = req.api_version_request

        backup = body['backup']
        container = backup.get('container', None)
        volume_id = backup['volume_id']

        self.clean_name_and_description(backup)
        name = backup.get('name', None)
        description = backup.get('description', None)
        incremental = strutils.bool_from_string(backup.get(
            'incremental', False), strict=True)
        force = strutils.bool_from_string(backup.get(
            'force', False), strict=True)
        snapshot_id = backup.get('snapshot_id', None)
        metadata = backup.get('metadata', None) if req_version.matches(
            mv.BACKUP_METADATA) else None

        if req_version.matches(mv.BACKUP_AZ):
            availability_zone = backup.get('availability_zone', None)
        else:
            availability_zone = None
        az_text = ' in az %s' % availability_zone if availability_zone else ''

        LOG.info("Creating backup of volume %(volume_id)s in container"
                 " %(container)s%(az)s",
                 {'volume_id': volume_id, 'container': container,
                  'az': az_text},
                 context=context)

        try:
            new_backup = self.backup_api.create(context, name, description,
                                                volume_id, container,
                                                incremental, availability_zone,
                                                force, snapshot_id, metadata)
        except (exception.InvalidVolume,
                exception.InvalidSnapshot,
                exception.InvalidVolumeMetadata,
                exception.InvalidVolumeMetadataSize) as error:
            raise exc.HTTPBadRequest(explanation=error.msg)
        # Other not found exceptions will be handled at the wsgi level
        except exception.ServiceNotFound as error:
            raise exc.HTTPServiceUnavailable(explanation=error.msg)

        retval = self._view_builder.summary(req, dict(new_backup))
        return retval

    @wsgi.response(HTTPStatus.ACCEPTED)
    @validation.schema(schema.restore)
    def restore(self, req, id, body):
        """Restore an existing backup to a volume."""
        LOG.debug('Restoring backup %(backup_id)s (%(body)s)',
                  {'backup_id': id, 'body': body})

        context = req.environ['cinder.context']
        restore = body['restore']
        volume_id = restore.get('volume_id', None)
        name = restore.get('name', None)

        LOG.info("Restoring backup %(backup_id)s to volume %(volume_id)s.",
                 {'backup_id': id, 'volume_id': volume_id},
                 context=context)

        try:
            new_restore = self.backup_api.restore(context,
                                                  backup_id=id,
                                                  volume_id=volume_id,
                                                  name=name)
        # Not found exception will be handled at the wsgi level
        except (exception.InvalidInput,
                exception.InvalidVolume,
                exception.InvalidBackup) as error:
            raise exc.HTTPBadRequest(explanation=error.msg)
        except (exception.VolumeSizeExceedsAvailableQuota,
                exception.VolumeLimitExceeded) as error:
            raise exc.HTTPRequestEntityTooLarge(
                explanation=error.msg, headers={'Retry-After': '0'})

        retval = self._view_builder.restore_summary(
            req, dict(new_restore))
        return retval

    def export_record(self, req, id):
        """Export a backup."""
        LOG.debug('Export record for backup %s.', id)
        context = req.environ['cinder.context']

        try:
            backup_info = self.backup_api.export_record(context, id)
        # Not found exception will be handled at the wsgi level
        except exception.InvalidBackup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)

        retval = self._view_builder.export_summary(
            req, dict(backup_info))
        LOG.debug('Exported record output: %s.', retval)
        return retval

    @wsgi.response(HTTPStatus.CREATED)
    @validation.schema(schema.import_record)
    def import_record(self, req, body):
        """Import a backup."""
        LOG.debug('Importing record from %s.', body)
        context = req.environ['cinder.context']
        import_data = body['backup-record']
        backup_service = import_data['backup_service']
        backup_url = import_data['backup_url']

        LOG.debug('Importing backup using %(service)s and url %(url)s.',
                  {'service': backup_service, 'url': backup_url})

        try:
            new_backup = self.backup_api.import_record(context,
                                                       backup_service,
                                                       backup_url)
        except exception.InvalidBackup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)
        # Other Not found exceptions will be handled at the wsgi level
        except exception.ServiceNotFound as error:
            raise exc.HTTPServiceUnavailable(explanation=error.msg)

        retval = self._view_builder.summary(req, dict(new_backup))
        LOG.debug('Import record output: %s.', retval)
        return retval


class Backups(extensions.ExtensionDescriptor):
    """Backups support."""

    name = 'Backups'
    alias = 'backups'
    updated = '2012-12-12T00:00:00+00:00'

    def get_resources(self):
        resources = []
        res = extensions.ResourceExtension(
            Backups.alias, BackupsController(),
            collection_actions={'detail': 'GET', 'import_record': 'POST'},
            member_actions={'restore': 'POST', 'export_record': 'GET',
                            'action': 'POST'})
        resources.append(res)
        return resources
