# -*- coding: utf-8 -*- #
# Copyright 2014 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.
"""Command for deleting instances."""


from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import utils
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import completers
from googlecloudsdk.command_lib.compute import exceptions as compute_exceptions
from googlecloudsdk.command_lib.compute.instances import flags
from googlecloudsdk.core import resources as cloud_resources
from googlecloudsdk.core.console import console_io
from six.moves import zip


_INSTANCE_DELETE_PROMPT = 'The following instances will be deleted.'
_INSTANCE_DELETE_PROMPT_DISK_ADDENDUM = """\
Any attached disks configured to be auto-deleted will be deleted unless they \
are attached to any other instances or the `--keep-disks` flag is given and \
specifies them for keeping. \
Deleting a disk is irreversible and any data on the disk will be lost."""

# During delete, graceful shutdown can take up to 60 minutes to complete, we
# are setting timeout to `70` minutes to give some space for delete operation
# to complete gracefully
_TIMEOUT_IN_SEC = 60 * 70

AUTO_DELETE_OVERRIDE_CHOICES = {
    'boot': 'The first partition is reserved for the root filesystem.',
    'data': 'A non-boot disk.',
    'all': 'All disk types.',
}

DETAILED_HELP = {
    'EXAMPLES': """
    To delete an instance called 'instance-1' in the zone 'us-central-2-a', run:

      $ {command} instance-1 --zone=us-central2-a

  """
}


@base.UniverseCompatible
class Delete(base.DeleteCommand):
  """Delete Compute Engine virtual machine instances.

  *{command}* deletes one or more Compute Engine virtual machine
  instances.
  """

  @classmethod
  def Args(cls, parser):
    auto_delete_override = parser.add_mutually_exclusive_group()

    auto_delete_override.add_argument(
        '--delete-disks',
        choices=AUTO_DELETE_OVERRIDE_CHOICES,
        metavar='DISK_TYPE',
        help="""\
        The types of disks to delete with instance deletion regardless
        of the disks' auto-delete configuration. When this flag is
        provided, the auto-delete bits on the attached disks are
        modified accordingly before the instance deletion requests are
        issued. For more information on disk auto-deletion, see
        [Set the auto-delete state of a zonal persistent disk](https://cloud.google.com/compute/docs/disks/modify-persistent-disk#updateautodelete)
        """)

    auto_delete_override.add_argument(
        '--keep-disks',
        choices=AUTO_DELETE_OVERRIDE_CHOICES,
        metavar='DISK_TYPE',
        help="""\
        The types of disks to not delete with instance deletion regardless
        of the disks' auto-delete configuration. When this flag is
        provided, the auto-delete bits on the attached disks are
        modified accordingly before the instance deletion requests are
        issued. For more information on disk auto-deletion, see
        [Set the auto-delete state of a zonal persistent disk](https://cloud.google.com/compute/docs/disks/modify-persistent-disk#updateautodelete)
        """)

    flags.INSTANCES_ARG.AddArgument(parser, operation_type='delete')

    if (
        cls.ReleaseTrack() == base.ReleaseTrack.ALPHA
        or cls.ReleaseTrack() == base.ReleaseTrack.BETA
    ):
      parser.add_argument(
          '--no-graceful-shutdown',
          action='store_true',
          default=None,
          help=(
              'Deletes the instance immediately without gracefully shutting it'
              ' down. If a graceful shutdown is in progress, then the instance'
              ' is forcefully stopped and deleted.'
          ),
      )

    parser.display_info.AddCacheUpdater(completers.InstancesCompleter)

  def GetInstances(self, refs, client):
    """Fetches instance objects corresponding to the given references."""
    instance_get_requests = []
    for ref in refs:
      request_protobuf = client.messages.ComputeInstancesGetRequest(
          **ref.AsDict())
      instance_get_requests.append((client.apitools_client.instances, 'Get',
                                    request_protobuf))

    errors = []
    instances = client.MakeRequests(
        requests=instance_get_requests,
        errors_to_collect=errors)
    if errors:
      utils.RaiseToolException(
          errors,
          error_message='Failed to fetch some instances:')
    return instances

  def PromptIfDisksWithoutAutoDeleteWillBeDeleted(self, disks_to_warn_for):
    """Prompts if disks with False autoDelete will be deleted.

    Args:
      disks_to_warn_for: list of tuple of (references to disk resources, disk
        type). disk_type(str)-> either 'zonalDisk' or 'regionalDisk'.
    """
    if not disks_to_warn_for:
      return

    prompt_list = []
    for ref, disk_type in disks_to_warn_for:
      prompt_list.append(
          '[{0}] in [{1}]'.format(
              ref.Name(), ref.zone if disk_type == 'zonalDisk' else ref.region
          )
      )

    prompt_message = utils.ConstructList(
        'The following disks are not configured to be automatically deleted '
        'with instance deletion, but they will be deleted as a result of '
        'this operation if they are not attached to any other instances:',
        prompt_list,
    )
    if not console_io.PromptContinue(message=prompt_message):
      raise compute_exceptions.AbortedError('Deletion aborted by user.')

  def AutoDeleteMustBeChanged(self, args, disk_resource):
    """Returns True if the autoDelete property of the disk must be changed."""
    if args.keep_disks == 'boot':
      return disk_resource.autoDelete and disk_resource.boot
    elif args.keep_disks == 'data':
      return disk_resource.autoDelete and not disk_resource.boot
    elif args.keep_disks == 'all':
      return disk_resource.autoDelete

    elif args.delete_disks == 'data':
      return not disk_resource.autoDelete and not disk_resource.boot
    elif args.delete_disks == 'all':
      return not disk_resource.autoDelete
    elif args.delete_disks == 'boot':
      return not disk_resource.autoDelete and disk_resource.boot

    return False

  def Run(self, args):
    holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
    client = holder.client

    refs = flags.INSTANCES_ARG.ResolveAsResource(
        args, holder.resources,
        scope_lister=flags.GetInstanceZoneScopeLister(client))
    msg = _INSTANCE_DELETE_PROMPT
    if args.keep_disks != 'all':
      msg += ' ' + _INSTANCE_DELETE_PROMPT_DISK_ADDENDUM
    utils.PromptForDeletion(refs, scope_name='zone', prompt_title=msg)

    if args.delete_disks or args.keep_disks:
      instance_resources = self.GetInstances(refs, client)

      disks_to_warn_for = []
      set_auto_delete_requests = []

      for ref, resource in zip(refs, instance_resources):
        for disk in resource.disks:
          # Determines whether the current disk needs to have its
          # autoDelete parameter changed.
          if not self.AutoDeleteMustBeChanged(args, disk):
            continue

          # At this point, we know that the autoDelete property of the
          # disk must be changed. Since autoDelete is a boolean, we
          # just negate it!
          # Yay, computer science! :) :) :)
          new_auto_delete = not disk.autoDelete
          if new_auto_delete:
            # Parse the disk as zonal and if it fails, parse it as regional.
            try:
              disks_to_warn_for.append((
                  holder.resources.Parse(
                      disk.source,
                      collection='compute.disks',
                      params={'zone': ref.zone},
                  ),
                  'zonalDisk',
              ))
            except cloud_resources.WrongResourceCollectionException:
              disks_to_warn_for.append((
                  holder.resources.Parse(
                      disk.source,
                      collection='compute.regionDisks',
                      params={'zone': ref.zone},
                  ),
                  'regionalDisk',
              ))

          set_auto_delete_requests.append((
              client.apitools_client.instances,
              'SetDiskAutoDelete',
              client.messages.ComputeInstancesSetDiskAutoDeleteRequest(
                  autoDelete=new_auto_delete,
                  deviceName=disk.deviceName,
                  instance=ref.Name(),
                  project=ref.project,
                  zone=ref.zone)))

      if set_auto_delete_requests:
        self.PromptIfDisksWithoutAutoDeleteWillBeDeleted(disks_to_warn_for)
        errors = []
        client.MakeRequests(
            requests=set_auto_delete_requests,
            errors_to_collect=errors,
            timeout=_TIMEOUT_IN_SEC,
        )
        if errors:
          utils.RaiseToolException(
              errors,
              error_message=('Some requests to change disk auto-delete '
                             'behavior failed:'))

    delete_requests = []
    for ref in refs:
      if (
          self.ReleaseTrack() == base.ReleaseTrack.ALPHA
          or self.ReleaseTrack() == base.ReleaseTrack.BETA
      ):
        request_protobuf = client.messages.ComputeInstancesDeleteRequest(
            **ref.AsDict(), noGracefulShutdown=args.no_graceful_shutdown
        )
      else:
        request_protobuf = client.messages.ComputeInstancesDeleteRequest(
            **ref.AsDict()
        )
      delete_requests.append((client.apitools_client.instances, 'Delete',
                              request_protobuf))

    return client.MakeRequests(delete_requests, timeout=_TIMEOUT_IN_SEC)

Delete.detailed_help = DETAILED_HELP
