# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Common flags for the consumers subcommand group."""

import re

from googlecloudsdk.api_lib.services import services_util
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope.concepts import concepts
from googlecloudsdk.calliope.concepts import deps
from googlecloudsdk.command_lib.util import completers
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import properties

_SERVICES_LEGACY_LIST_COMMAND = ('services list --format=disable '
                                 '--flatten=serviceName')
_SERVICES_LIST_COMMAND = ('beta services list --format=disable '
                          '--flatten=config.name')

_OPERATION_NAME_RE = re.compile(r'operations/(?P<namespace>\w+)\.(?P<id>.*)')


class ConsumerServiceCompleter(completers.ListCommandCompleter):

  def __init__(self, **kwargs):
    super(ConsumerServiceCompleter, self).__init__(
        collection=services_util.SERVICES_COLLECTION,
        list_command=_SERVICES_LIST_COMMAND,
        flags=['enabled'],
        **kwargs)


class ConsumerServiceLegacyCompleter(completers.ListCommandCompleter):

  def __init__(self, **kwargs):
    super(ConsumerServiceLegacyCompleter, self).__init__(
        collection=services_util.SERVICES_COLLECTION,
        list_command=_SERVICES_LEGACY_LIST_COMMAND,
        flags=['enabled'],
        **kwargs)


def operation_flag(suffix='to act on'):
  return base.Argument(
      'operation', help='The name of the operation {0}.'.format(suffix))


def get_operation_namespace(op_name):
  match = _OPERATION_NAME_RE.match(op_name)
  if not match:
    raise arg_parsers.ArgumentTypeError("Invalid value '{0}': {1}".format(
        op_name, 'Operation format should be operations/namespace.id'))
  return match.group('namespace')


def consumer_service_flag(suffix='to act on', flag_name='service'):
  return base.Argument(
      flag_name,
      # This makes the argument required (at least one)
      nargs='+',
      completer=ConsumerServiceCompleter,
      help='The name of the service(s) {0}.'.format(suffix))


def single_consumer_service_flag(suffix='to act on', flag_name='service'):
  return base.Argument(
      flag_name,
      completer=ConsumerServiceCompleter,
      help='The name of the service {0}.'.format(suffix))


def available_service_flag(suffix='to act on', flag_name='service'):
  # NOTE: Because listing available services often forces the tab completion
  #       code to timeout, this flag will not enable tab completion.
  return base.Argument(
      flag_name,
      # This makes the argument required (at least one)
      nargs='+',
      help='The name of the service(s) {0}.'.format(suffix))


def service_flag(suffix='to act on', flag_name='service'):
  return base.Argument(
      flag_name, help='The name of the service {0}.'.format(suffix)
  )


def mcp_content_security_provider_flag(
    suffix='to act on', flag_name='mcp_content_security_provider'
):
  return base.Argument(
      flag_name,
      help='The MCP content security provider {0}.'.format(suffix),
  )


# TODO(b/321801975) Link public documentation about consumer policy format
# after public preview.
def consumer_policy_file_flag():
  return base.Argument(
      '--consumer-policy-file',
      required=True,
      help=(
          'Path to the file that contains the consumer policy in the YAML'
          ' format.'
      ),
  )


def bypass_dependency_check():
  return base.Argument(
      '--bypass-dependency-check',
      action='store_true',
      help=(
          ' This flag controls dependency management within the consumer'
          ' policy. If false, dependencies are enforced. When adding a new'
          ' service, it verifies that all the services dependencies'
          ' are already present in the policy. When removing a service, the'
          ' system will ensure that no other services in the policy depend on'
          ' it. If the policy lacks any required dependencies, the update will'
          ' not succeed, and an error will be returned.'
      ),
  )


def bypass_api_usage_check():
  return base.Argument(
      '--bypass-api-usage-check',
      action='store_true',
      help=(
          ' If true, the system will bypass usage checks for services'
          ' that are being removed. If false, the system will check if'
          ' the service to be removed was used in the last 30 days or enabled'
          ' in the last 3 days. If so, an error will be returned.'
      ),
  )


def skip_mcp_endpoint_check_flag(parser):
  return base.Argument(
      '--skip-mcp-endpoint-check',
      action='store_true',
      help=(
          ' If true, the system will bypass the check for MCP endpoint while'
          ' enabling a service.'
      ),
      hidden=True,
  ).AddToParser(parser)


def _create_key_resource_arg(help_txt, api_version, required=True):
  return presentation_specs.ResourcePresentationSpec(
      'key', _get_key_resource_spec(api_version), help_txt, required=required
  )


def _get_key_resource_spec(api_version):
  """Return the resource specification for a key."""
  if api_version == 'v2':
    return concepts.ResourceSpec(
        'apikeys.projects.locations.keys',
        api_version=api_version,
        resource_name='key',
        disable_auto_completers=True,
        keysId=_key_attribute_config(),
        locationsId=_location_attribute_config(),
        projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG)
  else:
    return concepts.ResourceSpec(
        'apikeys.projects.keys',
        api_version=api_version,
        resource_name='key',
        disable_auto_completers=True,
        keysId=_key_attribute_config(),
        projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG)


def _key_attribute_config():
  return concepts.ResourceParameterAttributeConfig(
      name='key', help_text='Id of the key')


def _location_attribute_config():
  return concepts.ResourceParameterAttributeConfig(
      name='location',
      help_text='Location of the key.',
      fallthroughs=[
          deps.Fallthrough(
              function=lambda: 'global',
              hint='location will default to {}'.format('global'),
              active=True,
              plural=False)
      ])


def key_flag(parser, suffix='to act on', api_version='v2', required=True):
  return concept_parsers.ConceptParser(
      [
          _create_key_resource_arg(
              help_txt='The name of the key {0}.'.format(suffix),
              api_version=api_version,
              required=required,
          )
      ]
  ).AddToParser(parser)


def display_name_flag(parser, suffix='to act on'):
  base.Argument(
      '--display-name',
      help='Display name of the key {0}.'.format(suffix)).AddToParser(parser)


def key_id_flag(parser, suffix='to act on'):
  base.Argument(
      '--key-id', help='User-specified ID of the key {0}.'.format(suffix)
  ).AddToParser(parser)


def service_account_flag(parser):
  base.Argument(
      '--service-account',
      help=(
          'The email of the service account the key is bound to. If this field'
          ' is specified, the key is a service account bound key and auth'
          ' enabled.'
      ),
  ).AddToParser(parser)


def add_key_undelete_args(parser):
  """Adds args for api-keys undelete command."""
  undelete_set_group = parser.add_mutually_exclusive_group(required=True)
  key_flag(
      undelete_set_group, suffix='to undelete', api_version='v2', required=False
  )
  _key_string_flag(undelete_set_group)


def skip_dependency_flag(parser):
  base.Argument(
      '--skip-dependency',
      action='store_true',
      help=(
          'If set, the dependencies of the service to be enabled will not be'
          ' enabled.'
      ),
  ).AddToParser(parser)


def validate_only_args(parser, suffix='to act on'):
  base.Argument(
      '--validate-only',
      action='store_true',
      help=(
          "Validate the {} action, but don't actually perform it".format(suffix)
      ),
  ).AddToParser(parser)


def _bypass_dependency_service_check_flag(parser):
  base.Argument(
      '--bypass-dependency-service-check',
      action='store_true',
      help=(
          'If specified, the disable call will bypass the check for'
          ' dependencies and the dependencies will remain enabled.'
      ),
  ).AddToParser(parser)


def bypass_api_usage_check_flag(parser):
  base.Argument(
      '--bypass-api-usage-check',
      action='store_true',
      help=(
          'If specified, the system will bypass usage checks for services that'
          ' are being removed. Otherwise, the system will check if the service'
          ' to be removed was used in the last 30 days or enabled in the last 3'
          ' days. If so, the system will return an error.'
      ),
  ).AddToParser(parser)


def _disable_dependency_services_flag(parser):
  base.Argument(
      '--disable-dependency-services',
      action='store_true',
      help=(
          ' If specified, the disable call will proceed disabling the service'
          ' and all the enabled services depend on the service to be disabled'
      ),
  ).AddToParser(parser)


def add_dependency_check_args(parser):
  """Adds resource args for command."""
  dependent_check_group = parser.add_mutually_exclusive_group(required=False)
  _disable_dependency_services_flag(dependent_check_group)
  _bypass_dependency_service_check_flag(dependent_check_group)


def add_resource_args(parser):
  """Adds resource args for command."""
  resource_group = parser.add_mutually_exclusive_group(required=False)
  _project_id_flag(resource_group)
  _folder_id_flag(resource_group)
  _orgnaization_id_flag(resource_group)


def add_key_update_args(parser):
  """Adds args for api-keys update command."""
  update_set_restriction_group = parser.add_mutually_exclusive_group(
      required=False
  )
  _add_clear_restrictions_arg(update_set_restriction_group)
  restriction_group = update_set_restriction_group.add_argument_group()
  client_restriction_group = restriction_group.add_mutually_exclusive_group()
  _allowed_referrers_arg(client_restriction_group)
  _allowed_ips_arg(client_restriction_group)
  _allowed_bundle_ids(client_restriction_group)
  _allowed_application(client_restriction_group)
  _api_targets_arg(restriction_group)
  update_set_annotation_group = parser.add_mutually_exclusive_group(
      required=False
  )
  _annotations(update_set_annotation_group)
  _add_clear_annotations_arg(update_set_annotation_group)


def add_key_create_args(parser):
  """Add args for api-keys create command."""
  restriction_group = parser.add_argument_group()
  client_restriction_group = restriction_group.add_mutually_exclusive_group()
  _allowed_referrers_arg(client_restriction_group)
  _allowed_ips_arg(client_restriction_group)
  _allowed_bundle_ids(client_restriction_group)
  _allowed_application(client_restriction_group)
  _api_targets_arg(restriction_group)
  _annotations(parser)


def _add_clear_restrictions_arg(parser):
  base.Argument(
      '--clear-restrictions',
      action='store_true',
      help='If set, clear all restrictions on the key.').AddToParser(parser)


def _add_clear_annotations_arg(parser):
  base.Argument(
      '--clear-annotations',
      action='store_true',
      help='If set, clear all annotations on the key.').AddToParser(parser)


def _allowed_referrers_arg(parser):
  base.Argument(
      '--allowed-referrers',
      default=[],
      type=arg_parsers.ArgList(),
      metavar='ALLOWED_REFERRERS',
      help='A list of regular expressions for the referrer URLs that are '
      'allowed to make API calls with this key.').AddToParser(parser)


def _allowed_ips_arg(parser):
  base.Argument(
      '--allowed-ips',
      default=[],
      type=arg_parsers.ArgList(),
      metavar='ALLOWED_IPS',
      help='A list of the caller IP addresses that are allowed to make API '
      'calls with this key.').AddToParser(parser)


def _allowed_bundle_ids(parser):
  base.Argument(
      '--allowed-bundle-ids',
      default=[],
      metavar='ALLOWED_BUNDLE_IDS',
      type=arg_parsers.ArgList(),
      help='iOS app\'s bundle ids that are allowed to use the key.'
  ).AddToParser(parser)


def _allowed_application(parser):
  base.Argument(
      '--allowed-application',
      type=arg_parsers.ArgDict(
          spec={
              'sha1_fingerprint': str,
              'package_name': str
          },
          required_keys=['sha1_fingerprint', 'package_name'],
          max_length=2),
      metavar='sha1_fingerprint=SHA1_FINGERPRINT,package_name=PACKAGE_NAME',
      action='append',
      help=('Repeatable. Specify multiple allowed applications. '
            'The accepted keys are `sha1_fingerprint` and `package_name`.'
           )).AddToParser(parser)


def _annotations(parser):
  base.Argument(
      '--annotations',
      metavar='KEY=VALUE',
      type=arg_parsers.ArgDict(),
      required=False,
      help=("""Annotations are key resource. Specify annotations as
            a key-value dictionary for small amounts of arbitrary client data.
            """)).AddToParser(parser)


def _api_targets_arg(parser):
  base.Argument(
      '--api-target',
      type=arg_parsers.ArgDict(
          spec={
              'service': str,
              'methods': arg_parsers.ArgList(custom_delim_char=':'),
          },
          required_keys=['service'],
          min_length=1,
      ),
      metavar='service=SERVICE[,methods=METHOD:...]',
      action='append',
      help="""\
      Repeatable. Specify service and optionally one or multiple specific
      methods. Both fields are case insensitive.
      Methods can be specified as a colon-separated list (e.g.,
      `--api-target="service=foo.service.com,methods=method1:method2"`),
      or using `--flags-file` (see $ gcloud topic flags-file for details).
      """,
  ).AddToParser(parser)


def _key_string_flag(parser):
  base.Argument('--key-string', help='Key String of the key.').AddToParser(
      parser
  )


def _project_id_flag(parser):
  base.Argument(
      '--project',
      metavar='PROJECT_ID',
      help="""\
The Google Cloud project ID to use for this invocation. If
omitted, then the current project is assumed; the current project can
be listed using `gcloud config list --format='text(core.project)'`
and can be set using `gcloud config set project PROJECTID`.

`--project` and its fallback `{core_project}` property play two roles
in the invocation. It specifies the project of the resource to
operate on. It also specifies the project for API enablement check,
quota, and billing. To specify a different project for quota and
billing, use `--billing-project` or `{billing_project}` property.
    """.format(
          core_project=properties.VALUES.core.project,
          billing_project=properties.VALUES.billing.quota_project,
      ),
  ).AddToParser(parser)


def _folder_id_flag(parser):
  base.Argument(
      '--folder',
      metavar='FOLDER_ID',
      help='The Google Cloud Platform folder ID to use for this invocation.',
  ).AddToParser(parser)


def _orgnaization_id_flag(parser):
  base.Argument(
      '--organization',
      metavar='ORGANIZATION_ID',
      help=(
          'The Google Cloud Platform organization ID to use for this'
          ' invocation.'
      ),
  ).AddToParser(parser)
