# -*- 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 listing operations."""

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

from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import lister
from googlecloudsdk.api_lib.compute import request_helper
from googlecloudsdk.api_lib.compute import utils
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.core import properties


def _AllowPartialError():
  return properties.VALUES.compute.allow_partial_error.GetBool()


def AddFlags(parser, is_ga):
  """Helper function for adding flags dependant on the release track."""
  parser.display_info.AddFormat("""\
      table(
        name,
        operationType:label=TYPE,
        targetLink.scope():label=TARGET,
        operation_http_status():label=HTTP_STATUS,
        status,
        insertTime:label=TIMESTAMP
      )""")
  if is_ga:
    lister.AddMultiScopeListerFlags(
        parser, zonal=True, regional=True, global_=True)
  else:
    lister.AddBaseListerArgs(parser)
    parser.add_argument(
        '--zones',
        metavar='ZONE',
        help=('If arguments are provided, only resources from the given '
              'zones are shown. If no arguments are provided all zonal '
              'operations are shown.'),
        type=arg_parsers.ArgList())
    parser.add_argument(
        '--regions',
        metavar='REGION',
        help=('If arguments are provided, only resources from the given '
              'regions are shown. If no arguments are provided all regional '
              'operations are shown.'),
        type=arg_parsers.ArgList())
    parser.add_argument(
        '--global',
        action='store_true',
        help='If provided, all global resources are shown.',
        default=False)


@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.UniverseCompatible
class List(base.ListCommand):
  """List Compute Engine operations."""

  @staticmethod
  def Args(parser):
    AddFlags(parser, True)

  def NoArguments(self, args):
    """Determine if the user provided any flags indicating scope."""
    no_compute_args = (args.zones is None and args.regions is None and
                       not getattr(args, 'global'))
    return no_compute_args

  def Run(self, args):
    """Yields zonal, regional, and/or global resources."""
    compute_holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
    compute_client = compute_holder.client

    # This is True if the user provided no flags indicating scope.
    no_scope_flags = self.NoArguments(args)

    requests = []
    request_data = lister.ParseNamesAndRegexpFlags(args,
                                                   compute_holder.resources)

    # TODO(b/36050874): Start using aggregatedList for zones and regions when
    # the operations list API supports them.
    if no_scope_flags:
      requests.append(
          (compute_client.apitools_client.globalOperations, 'AggregatedList',
           compute_client.apitools_client.globalOperations.GetRequestType(
               'AggregatedList')(
                   filter=request_data.filter,
                   maxResults=request_data.max_results,
                   returnPartialSuccess=True,
                   project=list(request_data.scope_set)[0].project)))
    else:
      if getattr(args, 'global'):
        requests.append(
            (compute_client.apitools_client.globalOperations, 'List',
             compute_client.apitools_client.globalOperations.GetRequestType(
                 'List')(
                     filter=request_data.filter,
                     maxResults=request_data.max_results,
                     project=list(request_data.scope_set)[0].project)))
      if args.regions is not None:
        args_region_names = [
            compute_holder.resources.Parse(  # pylint:disable=g-complex-comprehension
                region,
                params={'project': properties.VALUES.core.project.GetOrFail},
                collection='compute.regions').Name()
            for region in args.regions or []]
        # If no regions were provided by the user, fetch a list.
        errors = []
        region_names = (
            args_region_names or [res.name for res in lister.GetGlobalResources(  # pylint:disable=g-complex-comprehension
                service=compute_client.apitools_client.regions,
                project=properties.VALUES.core.project.GetOrFail(),
                filter_expr=None,
                http=compute_client.apitools_client.http,
                batch_url=compute_client.batch_url,
                errors=errors)])
        if errors:
          utils.RaiseToolException(
              errors,
              'Unable to fetch a list of regions. Specifying [--regions] may '
              'fix this issue:')
        for region_name in region_names:
          requests.append(
              (compute_client.apitools_client.regionOperations, 'List',
               compute_client.apitools_client.regionOperations.GetRequestType(
                   'List')(
                       filter=request_data.filter,
                       maxResults=request_data.max_results,
                       region=region_name,
                       project=list(request_data.scope_set)[0].project)))
      if args.zones is not None:
        args_zone_names = [
            compute_holder.resources.Parse(  # pylint:disable=g-complex-comprehension
                zone,
                params={
                    'project': properties.VALUES.core.project.GetOrFail,
                },
                collection='compute.zones').Name()
            for zone in args.zones or []]
        # If no zones were provided by the user, fetch a list.
        errors = []
        zone_names = (
            args_zone_names or [res.name for res in lister.GetGlobalResources(  # pylint:disable=g-complex-comprehension
                service=compute_client.apitools_client.zones,
                project=properties.VALUES.core.project.GetOrFail(),
                filter_expr=None,
                http=compute_client.apitools_client.http,
                batch_url=compute_client.batch_url,
                errors=errors)])
        if errors:
          utils.RaiseToolException(
              errors,
              'Unable to fetch a list of zones. Specifying [--zones] may '
              'fix this issue:')
        for zone_name in zone_names:
          requests.append(
              (compute_client.apitools_client.zoneOperations, 'List',
               compute_client.apitools_client.zoneOperations.GetRequestType(
                   'List')(
                       filter=request_data.filter,
                       maxResults=request_data.max_results,
                       zone=zone_name,
                       project=list(request_data.scope_set)[0].project)))
    errors = []
    results = list(
        request_helper.ListJson(
            requests=requests,
            http=compute_client.apitools_client.http,
            batch_url=compute_client.batch_url,
            errors=errors))

    if errors:
      if _AllowPartialError():
        utils.WarnIfPartialRequestFail(errors)
      else:
        utils.RaiseToolException(errors)

    return results


@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class ListBeta(List):
  """List Compute Engine operations."""

  @staticmethod
  def Args(parser):
    AddFlags(parser, False)

List.detailed_help = base_classes.GetGlobalRegionalListerHelp('operations')
ListBeta.detailed_help = {
    'brief': 'List Compute Engine operations',
    'DESCRIPTION': """
        *{command}* displays all Compute Engine operations in a
        project.

        By default, all global, regional, and zonal operations are listed. The
        results can be narrowed by providing combinations of the --zones,
        --regions, and --global flags.

        Note: *{command}* displays operations fewer than 14 days old, up to a
        maximum of 5000.
        """,
    'EXAMPLES': """
        To list all operations in a project in table form, run:

          $ {command}

        To list the URIs of all operations in a project, run:

          $ {command} --uri

        To list all operations in zones us-central1-b and
        europe-west1-d, run:

           $ {command} --zones=us-central1-b,europe-west1-d

        To list all global operations in a project, run:

           $ {command} --global

        To list all regional operations in a project, run:

           $ {command} --regions=""

        To list all operations with names prefixed with `operation`, run:

           $ {command} --filter="name:operation"

        To list all operations in the us-central1 and europe-west1
        regions and all operations in the us-central1-a zone, run:

           $ {command} --zones=us-central1-a --regions=us-central1,europe-west1

        To list all operations with a specified target, filter on the targetLink
        field (run `{command} --format=json` to see a full, well-structured list
        of available fields). Additionally, use `scope()` which extracts the
        last part of the URL to get the required target information, and run:

           $ {command} --filter="targetLink.scope():default-12345abc"
        """,
}
