# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. 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.
"""Implementation of rm command for deleting resources."""

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import collections

from googlecloudsdk.api_lib.storage import cloud_api
from googlecloudsdk.api_lib.storage import errors as api_errors
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.storage import flags
from googlecloudsdk.command_lib.storage import folder_util
from googlecloudsdk.command_lib.storage import name_expansion
from googlecloudsdk.command_lib.storage import plurality_checkable_iterator
from googlecloudsdk.command_lib.storage import rm_command_util
from googlecloudsdk.command_lib.storage import stdin_iterator
from googlecloudsdk.command_lib.storage import user_request_args_factory
from googlecloudsdk.command_lib.storage.tasks import task_executor
from googlecloudsdk.command_lib.storage.tasks import task_graph_executor
from googlecloudsdk.command_lib.storage.tasks import task_status
from googlecloudsdk.command_lib.storage.tasks.rm import delete_task_iterator_factory
from googlecloudsdk.core import log


@base.UniverseCompatible
class Rm(base.Command):
  """Delete objects and buckets."""

  detailed_help = {
      'DESCRIPTION':
          """
      Delete objects and buckets.
      """,
      'EXAMPLES':
          """

      The following command deletes a Cloud Storage object named ``my-object''
      from the bucket ``my-bucket'':

        $ {command} gs://my-bucket/my-object

      The following command deletes all objects directly within the directory
      ``my-dir'' but no objects within subdirectories:

        $ {command} gs://my-bucket/my-dir/*

      The following command deletes all objects and subdirectories within the
      directory ``my-dir'':

        $ {command} gs://my-bucket/my-dir/**

      Note that for buckets that contain
      [versioned objects](https://cloud.google.com/storage/docs/object-versioning),
      the above command only affects live versions. Use the `--recursive` flag
      instead to delete all versions.

      The following command deletes all versions of all resources in
      ``my-bucket'' and then deletes the bucket.

        $ {command} --recursive gs://my-bucket/

      The following command deletes all text files in the top-level of
      ``my-bucket'', but not text files in subdirectories:

        $ {command} -recursive gs://my-bucket/*.txt

      The following command deletes one wildcard expression per line passed
      in by stdin:

        $ some_program | {command} -I
      """,
  }

  @classmethod
  def Args(cls, parser):
    parser.add_argument(
        'urls',
        nargs='*',
        help='The URLs of the resources to delete.')
    parser.add_argument(
        '--recursive',
        '-R',
        '-r',
        action='store_true',
        help=(
            'Recursively delete the contents of buckets or directories that'
            ' match the path expression.'
            ' By default, this will delete managed folders as well.'
            ' If the path is set to a bucket, like'
            " ``gs://bucket'', the bucket is also deleted. This option"
            ' implies the `--all-versions` option. If you want to delete only'
            " live object versions, use the ``**'' wildcard instead."
        ),
    )
    parser.add_argument(
        '--all-versions',
        '-a',
        action='store_true',
        help='Delete all'
        ' [versions](https://cloud.google.com/storage/docs/object-versioning)'
        ' of an object.')

    parser.add_argument(
        '--exclude-managed-folders',
        action='store_true',
        default=False,
        help=(
            'Excludes managed folders from command operations. By default'
            ' gcloud storage includes managed folders in recursive removals.'
            ' Please note that this flag would not be applicable for'
            ' hierarchical namespace buckets as we always list managed folders'
            ' for these buckets.'
        ),
    )

    flags.add_additional_headers_flag(parser)
    flags.add_continue_on_error_flag(parser)
    flags.add_precondition_flags(parser)
    flags.add_read_paths_from_stdin_flag(parser)

  def Run(self, args):

    if args.recursive:
      bucket_setting = name_expansion.BucketSetting.YES
      object_state = cloud_api.ObjectState.LIVE_AND_NONCURRENT
      recursion_setting = name_expansion.RecursionSetting.YES
    else:
      bucket_setting = name_expansion.BucketSetting.NO
      object_state = flags.get_object_state_from_flags(args)
      recursion_setting = name_expansion.RecursionSetting.NO_WITH_WARNING

    should_perform_managed_folder_operations = (
        args.recursive and not args.exclude_managed_folders
    )

    url_found_match_tracker = collections.OrderedDict()
    # We need to ensure that if we are going to do additional lookups for
    # Folders or Managed Folders, then we do not throw an error for unmatched
    # URLs at this stage.
    # If the command is run with recursive option, then for sure we will do
    # a Folders look up at a minimum and hence we will let that lookup throw
    # an error if we are not going to include managed folders or fallback to the
    # managed folders lookup stage.
    # If the command is run without a recursive option, then for sure we will
    # not do a Folders lookup and we will also not do a Managed Folders lookup
    # hence, this becomes the right place to throw an error.
    name_expansion_iterator = name_expansion.NameExpansionIterator(
        stdin_iterator.get_urls_iterable(args.urls, args.read_paths_from_stdin),
        fields_scope=cloud_api.FieldsScope.SHORT,
        include_buckets=bucket_setting,
        managed_folder_setting=folder_util.ManagedFolderSetting.DO_NOT_LIST,
        folder_setting=folder_util.FolderSetting.LIST_AS_PREFIXES,
        object_state=object_state,
        raise_error_for_unmatched_urls=not args.recursive,
        recursion_requested=recursion_setting,
        url_found_match_tracker=url_found_match_tracker,
    )

    user_request_args = (
        user_request_args_factory.get_user_request_args_from_command_args(args))
    task_status_queue = task_graph_executor.multiprocessing_context.Queue()
    task_iterator_factory = (
        delete_task_iterator_factory.DeleteTaskIteratorFactory(
            name_expansion_iterator,
            task_status_queue=task_status_queue,
            user_request_args=user_request_args))

    log.status.Print('Removing objects:')
    object_exit_code = task_executor.execute_tasks(
        task_iterator_factory.object_iterator(),
        parallelizable=True,
        task_status_queue=task_status_queue,
        progress_manager_args=task_status.ProgressManagerArgs(
            increment_type=task_status.IncrementType.INTEGER, manifest_path=None
        ),
        continue_on_error=args.continue_on_error,
    )

    if args.recursive:
      folder_expansion_iterator = name_expansion.NameExpansionIterator(
          args.urls,
          folder_setting=folder_util.FolderSetting.LIST_AS_FOLDERS,
          raise_error_for_unmatched_urls=not should_perform_managed_folder_operations,
          recursion_requested=recursion_setting,
          url_found_match_tracker=url_found_match_tracker,
      )
      try:
        folder_exit_code = rm_command_util.remove_folders(
            folder_expansion_iterator,
            task_status_queue,
            verbose=True,
        )
      except api_errors.GcsApiError as error:
        if error.payload.status_code != 403:
          # Avoids unexpectedly escalating permissions.
          raise
        log.warning('Unable to delete folders due to missing permissions.')
        folder_exit_code = 0
    else:
      folder_exit_code = 0

    if should_perform_managed_folder_operations:
      managed_folder_expansion_iterator = name_expansion.NameExpansionIterator(
          args.urls,
          managed_folder_setting=folder_util.ManagedFolderSetting.LIST_WITHOUT_OBJECTS,
          raise_error_for_unmatched_urls=True,
          # `rm` defaults to including managed folders, but this will raise a
          # precondition error if the command targets a non-UBLA bucket. These
          # errors should be silenced.
          raise_managed_folder_precondition_errors=False,
          recursion_requested=name_expansion.RecursionSetting.YES,
          url_found_match_tracker=url_found_match_tracker,
      )
      try:
        managed_folder_exit_code = rm_command_util.remove_managed_folders(
            args,
            managed_folder_expansion_iterator,
            task_status_queue,
            verbose=True,
        )
      except api_errors.GcsApiError as error:
        if error.payload.status_code != 403:
          # Avoids unexpectedly escalating permissions.
          raise
        log.warning(
            'Unable to delete managed folders due to missing permissions.'
        )
        managed_folder_exit_code = 0
    else:
      managed_folder_exit_code = 0

    bucket_iterator = plurality_checkable_iterator.PluralityCheckableIterator(
        task_iterator_factory.bucket_iterator()
    )

    # We perform the is_empty check to avoid printing unneccesary status lines.
    if args.recursive and not bucket_iterator.is_empty():
      log.status.Print('Removing buckets:')
      bucket_exit_code = task_executor.execute_tasks(
          bucket_iterator,
          parallelizable=True,
          task_status_queue=task_status_queue,
          progress_manager_args=task_status.ProgressManagerArgs(
              increment_type=task_status.IncrementType.INTEGER,
              manifest_path=None,
          ),
          continue_on_error=args.continue_on_error,
      )
    else:
      bucket_exit_code = 0

    self.exit_code = max(
        object_exit_code,
        managed_folder_exit_code,
        folder_exit_code,
        bucket_exit_code,
    )
