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

from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils

from manilaclient import api_versions
from manilaclient.common._i18n import _
from manilaclient.common import cliutils
from manilaclient.osc import utils as oscutils

LOG = logging.getLogger(__name__)


class CreateShareSnapshot(command.ShowOne):
    """Create a share snapshot."""
    _description = _(
        "Create a snapshot of the given share")

    def get_parser(self, prog_name):
        parser = super(CreateShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "share",
            metavar="<share>",
            help=_("Name or ID of the share to create snapshot of")
        )
        parser.add_argument(
            "--force",
            action='store_true',
            default=False,
            help=_("Optional flag to indicate whether to snapshot "
                   "a share even if it's busy. (Default=False)")
        )
        parser.add_argument(
            "--name",
            metavar="<name>",
            default=None,
            help=_("Add a name to the snapshot (Optional).")
        )
        parser.add_argument(
            "--description",
            metavar="<description>",
            default=None,
            help=_("Add a description to the snapshot (Optional).")
        )
        parser.add_argument(
            '--wait',
            action='store_true',
            default=False,
            help=_('Wait for share snapshot creation')
        )
        parser.add_argument(
            "--property",
            metavar="<key=value>",
            default={},
            action=parseractions.KeyValueAction,
            help=_("Set a property to this snapshot "
                   "(repeat option to set multiple properties)."
                   "Available only for microversion >= 2.73"),
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        share = utils.find_resource(share_client.shares,
                                    parsed_args.share)

        if share_client.api_version >= api_versions.APIVersion("2.73"):
            property = parsed_args.property or {}
        elif parsed_args.property:
            raise exceptions.CommandError(
                "Setting metadtaa is only available with manila API version "
                ">= 2.73")

        share_snapshot = share_client.share_snapshots.create(
            share=share,
            force=parsed_args.force,
            name=parsed_args.name or None,
            description=parsed_args.description or None,
            metadata=property
        )
        if parsed_args.wait:
            if not utils.wait_for_status(
                status_f=share_client.share_snapshots.get,
                res_id=share_snapshot.id,
                success_status=['available']
            ):
                LOG.error(_("ERROR: Share snapshot is in error state."))

            share_snapshot = utils.find_resource(
                share_client.share_snapshots,
                share_snapshot.id)
        share_snapshot._info.pop('links', None)

        return self.dict2columns(share_snapshot._info)


class DeleteShareSnapshot(command.Command):
    """Delete one or more share snapshots"""
    _description = _(
        "Delete one or more share snapshots")

    def get_parser(self, prog_name):
        parser = super(DeleteShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            nargs="+",
            help=_("Name or ID of the snapshot(s) to delete")
        )
        parser.add_argument(
            "--force",
            action='store_true',
            default=False,
            help=_("Delete the snapshot(s) ignoring the current state.")
        )
        parser.add_argument(
            "--wait",
            action='store_true',
            default=False,
            help=_("Wait for share snapshot deletion")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share
        result = 0

        for snapshot in parsed_args.snapshot:
            try:
                snapshot_obj = utils.find_resource(
                    share_client.share_snapshots,
                    snapshot)
                if parsed_args.force:
                    share_client.share_snapshots.force_delete(
                        snapshot_obj)
                else:
                    share_client.share_snapshots.delete(
                        snapshot_obj)
                if parsed_args.wait:
                    if not utils.wait_for_delete(
                            manager=share_client.share_snapshots,
                            res_id=snapshot_obj.id):
                        result += 1
            except Exception as e:
                result += 1
                LOG.error(_(
                    "Failed to delete snapshot with "
                    "name or ID '%(snapshot)s': %(e)s"),
                    {'snapshot': snapshot, 'e': e})

        if result > 0:
            total = len(parsed_args.snapshot)
            msg = (_("%(result)s of %(total)s snapshots failed "
                   "to delete.") % {'result': result, 'total': total})
            raise exceptions.CommandError(msg)


class ShowShareSnapshot(command.ShowOne):
    """Display a share snapshot"""
    _description = _(
        "Show details about a share snapshot")

    def get_parser(self, prog_name):
        parser = super(ShowShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_("Name or ID of the snapshot to display")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        share_snapshot = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        export_locations = (
            share_client.share_snapshot_export_locations.list(
                share_snapshot))

        locations = []
        for location in export_locations:
            location._info.pop('links', None)
            locations.append(location._info)

        if parsed_args.formatter == 'table':
            locations = cliutils.convert_dict_list_to_string(
                locations)

        data = share_snapshot._info
        data['export_locations'] = locations
        # Special mapping for columns to make the output easier to read:
        # 'metadata' --> 'properties'
        data.update(
            {
                'properties':
                    format_columns.DictColumn(data.pop('metadata', {})),
            },
        )
        data.pop('links', None)

        return self.dict2columns(data)


class SetShareSnapshot(command.Command):
    """Set share snapshot properties."""
    _description = _("Set share snapshot properties")

    def get_parser(self, prog_name):
        parser = super(SetShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_('Name or ID of the snapshot to set a property for')
        )
        parser.add_argument(
            "--name",
            metavar="<name>",
            default=None,
            help=_("Set a name to the snapshot.")
        )
        parser.add_argument(
            "--description",
            metavar="<description>",
            default=None,
            help=_("Set a description to the snapshot.")
        )
        parser.add_argument(
            "--status",
            metavar="<status>",
            choices=['available', 'error', 'creating', 'deleting',
                     'manage_starting', 'manage_error',
                     'unmanage_starting', 'unmanage_error',
                     'error_deleting'],
            help=_("Assign a status to the snapshot (Admin only). "
                   "Options include : available, error, creating, "
                   "deleting, manage_starting, manage_error, "
                   "unmanage_starting, unmanage_error, error_deleting.")
        )
        parser.add_argument(
            "--property",
            metavar="<key=value>",
            default={},
            action=parseractions.KeyValueAction,
            help=_("Set a property to this snapshot "
                   "(repeat option to set multiple properties)"),
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share
        result = 0

        share_snapshot = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        kwargs = {}

        if parsed_args.name is not None:
            kwargs['display_name'] = parsed_args.name
        if parsed_args.description is not None:
            kwargs['display_description'] = parsed_args.description

        try:
            share_client.share_snapshots.update(
                share_snapshot,
                **kwargs
            )
        except Exception as e:
            result += 1
            LOG.error(_(
                "Failed to set share snapshot properties "
                "'%(properties)s': %(exception)s"),
                {'properties': kwargs,
                 'exception': e})

        if parsed_args.status:
            try:
                share_client.share_snapshots.reset_state(
                    share_snapshot,
                    parsed_args.status
                )
            except Exception as e:
                result += 1
                LOG.error(_(
                    "Failed to update snapshot status to "
                    "'%(status)s': %(e)s"),
                    {'status': parsed_args.status, 'e': e})
        if parsed_args.property:
            try:
                share_snapshot.set_metadata(parsed_args.property)
            except Exception as e:
                LOG.error(_("Failed to set share snapshot properties "
                            "'%(properties)s': %(exception)s"),
                          {'properties': parsed_args.property,
                           'exception': e})
                result += 1
        if result > 0:
            raise exceptions.CommandError(_("One or more of the "
                                          "set operations failed"))


class UnsetShareSnapshot(command.Command):
    """Unset a share snapshot property."""
    _description = _("Unset a share snapshot property")

    def get_parser(self, prog_name):
        parser = super(UnsetShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_("Name or ID of the snapshot to set a property for")
        )
        parser.add_argument(
            "--name",
            action='store_true',
            help=_("Unset snapshot name."),
        )
        parser.add_argument(
            "--description",
            action='store_true',
            help=_("Unset snapshot description."),
        )
        parser.add_argument(
            '--property',
            metavar='<key>',
            action='append',
            help=_('Remove a property from snapshot '
                   '(repeat option to remove multiple properties)'),
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        share_snapshot = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        kwargs = {}
        if parsed_args.name:
            kwargs['display_name'] = None
        if parsed_args.description:
            kwargs['display_description'] = None
        if kwargs:
            try:
                share_client.share_snapshots.update(
                    share_snapshot,
                    **kwargs
                )
            except Exception as e:
                raise exceptions.CommandError(_(
                    "Failed to unset snapshot display name "
                    "or display description : %s" % e))
        if parsed_args.property:
            for key in parsed_args.property:
                try:
                    share_snapshot.delete_metadata([key])
                except Exception as e:
                    raise exceptions.CommandError(_(
                        "Failed to unset snapshot property "
                        "'%(key)s': %(e)s"),
                        {'key': key, 'e': e})


class ListShareSnapshot(command.Lister):
    """List snapshots."""
    _description = _("List snapshots")

    def get_parser(self, prog_name):
        parser = super(ListShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "--all-projects",
            action='store_true',
            default=False,
            help=_("Display snapshots from all projects (Admin only).")
        )
        parser.add_argument(
            "--name",
            metavar="<name>",
            default=None,
            help=_("Filter results by name.")
        )
        parser.add_argument(
            '--description',
            metavar="<description>",
            default=None,
            help=_("Filter results by description. Available only for "
                   "microversion >= 2.36.")
        )
        parser.add_argument(
            '--status',
            metavar="<status>",
            default=None,
            help=_('Filter results by status')
        )
        parser.add_argument(
            '--share',
            metavar='<share>',
            default=None,
            help=_('Name or ID of a share to filter results by.')
        )
        parser.add_argument(
            '--usage',
            metavar='<usage>',
            default=None,
            choices=['used', 'unused'],
            help=_("Option to filter snapshots by usage.")
        )
        parser.add_argument(
            "--limit",
            metavar="<num-snapshots>",
            type=int,
            default=None,
            action=parseractions.NonNegativeAction,
            help=_("Limit the number of snapshots returned")
        )
        parser.add_argument(
            "--marker",
            metavar="<snapshot>",
            help=_("The last share ID of the previous page")
        )
        parser.add_argument(
            '--sort',
            metavar="<key>[:<direction>]",
            default='name:asc',
            help=_("Sort output by selected keys and directions(asc or desc) "
                   "(default: name:asc), multiple keys and directions can be "
                   "specified separated by comma")
        )
        parser.add_argument(
            "--name~",
            metavar="<name~>",
            default=None,
            help=_("Filter results matching a share snapshot name pattern. "
                   "Available only for microversion >= 2.36.")
        )
        parser.add_argument(
            '--description~',
            metavar="<description~>",
            default=None,
            help=_("Filter results matching a share snapshot description "
                   "pattern. Available only for microversion >= 2.36.")
        )
        parser.add_argument(
            '--detail',
            action='store_true',
            default=False,
            help=_("List share snapshots with details")
        )
        parser.add_argument(
            '--property',
            metavar='<key=value>',
            action=parseractions.KeyValueAction,
            help=_('Filter snapshots having a given metadata key=value '
                   'property. (repeat option to filter by multiple '
                   'properties)'),
        )
        parser.add_argument(
            '--count',
            action='store_true',
            default=False,
            help=_("The total count of share snapshots before pagination is "
                   "applied. This parameter is useful when applying "
                   "pagination parameters '--limit' and '--offset'. Available "
                   "only for microversion >= 2.79.")
        )

        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        share_id = None
        if parsed_args.share:
            share_id = utils.find_resource(share_client.shares,
                                           parsed_args.share).id
        columns = ['ID', 'Name']

        search_opts = {
            'offset': parsed_args.marker,
            'limit': parsed_args.limit,
            'all_tenants': parsed_args.all_projects,
            'name': parsed_args.name,
            'status': parsed_args.status,
            'share_id': share_id,
            'usage': parsed_args.usage,
            'metadata': oscutils.extract_key_value_options(
                parsed_args.property),
        }

        if share_client.api_version >= api_versions.APIVersion("2.36"):
            search_opts['name~'] = getattr(parsed_args, 'name~')
            search_opts['description~'] = getattr(parsed_args, 'description~')
            search_opts['description'] = parsed_args.description
        elif (parsed_args.description or getattr(parsed_args, 'name~') or
              getattr(parsed_args, 'description~')):
            raise exceptions.CommandError(
                "Pattern based filtering (name~, description~ and description)"
                " is only available with manila API version >= 2.36")

        if parsed_args.count:
            if share_client.api_version < api_versions.APIVersion("2.79"):
                raise exceptions.CommandError(
                    "Displaying total number of share snapshots is only "
                    "available with manila API version >= 2.79")
            if parsed_args.formatter != 'table':
                raise exceptions.CommandError(
                    "Count can only be printed when using '--format table'")

        if parsed_args.detail:
            columns.extend([
                'Status',
                'Description',
                'Created At',
                'Size',
                'Share ID',
                'Share Proto',
                'Share Size',
                'User ID'
            ])

        if parsed_args.all_projects:
            columns.append('Project ID')

        total_count = 0
        if parsed_args.count:
            search_opts['with_count'] = True
            snapshots, total_count = share_client.share_snapshots.list(
                search_opts=search_opts)
        else:
            snapshots = share_client.share_snapshots.list(
                search_opts=search_opts)

        snapshots = utils.sort_items(snapshots, parsed_args.sort, str)

        if parsed_args.count:
            print("Total number of snapshots: %s" % total_count)
        return (columns,
                (utils.get_item_properties(s, columns) for s in snapshots))


class AdoptShareSnapshot(command.ShowOne):
    """Adopt a share snapshot not handled by Manila (Admin only)."""

    _description = _("Adopt a share snapshot")

    def get_parser(self, prog_name):
        parser = super(AdoptShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "share",
            metavar="<share>",
            help=_("Name or ID of the share that owns the snapshot "
                   "to be adopted.")
        )
        parser.add_argument(
            "provider_location",
            metavar="<provider-location>",
            help=_("Provider location of the snapshot on the backend.")
        )
        parser.add_argument(
            "--name",
            metavar="<name>",
            default=None,
            help=_("Optional snapshot name (Default=None).")
        )
        parser.add_argument(
            "--description",
            metavar="<description>",
            default=None,
            help=_("Optional snapshot description (Default=None).")
        )
        parser.add_argument(
            "--driver-option",
            metavar="<key=value>",
            default={},
            action=parseractions.KeyValueAction,
            help=_(
                "Set driver options as key=value pairs."
                "(repeat option to set multiple key=value pairs)")
        )
        parser.add_argument(
            "--wait",
            action='store_true',
            help=_("Wait until share snapshot is adopted")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        share = utils.find_resource(share_client.shares,
                                    parsed_args.share)

        snapshot = share_client.share_snapshots.manage(
            share=share,
            provider_location=parsed_args.provider_location,
            driver_options=parsed_args.driver_option,
            name=parsed_args.name,
            description=parsed_args.description
        )

        if parsed_args.wait:
            if not utils.wait_for_status(
                status_f=share_client.share_snapshots.get,
                res_id=snapshot.id,
                success_status=['available'],
                error_status=['manage_error', 'error']
            ):
                LOG.error(_("ERROR: Share snapshot is in error state."))

            snapshot = utils.find_resource(share_client.share_snapshots,
                                           snapshot.id)

        snapshot._info.pop('links', None)

        return self.dict2columns(snapshot._info)


class AbandonShareSnapshot(command.Command):
    """Abandon one or more share snapshots (Admin only)."""

    _description = _("Abandon share snapshot(s)")

    def get_parser(self, prog_name):
        parser = super(AbandonShareSnapshot, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            nargs='+',
            help=_("Name or ID of the snapshot(s) to be abandoned.")
        )
        parser.add_argument(
            "--wait",
            action='store_true',
            help=_("Wait until share snapshot is abandoned")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share
        result = 0

        for snapshot in parsed_args.snapshot:
            snapshot_obj = utils.find_resource(
                share_client.share_snapshots,
                snapshot)
            try:
                share_client.share_snapshots.unmanage(snapshot_obj)
                if parsed_args.wait:
                    if not utils.wait_for_delete(
                            manager=share_client.share_snapshots,
                            res_id=snapshot_obj.id):
                        result += 1
            except Exception as e:
                result += 1
                LOG.error(_(
                    "Failed to abandon share snapshot with "
                    "name or ID '%(snapshot)s': %(e)s"),
                    {'snapshot': snapshot, 'e': e})

        if result > 0:
            total = len(parsed_args.snapshot)
            msg = (_("%(result)s of %(total)s snapshots failed "
                   "to abandon.") % {'result': result, 'total': total})
            raise exceptions.CommandError(msg)


class ShareSnapshotAccessAllow(command.ShowOne):
    """Allow read only access to a snapshot."""

    _description = _("Allow access to a snapshot")

    def get_parser(self, prog_name):
        parser = super(ShareSnapshotAccessAllow, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_("Name or ID of the snapshot")
        )
        parser.add_argument(
            'access_type',
            metavar="<access_type>",
            help=_('Access rule type (only "ip", "user" (user or group), '
                   '"cert" or "cephx" are supported).')
        )
        parser.add_argument(
            'access_to',
            metavar="<access_to>",
            help=_('Value that defines access.')
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        snapshot_obj = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        try:
            snapshot_access = share_client.share_snapshots.allow(
                snapshot=snapshot_obj,
                access_type=parsed_args.access_type,
                access_to=parsed_args.access_to
            )
            return self.dict2columns(snapshot_access)

        except Exception as e:
            raise exceptions.CommandError(
                "Failed to create access to share snapshot "
                "'%s': %s" % (snapshot_obj, e))


class ShareSnapshotAccessDeny(command.Command):
    """Delete access to a snapshot"""

    _description = _(
        "Delete access to a snapshot")

    def get_parser(self, prog_name):
        parser = super(ShareSnapshotAccessDeny, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_("Name or ID of the share snapshot to deny access to.")
        )
        parser.add_argument(
            "id",
            metavar="<id>",
            nargs="+",
            help=_("ID(s) of the access rule(s) to be deleted.")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share
        result = 0

        snapshot_obj = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        for access_id in parsed_args.id:
            try:
                share_client.share_snapshots.deny(
                    snapshot=snapshot_obj,
                    id=access_id
                )
            except Exception as e:
                result += 1
                LOG.error(_(
                    "Failed to delete access to share snapshot "
                    "for an access rule with ID '%(access)s': %(e)s"),
                    {'access': access_id, 'e': e})

        if result > 0:
            total = len(parsed_args.id)
            msg = (_("Failed to delete access to a share snapshot for "
                     "%(result)s out of %(total)s access rule ID's ")
                   % {'result': result, 'total': total})
            raise exceptions.CommandError(msg)


class ShareSnapshotAccessList(command.Lister):
    """Show access list for a snapshot"""

    _description = _(
        "Show access list for a snapshot")

    def get_parser(self, prog_name):
        parser = super(ShareSnapshotAccessList, self).get_parser(prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_("Name or ID of the share snapshot to show access list for.")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        snapshot_obj = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        access_rules = share_client.share_snapshots.access_list(
            snapshot_obj)

        columns = [
            "ID",
            "Access Type",
            "Access To",
            "State"
        ]

        return (columns,
                (utils.get_item_properties(s, columns) for s in access_rules))


class ShareSnapshotListExportLocation(command.Lister):
    """List export locations of a given snapshot"""

    _description = _(
        "List export locations of a given snapshot")

    def get_parser(self, prog_name):
        parser = super(ShareSnapshotListExportLocation, self).get_parser(
            prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_("Name or ID of the share snapshot.")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        snapshot_obj = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        export_locations = share_client.share_snapshot_export_locations.list(
            snapshot=snapshot_obj)

        columns = ["ID", "Path"]

        return (
            columns,
            (utils.get_item_properties(s, columns) for s in export_locations))


class ShareSnapshotShowExportLocation(command.ShowOne):
    """Show export location of the share snapshot"""

    _description = _(
        "Show export location of the share snapshot")

    def get_parser(self, prog_name):
        parser = super(ShareSnapshotShowExportLocation, self).get_parser(
            prog_name)
        parser.add_argument(
            "snapshot",
            metavar="<snapshot>",
            help=_("Name or ID of the share snapshot.")
        )
        parser.add_argument(
            "export_location",
            metavar="<export-location>",
            help=_("ID of the share snapshot export location.")
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share

        snapshot_obj = utils.find_resource(
            share_client.share_snapshots,
            parsed_args.snapshot)

        export_location = share_client.share_snapshot_export_locations.get(
            export_location=parsed_args.export_location,
            snapshot=snapshot_obj)

        return self.dict2columns(export_location._info)
