# -*- coding: utf-8 -*- #
# Copyright 2016 Google Inc. 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.
"""Delete images command."""

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

from containerregistry.client import docker_name
from containerregistry.client.v2_2 import docker_session
from googlecloudsdk.api_lib.container.images import util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.container import flags
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import http
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.resource import resource_printer


class Delete(base.DeleteCommand):
  """Delete existing images.

  The container images delete command of gcloud deletes a specified
  image and tags in a specified repository. Repositories
  must be hosted by the Google Container Registry.
  """

  detailed_help = {
      'DESCRIPTION':
          """\
          The container images delete command deletes the specified image from
          the registry. All associated tags are also deleted.
      """,
      'EXAMPLES':
          """\
          Deletes the image as long as there aren't additional, unspecified tags
          referencing it:

            $ {command} <IMAGE_NAME>

          Deletes the image (and tags) from the input IMAGE_NAME:

            $ {command} <IMAGE_NAME> --force-delete-tags

          Deletes the image (and tags) from the input IMAGE_NAME, without
          additional prompting:

            $ {command} <IMAGE_NAME> --force-delete-tags --quiet

          To easily identify and delete untagged images in a project, first
          filter digests that lack tags:

            $ gcloud container images list-tags [HOSTNAME]/[PROJECT-ID]/[IMAGE]\
              --filter='-tags:*'  --format='get(digest)' --limit=$BIG_NUMBER

          Then, delete these tagless images without prompting by running:

            $ {command} --quiet [HOSTNAME]/[PROJECT-ID]/[IMAGE]@DIGEST

      """,
  }

  @staticmethod
  def Args(parser):
    """Register flags for this command.

    Args:
      parser: An argparse.ArgumentParser-like object. It is mocked out in order
        to capture some information, but behaves like an ArgumentParser.
    """
    flags.AddTagOrDigestPositional(parser, verb='delete')

    parser.add_argument(
        '--force-delete-tags',
        action='store_true',
        default=False,
        help=(
            'If there are tags pointing to an image to be deleted then they '
            'must all be specified explicitly, or this flag must be specified, '
            'for the command to succeed.'))

  def Run(self, args):
    """This is what ts called when the user runs this command.

    Args:
      args: an argparse namespace. All the arguments that were provided to this
        command invocation.

    Raises:
      InvalidImageNameError: If the user specified an invalid image name.
    Returns:
      A list of the deleted docker_name.Tag and docker_name.Digest objects
    """
    # IMAGE_NAME: The fully-qualified image name to delete (with a digest).
    # Deletes the layers. Ex. gcr.io/google-appengine/java(@DIGEST|:TAG).

    http_obj = http.Http()
    with util.WrapExpectedDockerlessErrors():
      # collect input/validate
      digests, explicit_tags = self._ProcessImageNames(args.image_names)

      # Resolve tags to digests.
      for tag in explicit_tags:
        digests.add(util.GetDigestFromName(str(tag)))

      # Find all the tags that reference digests to be deleted.
      all_tags = set()
      for digest in digests:
        all_tags.update(util.GetDockerTagsForDigest(digest, http_obj))

      # Find all the tags that weren't specified explicitly.
      implicit_tags = all_tags.difference(explicit_tags)

      if implicit_tags and not args.force_delete_tags:
        log.error('Tags:')
        for tag in explicit_tags:
          log.error('- ' + str(tag))
        raise exceptions.Error(
            'This operation will implicitly delete the tags listed above. '
            'Please manually remove with the `untag` command or re-run with '
            '--force-delete-tags to confirm.')

      # Print the digests to be deleted.
      if digests:
        log.status.Print('Digests:')
      for digest in digests:
        self._PrintDigest(digest, http_obj)

      # Print the tags to be deleted.
      if explicit_tags:
        log.status.Print('Tags:')
      for tag in explicit_tags:
        log.status.Print('- ' + str(tag))

      # Prompt the user for consent to delete all the above.
      console_io.PromptContinue(
          'This operation will delete the tags and images identified by the '
          'digests above.',
          default=True,
          cancel_on_no=True)

      # The user has given explicit consent, merge the tags.
      explicit_tags.update(implicit_tags)

      # delete and collect output
      result = []
      for tag in explicit_tags:  # tags must be deleted before digests
        self._DeleteDockerTagOrDigest(tag, http_obj)
        result.append({'name': str(tag)})
      for digest in digests:
        self._DeleteDockerTagOrDigest(digest, http_obj)
        result.append({'name': str(digest)})
      return result

  def _ProcessImageNames(self, image_names):
    digests = set()
    tags = set()
    for image_name in image_names:
      docker_obj = util.GetDockerImageFromTagOrDigest(image_name)
      if isinstance(docker_obj, docker_name.Digest):
        digests.add(docker_obj)
      elif isinstance(docker_obj, docker_name.Tag):
        if not util.IsFullySpecified(image_name):
          log.warning('Implicit ":latest" tag specified: ' + image_name)
        tags.add(docker_obj)
    return [digests, tags]

  def _DeleteDockerTagOrDigest(self, tag_or_digest, http_obj):
    docker_session.Delete(
        creds=util.CredentialProvider(), name=tag_or_digest, transport=http_obj)
    log.DeletedResource(tag_or_digest)

  def _DeleteDigestAndAssociatedTags(self, digest, http_obj):
    # Digest must not have any tags in order to be deleted.
    # Errors raised from tag deletion are deliberately uncaught.
    util.DeleteTagsFromDigest(digest, http_obj)
    tag_list = util.GetTagNamesForDigest(digest, http_obj)
    for tag in tag_list:
      log.DeletedResource(tag)

    docker_session.Delete(
        creds=util.CredentialProvider(), name=digest, transport=http_obj)
    log.DeletedResource(digest)

  def _PrintDigest(self, digest, http_obj):
    log.status.Print('- ' + str(digest))
    self._DisplayDigestTags(digest, http_obj)

  def _DisplayDigestTags(self, digest, http_obj):
    tag_list = util.GetTagNamesForDigest(digest, http_obj)
    if not tag_list:  # no tags on this digest, skip delete prompt
      return
    fmt = ('list[title="  Associated tags:"]')
    resource_printer.Print(tag_list, fmt, out=log.status)
