#    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 openstackclient.identity import common as identity_common
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils as osc_utils
from oslo_utils import uuidutils

from manilaclient.common._i18n import _
from manilaclient.common.apiclient import utils as apiutils
from manilaclient.common import constants


LOG = logging.getLogger(__name__)


LOCK_DETAIL_ATTRIBUTES = [
    'ID',
    'Resource Id',
    'Resource Type',
    'Resource Action',
    'Lock Context',
    'User Id',
    'Project Id',
    'Created At',
    'Updated At',
    'Lock Reason',
]

LOCK_SUMMARY_ATTRIBUTES = [
    'ID',
    'Resource Id',
    'Resource Type',
    'Resource Action',
]

RESOURCE_TYPE_MANAGERS = {
    'share': 'shares',
    'access_rule': 'share_access_rules',
}


class CreateResourceLock(command.ShowOne):
    """Create a new resource lock."""

    _description = _("Lock a resource action from occurring on a resource")

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            'resource',
            metavar='<resource_name_or_id>',
            help='Name or ID of resource to lock.',
        )
        parser.add_argument(
            'resource_type',
            metavar='<resource_type>',
            help='Type of the resource (e.g.: share, access).',
        )
        parser.add_argument(
            '--resource-action',
            '--resource_action',
            metavar='<resource_action>',
            default='delete',
            help='Action to lock on the resource (default="delete")',
        )
        parser.add_argument(
            '--lock-reason',
            '--lock_reason',
            '--reason',
            metavar='<lock_reason>',
            help='Reason for the resource lock.',
        )
        return parser

    def take_action(self, parsed_args):
        share_client = self.app.client_manager.share
        resource_type = parsed_args.resource_type
        if resource_type not in RESOURCE_TYPE_MANAGERS:
            raise exceptions.CommandError(_("Unsupported resource type"))
        res_manager = RESOURCE_TYPE_MANAGERS[resource_type]

        resource = osc_utils.find_resource(
            getattr(share_client, res_manager), parsed_args.resource
        )
        resource_lock = share_client.resource_locks.create(
            resource.id,
            resource_type,
            parsed_args.resource_action,
            parsed_args.lock_reason,
        )

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

        return self.dict2columns(resource_lock._info)


class DeleteResourceLock(command.Command):
    """Remove one or more resource locks."""

    _description = _("Remove one or more resource locks")

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            'lock', metavar='<lock>', nargs='+', help='ID(s) of the lock(s).'
        )
        return parser

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

        for lock in parsed_args.lock:
            try:
                lock = apiutils.find_resource(
                    share_client.resource_locks, lock
                )
                lock.delete()
            except Exception as e:
                failure_count += 1
                LOG.error(
                    _("Failed to delete %(lock)s: %(e)s"),
                    {'lock': lock, 'e': e},
                )

        if failure_count > 0:
            raise exceptions.CommandError(
                _("Unable to delete some or all of the specified locks.")
            )


class ListResourceLock(command.Lister):
    """Lists all resource locks."""

    _description = _("Lists all resource locks")

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            '--all-projects',
            action='store_true',
            help=_("Filter resource locks for all projects. (Admin only)."),
        )
        parser.add_argument(
            '--project',
            default=None,
            help=_(
                "Filter resource locks for specific project by name or ID, "
                "combine with --all-projects (Admin only)."
            ),
        )
        parser.add_argument(
            '--user',
            default=None,
            help=_(
                "Filter resource locks for specific user by name or ID, "
                "combine with --all-projects to search across projects "
                "(Admin only)."
            ),
        )
        parser.add_argument(
            '--id',
            metavar='<id>',
            default=None,
            help='Filter resource locks by ID. Default=None.',
        )
        parser.add_argument(
            '--resource',
            '--resource-id',
            '--resource_id',
            default=None,
            metavar='<resource-id>',
            dest='resource',
            help=_(
                "Filter resource locks for a resource by ID, specify "
                "--resource-type to look up by name."
            ),
        )
        parser.add_argument(
            '--resource-type',
            '--resource_type',
            default=None,
            metavar='<resource_type>',
            help=_("Filter resource locks by type of resource."),
        )
        parser.add_argument(
            '--resource-action',
            '--resource_action',
            default=None,
            metavar='<resource_action>',
            help=_("Filter resource locks by resource action."),
        )

        parser.add_argument(
            '--lock-context',
            '--lock_context',
            '--context',
            default=None,
            choices=['user', 'admin', 'service'],
            metavar='<lock_context>',
            help=_("Filter resource locks by context."),
        )
        parser.add_argument(
            '--since',
            default=None,
            metavar='<created_since>',
            help=_(
                "Filter resource locks created since given date. "
                "The date format must be conforming to ISO8601. "
            ),
        )
        parser.add_argument(
            '--before',
            default=None,
            metavar='<created_before>',
            help=_(
                "Filter resource locks created before given date. "
                "The date format must be conforming to ISO8601. "
            ),
        )
        parser.add_argument(
            '--limit',
            metavar='<limit>',
            type=int,
            default=None,
            help=_("Number of resource locks to list. (Default=None)"),
        )
        parser.add_argument(
            '--offset',
            metavar="<offset>",
            default=None,
            help='Starting position of resource lock records '
            'in a paginated list.',
        )
        parser.add_argument(
            '--sort-key',
            '--sort_key',
            metavar='<sort_key>',
            type=str,
            default=None,
            choices=constants.RESOURCE_LOCK_SORT_KEY_VALUES,
            help=(
                f'Key to be sorted, available keys are '
                f'{constants.RESOURCE_LOCK_SORT_KEY_VALUES}. Default=None.'
            ),
        )
        parser.add_argument(
            '--sort-dir',
            '--sort_dir',
            metavar='<sort_dir>',
            type=str,
            default=None,
            choices=constants.SORT_DIR_VALUES,
            help=(
                f'Sort direction, available values are '
                f'{constants.SORT_DIR_VALUES}. OPTIONAL: Default=None.'
            ),
        )
        parser.add_argument(
            '--detailed',
            dest='detailed',
            metavar='<0|1>',
            nargs='?',
            type=int,
            const=1,
            default=0,
            help="Show detailed information about filtered resource locks.",
        )
        return parser

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

        columns = (
            LOCK_SUMMARY_ATTRIBUTES
            if not parsed_args.detailed
            else LOCK_DETAIL_ATTRIBUTES
        )

        project_id = None
        user_id = None

        if parsed_args.project:
            project_id = identity_common.find_project(
                identity_client,
                parsed_args.project,
                parsed_args.project_domain,
            ).id
        if parsed_args.user:
            user_id = identity_common.find_user(
                identity_client, parsed_args.user, parsed_args.user_domain
            ).id
        # set all_projects when using project option
        all_projects = bool(parsed_args.project) or parsed_args.all_projects

        resource_id = parsed_args.resource
        resource_type = parsed_args.resource_type
        if resource_type is not None:
            if resource_type not in RESOURCE_TYPE_MANAGERS:
                raise exceptions.CommandError(_("Unsupported resource type"))
            if resource_id is not None:
                res_manager = RESOURCE_TYPE_MANAGERS[resource_type]
                resource_id = osc_utils.find_resource(
                    getattr(share_client, res_manager), parsed_args.resource
                ).id
        elif resource_id and not uuidutils.is_uuid_like(resource_id):
            raise exceptions.CommandError(
                _("Provide resource ID or specify --resource-type.")
            )

        search_opts = {
            'all_projects': all_projects,
            'project_id': project_id,
            'user_id': user_id,
            'id': parsed_args.id,
            'resource_id': resource_id,
            'resource_type': parsed_args.resource_type,
            'resource_action': parsed_args.resource_action,
            'lock_context': parsed_args.lock_context,
            'created_before': parsed_args.before,
            'created_since': parsed_args.since,
            'limit': parsed_args.limit,
            'offset': parsed_args.offset,
        }

        resource_locks = share_client.resource_locks.list(
            search_opts=search_opts,
            sort_key=parsed_args.sort_key,
            sort_dir=parsed_args.sort_dir,
        )

        return (
            columns,
            (
                osc_utils.get_item_properties(m, columns)
                for m in resource_locks
            ),
        )


class ShowResourceLock(command.ShowOne):
    """Show details about a resource lock."""

    _description = _("Show details about a resource lock")

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            'lock', metavar='<lock>', help=_('ID of resource lock to show.')
        )
        return parser

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

        resource_lock = apiutils.find_resource(
            share_client.resource_locks, parsed_args.lock
        )

        return (
            LOCK_DETAIL_ATTRIBUTES,
            osc_utils.get_dict_properties(
                resource_lock._info, LOCK_DETAIL_ATTRIBUTES
            ),
        )


class SetResourceLock(command.Command):
    """Set resource lock properties."""

    _description = _("Update resource lock properties")

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            'lock', metavar='<lock>', help='ID of lock to update.'
        )
        parser.add_argument(
            '--resource-action',
            '--resource_action',
            metavar='<resource_action>',
            help='Resource action to set in the resource lock',
        )
        parser.add_argument(
            '--lock-reason',
            '--lock_reason',
            '--reason',
            dest='lock_reason',
            help="Reason for the resource lock",
        )
        return parser

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

        update_kwargs = {}
        if parsed_args.resource_action is not None:
            update_kwargs['resource_action'] = parsed_args.resource_action
        if parsed_args.lock_reason is not None:
            update_kwargs['lock_reason'] = parsed_args.lock_reason
        if update_kwargs:
            share_client.resource_locks.update(
                parsed_args.lock, **update_kwargs
            )


class UnsetResourceLock(command.Command):
    """Unsets a property on a resource lock."""

    _description = _("Remove resource lock properties")

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            'lock', metavar='<lock>', help='ID of resource lock to update.'
        )
        parser.add_argument(
            '--lock-reason',
            '--lock_reason',
            '--reason',
            dest='lock_reason',
            action='store_true',
            default=False,
            help="Unset the lock reason. (Default=False)",
        )
        return parser

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

        if parsed_args.lock_reason:
            share_client.resource_locks.update(
                parsed_args.lock, lock_reason=None
            )
