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

import random

from rally.common import utils as rutils
from rally.task import atomic

from rally_openstack.common import service
from rally_openstack.common.services.storage import block
from rally_openstack.common.services.storage import cinder_common


CONF = block.CONF


@service.service("cinder", service_type="block-storage", version="3")
class CinderV3Service(service.Service, cinder_common.CinderMixin):

    @atomic.action_timer("cinder_v3.create_volume")
    def create_volume(self, size, consistencygroup_id=None,
                      snapshot_id=None, source_volid=None, name=None,
                      description=None, volume_type=None,
                      availability_zone=None, metadata=None, imageRef=None,
                      scheduler_hints=None, backup_id=None):
        """Creates a volume.

        :param size: Size of volume in GB
        :param consistencygroup_id: ID of the consistencygroup
        :param snapshot_id: ID of the snapshot
        :param name: Name of the volume
        :param description: Description of the volume
        :param volume_type: Type of volume
        :param availability_zone: Availability Zone to use
        :param metadata: Optional metadata to set on volume creation
        :param imageRef: reference to an image stored in glance
        :param source_volid: ID of source volume to clone from
        :param scheduler_hints: (optional extension) arbitrary key-value pairs
                            specified by the client to help boot an instance
        :param backup_id: ID of the backup

        :returns: Return a new volume.
        """
        kwargs = {"name": name or self.generate_random_name(),
                  "description": description,
                  "consistencygroup_id": consistencygroup_id,
                  "snapshot_id": snapshot_id,
                  "source_volid": source_volid,
                  "volume_type": volume_type,
                  "availability_zone": availability_zone,
                  "metadata": metadata,
                  "imageRef": imageRef,
                  "scheduler_hints": scheduler_hints,
                  "backup_id": backup_id}
        if isinstance(size, dict):
            size = random.randint(size["min"], size["max"])

        volume = (self._get_client()
                  .volumes.create(size, **kwargs))

        # NOTE(msdubov): It is reasonable to wait 5 secs before starting to
        #                check whether the volume is ready => less API calls.
        rutils.interruptable_sleep(
            CONF.openstack.cinder_volume_create_prepoll_delay)

        return self._wait_available_volume(volume)

    @atomic.action_timer("cinder_v3.update_volume")
    def update_volume(self, volume_id, name=None, description=None):
        """Update the name or description for a volume.

        :param volume_id: The updated volume id.
        :param name: The volume name.
        :param description: The volume description.

        :returns: The updated volume.
        """
        kwargs = {}
        if name is not None:
            kwargs["name"] = name
        if description is not None:
            kwargs["description"] = description
        updated_volume = self._get_client().volumes.update(
            volume_id, **kwargs)
        return updated_volume["volume"]

    @atomic.action_timer("cinder_v3.list_volumes")
    def list_volumes(self, detailed=True, search_opts=None, marker=None,
                     limit=None, sort=None):
        """List all volumes."""
        return self._get_client().volumes.list(
            detailed=detailed, search_opts=search_opts, marker=marker,
            limit=limit, sort=sort)

    @atomic.action_timer("cinder_v3.list_types")
    def list_types(self, search_opts=None, is_public=None):
        """Lists all volume types."""
        return (self._get_client()
                .volume_types.list(search_opts=search_opts,
                                   is_public=is_public))

    @atomic.action_timer("cinder_v3.create_snapshot")
    def create_snapshot(self, volume_id, force=False,
                        name=None, description=None, metadata=None):
        """Create one snapshot.

        Returns when the snapshot is actually created and is in the "Available"
        state.

        :param volume_id: volume uuid for creating snapshot
        :param force: flag to indicate whether to snapshot a volume even if
                      it's attached to an instance
        :param name: Name of the snapshot
        :param description: Description of the snapshot
        :returns: Created snapshot object
        """
        kwargs = {"force": force,
                  "name": name or self.generate_random_name(),
                  "description": description,
                  "metadata": metadata}

        snapshot = self._get_client().volume_snapshots.create(volume_id,
                                                              **kwargs)
        rutils.interruptable_sleep(
            CONF.openstack.cinder_volume_create_prepoll_delay)
        snapshot = self._wait_available_volume(snapshot)
        return snapshot

    @atomic.action_timer("cinder_v3.create_backup")
    def create_backup(self, volume_id, container=None,
                      name=None, description=None,
                      incremental=False, force=False,
                      snapshot_id=None):
        """Create a volume backup of the given volume.

        :param volume_id: The ID of the volume to backup.
        :param container: The name of the backup service container.
        :param name: The name of the backup.
        :param description: The description of the backup.
        :param incremental: Incremental backup.
        :param force: If True, allows an in-use volume to be backed up.
        :param snapshot_id: The ID of the snapshot to backup.
        """
        kwargs = {"force": force,
                  "name": name or self.generate_random_name(),
                  "description": description,
                  "container": container,
                  "incremental": incremental,
                  "force": force,
                  "snapshot_id": snapshot_id}
        backup = self._get_client().backups.create(volume_id, **kwargs)
        return self._wait_available_volume(backup)

    @atomic.action_timer("cinder_v3.create_volume_type")
    def create_volume_type(self, name=None, description=None, is_public=True):
        """create volume type.

        :param name: Descriptive name of the volume type
        :param description: Description of the volume type
        :param is_public: Volume type visibility
        :returns: Return the created volume type.
        :returns: VolumeType object
        """
        kwargs = {"name": name or self.generate_random_name(),
                  "description": description,
                  "is_public": is_public}
        return self._get_client().volume_types.create(**kwargs)

    @atomic.action_timer("cinder_v3.update_volume_type")
    def update_volume_type(self, volume_type, update_name=False,
                           description=None, is_public=None):
        """Update the name and/or description for a volume type.

        :param volume_type: The ID or a instance of the :class:`VolumeType`
                            to update.
        :param update_name: if True, can update name by generating random name.
                            if False, don't update name.
        :param description: Description of the volume type.
        :rtype: :class:`VolumeType`
        """
        name = None
        if update_name:
            name = self.generate_random_name()
        return self._get_client().volume_types.update(
            volume_type=volume_type, name=name, description=description,
            is_public=is_public)

    @atomic.action_timer("cinder_v3.add_type_access")
    def add_type_access(self, volume_type, project):
        """Add a project to the given volume type access list.

        :param volume_type: Volume type name or ID to add access for the given
                            project
        :project: Project ID to add volume type access for
        :return: An instance of cinderclient.apiclient.base.TupleWithMeta
        """
        return self._get_client().volume_type_access.add_project_access(
            volume_type=volume_type, project=project)

    @atomic.action_timer("cinder_v3.list_type_access")
    def list_type_access(self, volume_type):
        """Print access information about the given volume type

        :param volume_type: Filter results by volume type name or ID
        :return: VolumeTypeAcces of specific project
        """
        return self._get_client().volume_type_access.list(volume_type)


@service.compat_layer(CinderV3Service)
class UnifiedCinderV3Service(cinder_common.UnifiedCinderMixin,
                             block.BlockStorage):

    @staticmethod
    def _unify_volume(volume):
        if isinstance(volume, dict):
            return block.Volume(id=volume["id"], name=volume["name"],
                                size=volume["size"], status=volume["status"])
        else:
            return block.Volume(id=volume.id, name=volume.name,
                                size=volume.size, status=volume.status)

    @staticmethod
    def _unify_snapshot(snapshot):
        return block.VolumeSnapshot(id=snapshot.id, name=snapshot.name,
                                    volume_id=snapshot.volume_id,
                                    status=snapshot.status)

    def create_volume(self, size, consistencygroup_id=None,
                      group_id=None, snapshot_id=None, source_volid=None,
                      name=None, description=None,
                      volume_type=None, user_id=None,
                      project_id=None, availability_zone=None,
                      metadata=None, imageRef=None, scheduler_hints=None,
                      source_replica=None, backup_id=None):
        """Creates a volume.

        :param size: Size of volume in GB
        :param consistencygroup_id: ID of the consistencygroup
        :param group_id: ID of the group
        :param snapshot_id: ID of the snapshot
        :param name: Name of the volume
        :param description: Description of the volume
        :param volume_type: Type of volume
        :param user_id: User id derived from context(IGNORED)
        :param project_id: Project id derived from context(IGNORED)
        :param availability_zone: Availability Zone to use
        :param metadata: Optional metadata to set on volume creation
        :param imageRef: reference to an image stored in glance
        :param source_volid: ID of source volume to clone from
        :param scheduler_hints: (optional extension) arbitrary key-value pairs
                            specified by the client to help boot an instance
        :param backup_id: ID of the backup

        :returns: Return a new volume.
        """
        return self._unify_volume(self._impl.create_volume(
            size, consistencygroup_id=consistencygroup_id,
            snapshot_id=snapshot_id,
            source_volid=source_volid, name=name,
            description=description, volume_type=volume_type,
            availability_zone=availability_zone, metadata=metadata,
            imageRef=imageRef, scheduler_hints=scheduler_hints,
            backup_id=backup_id))

    def list_volumes(self, detailed=True, search_opts=None, marker=None,
                     limit=None, sort=None):
        """Lists all volumes.

        :param detailed: Whether to return detailed volume info.
        :param search_opts: Search options to filter out volumes.
        :param marker: Begin returning volumes that appear later in the volume
                       list than that represented by this volume id.
        :param limit: Maximum number of volumes to return.
        :param sort: Sort information
        :returns: Return volumes list.
        """
        return [self._unify_volume(volume)
                for volume in self._impl.list_volumes(
                detailed=detailed, search_opts=search_opts, marker=marker,
                limit=limit, sort=sort)]

    def get_volume(self, volume_id):
        """Get a volume.

        :param volume_id: The ID of the volume to get.

        :returns: Return the volume.
        """
        return self._unify_volume(self._impl.get_volume(volume_id))

    def extend_volume(self, volume, new_size):
        """Extend the size of the specified volume."""
        return self._unify_volume(
            self._impl.extend_volume(volume, new_size=new_size))

    def update_volume(self, volume_id,
                      name=None, description=None):
        """Update the name or description for a volume.

        :param volume_id: The updated volume id.
        :param name: The volume name.
        :param description: The volume description.

        :returns: The updated volume.
        """
        return self._unify_volume(self._impl.update_volume(
            volume_id, name=name, description=description))

    def list_types(self, search_opts=None, is_public=None):
        """Lists all volume types."""
        return self._impl.list_types(search_opts=search_opts,
                                     is_public=is_public)

    def create_snapshot(self, volume_id, force=False,
                        name=None, description=None, metadata=None):
        """Create one snapshot.

        Returns when the snapshot is actually created and is in the "Available"
        state.

        :param volume_id: volume uuid for creating snapshot
        :param force: If force is True, create a snapshot even if the volume is
                      attached to an instance. Default is False.
        :param name: Name of the snapshot
        :param description: Description of the snapshot
        :param metadata: Metadata of the snapshot
        :returns: Created snapshot object
        """
        return self._unify_snapshot(self._impl.create_snapshot(
            volume_id, force=force, name=name,
            description=description, metadata=metadata))

    def list_snapshots(self, detailed=True):
        """Get a list of all snapshots."""
        return [self._unify_snapshot(snapshot)
                for snapshot in self._impl.list_snapshots(detailed=detailed)]

    def create_backup(self, volume_id, container=None,
                      name=None, description=None,
                      incremental=False, force=False,
                      snapshot_id=None):
        """Creates a volume backup.

        :param volume_id: The ID of the volume to backup.
        :param container: The name of the backup service container.
        :param name: The name of the backup.
        :param description: The description of the backup.
        :param incremental: Incremental backup.
        :param force: If True, allows an in-use volume to be backed up.
        :param snapshot_id: The ID of the snapshot to backup.

        :returns: The created backup object.
        """
        return self._unify_backup(self._impl.create_backup(
            volume_id, container=container, name=name, description=description,
            incremental=incremental, force=force, snapshot_id=snapshot_id))

    def create_volume_type(self, name=None, description=None, is_public=True):
        """Creates a volume type.

        :param name: Descriptive name of the volume type
        :param description: Description of the volume type
        :param is_public: Volume type visibility
        :returns: Return the created volume type.
        """
        return self._impl.create_volume_type(name=name,
                                             description=description,
                                             is_public=is_public)

    def restore_backup(self, backup_id, volume_id=None):
        """Restore the given backup.

        :param backup_id: The ID of the backup to restore.
        :param volume_id: The ID of the volume to restore the backup to.

        :returns: Return the restored backup.
        """
        return self._unify_volume(self._impl.restore_backup(
            backup_id, volume_id=volume_id))
