# Copyright 2015 Andrew Kerr
# Copyright 2015 Chuck Fouts
# Copyright 2016 Clinton Knight
# 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.

from manilaclient import api_versions
from manilaclient import base
from manilaclient.common import constants

RESOURCES_PATH = '/share-groups'
RESOURCE_PATH = '/share-groups/%s'
RESOURCE_PATH_ACTION = '/share-groups/%s/action'
RESOURCES_NAME = 'share_groups'
RESOURCE_NAME = 'share_group'
SG_GRADUATION_VERSION = "2.55"


class ShareGroup(base.Resource):
    """A share group is a logical grouping of shares on a single backend."""

    def __repr__(self):
        return f"<Share Group: {self.id}>"

    def update(self, **kwargs):
        """Update this share group."""
        self.manager.update(self, **kwargs)

    def delete(self, force=False):
        """Delete this share group."""
        self.manager.delete(self, force=force)

    def reset_state(self, state):
        """Update this share group with the provided state."""
        self.manager.reset_state(self, state)


class ShareGroupManager(base.ManagerWithFind):
    """Manage :class:`ShareGroup` resources."""

    resource_class = ShareGroup

    def _create_share_group(
        self,
        share_group_type=None,
        share_types=None,
        share_network=None,
        name=None,
        description=None,
        source_share_group_snapshot=None,
        availability_zone=None,
    ):
        """Create a Share Group.

        :param share_group_type: either instance of ShareGroupType or text
            with UUID
        :param share_types: list of the share types allowed in the group. May
            not be supplied when 'source_group_snapshot_id' is provided.  These
            may be ShareType objects or UUIDs.
        :param share_network: either the share network object or text of the
            UUID - represents the share network to use when creating a
            share group when driver_handles_share_servers = True.
        :param name: text - name of the new share group
        :param description: text - description of the share group
        :param source_share_group_snapshot: text - either instance of
            ShareGroupSnapshot or text with UUID from which this shar_group is
            to be created. May not be supplied when 'share_types' is provided.
        :param availability_zone: name of the availability zone where the
            group is to be created
        :rtype: :class:`ShareGroup`
        """

        if share_types and source_share_group_snapshot:
            raise ValueError(
                'Cannot specify a share group with both'
                'share_types and source_share_group_snapshot.'
            )

        body = {}

        if name:
            body['name'] = name
        if description:
            body['description'] = description
        if availability_zone:
            body['availability_zone'] = availability_zone
        if share_group_type:
            body['share_group_type_id'] = base.getid(share_group_type)
        if share_network:
            body['share_network_id'] = base.getid(share_network)

        if source_share_group_snapshot:
            body['source_share_group_snapshot_id'] = base.getid(
                source_share_group_snapshot
            )
        elif share_types:
            body['share_types'] = [
                base.getid(share_type) for share_type in share_types
            ]

        return self._create(
            RESOURCES_PATH, {RESOURCE_NAME: body}, RESOURCE_NAME
        )

    @api_versions.wraps("2.31", "2.54")
    @api_versions.experimental_api
    def create(
        self,
        share_group_type=None,
        share_types=None,
        share_network=None,
        name=None,
        description=None,
        source_share_group_snapshot=None,
        availability_zone=None,
    ):
        return self._create_share_group(
            share_group_type=share_group_type,
            share_types=share_types,
            share_network=share_network,
            name=name,
            description=description,
            source_share_group_snapshot=source_share_group_snapshot,
            availability_zone=availability_zone,
        )

    @api_versions.wraps(SG_GRADUATION_VERSION)  # noqa
    def create(  # noqa
        self,
        share_group_type=None,
        share_types=None,
        share_network=None,
        name=None,
        description=None,
        source_share_group_snapshot=None,
        availability_zone=None,
    ):
        return self._create_share_group(
            share_group_type=share_group_type,
            share_types=share_types,
            share_network=share_network,
            name=name,
            description=description,
            source_share_group_snapshot=source_share_group_snapshot,
            availability_zone=availability_zone,
        )

    def _get_share_group(self, share_group):
        """Get a share group.

        :param share_group: either ShareGroup object or text with its UUID
        :rtype: :class:`ShareGroup`
        """
        share_group_id = base.getid(share_group)
        url = RESOURCE_PATH % share_group_id
        return self._get(url, RESOURCE_NAME)

    @api_versions.wraps("2.31", "2.54")
    @api_versions.experimental_api
    def get(self, share_group):
        return self._get_share_group(share_group)

    @api_versions.wraps(SG_GRADUATION_VERSION)  # noqa
    def get(self, share_group):  # noqa
        return self._get_share_group(share_group)

    def _list_share_groups(
        self, detailed=True, search_opts=None, sort_key=None, sort_dir=None
    ):
        """Get a list of all share groups.

        :param detailed: Whether to return detailed share group info or not.
        :param search_opts: dict with search options to filter out groups.
            available keys include (('name1', 'name2', ...), 'type'):
            - ('offset', int)
            - ('limit', int)
            - ('all_tenants', int)
            - ('name', text)
            - ('status', text)
            - ('share_server_id', text)
            - ('share_group_type_id', text)
            - ('source_share_group_snapshot_id', text)
            - ('host', text)
            - ('share_network_id', text)
            - ('project_id', text)
        :param sort_key: Key to be sorted (i.e. 'created_at' or 'status').
        :param sort_dir: Sort direction, should be 'desc' or 'asc'.
        :rtype: list of :class:`ShareGroup`
        """

        search_opts = search_opts or {}

        if sort_key is not None:
            if sort_key in constants.SHARE_GROUP_SORT_KEY_VALUES:
                search_opts['sort_key'] = sort_key
                # NOTE(cknight): Replace aliases with appropriate keys
                if sort_key == 'share_group_type':
                    search_opts['sort_key'] = 'share_group_type_id'
                elif sort_key == 'share_network':
                    search_opts['sort_key'] = 'share_network_id'
            else:
                msg = 'sort_key must be one of the following: %s.'
                msg_args = ', '.join(constants.SHARE_GROUP_SORT_KEY_VALUES)
                raise ValueError(msg % msg_args)

        if sort_dir is not None:
            if sort_dir in constants.SORT_DIR_VALUES:
                search_opts['sort_dir'] = sort_dir
            else:
                raise ValueError(
                    'sort_dir must be one of the following: {}.'.format(
                        ', '.join(constants.SORT_DIR_VALUES)
                    )
                )

        query_string = self._build_query_string(search_opts)

        if detailed:
            url = RESOURCES_PATH + '/detail' + query_string
        else:
            url = RESOURCES_PATH + query_string

        return self._list(url, RESOURCES_NAME)

    @api_versions.wraps("2.31", "2.54")
    @api_versions.experimental_api
    def list(
        self, detailed=True, search_opts=None, sort_key=None, sort_dir=None
    ):
        return self._list_share_groups(
            detailed=detailed,
            search_opts=search_opts,
            sort_key=sort_key,
            sort_dir=sort_dir,
        )

    @api_versions.wraps(SG_GRADUATION_VERSION)  # noqa
    def list(  # noqa
        self,
        detailed=True,
        search_opts=None,
        sort_key=None,
        sort_dir=None,
    ):
        return self._list_share_groups(
            detailed=detailed,
            search_opts=search_opts,
            sort_key=sort_key,
            sort_dir=sort_dir,
        )

    def _update_share_group(self, share_group, **kwargs):
        """Updates a share group.

        :param share_group: either ShareGroup object or text with its UUID
        :rtype: :class:`ShareGroup`
        """
        share_group_id = base.getid(share_group)
        url = RESOURCE_PATH % share_group_id
        if not kwargs:
            return self._get(url, RESOURCE_NAME)
        else:
            body = {RESOURCE_NAME: kwargs}
            return self._update(url, body, RESOURCE_NAME)

    @api_versions.wraps("2.31", "2.54")
    @api_versions.experimental_api
    def update(self, share_group, **kwargs):
        return self._update_share_group(share_group, **kwargs)

    @api_versions.wraps(SG_GRADUATION_VERSION)  # noqa
    def update(self, share_group, **kwargs):  # noqa
        return self._update_share_group(share_group, **kwargs)

    def _delete_share_group(self, share_group, force=False):
        """Delete a share group.

        :param share_group: either ShareGroup object or text with its UUID
        :param force: True to force the deletion
        """
        share_group_id = base.getid(share_group)
        if force:
            url = RESOURCE_PATH_ACTION % share_group_id
            body = {'force_delete': None}
            self.api.client.post(url, body=body)
        else:
            url = RESOURCE_PATH % share_group_id
            self._delete(url)

    @api_versions.wraps("2.31", "2.54")
    @api_versions.experimental_api
    def delete(self, share_group, force=False):
        self._delete_share_group(share_group, force=force)

    @api_versions.wraps(SG_GRADUATION_VERSION)  # noqa
    def delete(self, share_group, force=False):  # noqa
        self._delete_share_group(share_group, force=force)

    def _share_group_reset_state(self, share_group, state):
        """Update the specified share group with the provided state.

        :param share_group: either ShareGroup object or text with its UUID
        :param state: The new state for the share group
        """

        share_group_id = base.getid(share_group)
        url = RESOURCE_PATH_ACTION % share_group_id
        body = {'reset_status': {'status': state}}
        self.api.client.post(url, body=body)

    @api_versions.wraps("2.31", "2.54")
    @api_versions.experimental_api
    def reset_state(self, share_group, state):
        self._share_group_reset_state(share_group, state)

    @api_versions.wraps(SG_GRADUATION_VERSION)  # noqa
    def reset_state(self, share_group, state):  # noqa
        self._share_group_reset_state(share_group, state)
