# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Shared resource flags for Cloud Monitoring commands."""


from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.monitoring import completers
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.command_lib.util.args import repeated
from googlecloudsdk.core.util import times


COMPARISON_TO_ENUM = {
    '>': 'COMPARISON_GT',
    '<': 'COMPARISON_LT',
    '>=': 'COMPARISON_GE',
    '<=': 'COMPARISON_LE',
    '==': 'COMPARISON_EQ',
    '=': 'COMPARISON_EQ',
    '!=': 'COMPARISON_NE',
}

UPTIME_MONITORED_RESOURCES = {
    'uptime-url': 'Uptime check against a URL.',
    'gce-instance': 'Uptime check against a Compute Engine instance.',
    'gae-app': 'Uptime check against an App Engine module.',
    'aws-ec2-instance': 'Uptime check against an AWS EC2 instance.',
    'aws-elb-load-balancer': 'Uptime check against an ElasticLoadBalancer.',
    'servicedirectory-service': (
        'Uptime check against a Service Directory service.'
    ),
    'cloud-run-revision': 'Uptime check against a Cloud Run revision.',
}

UPTIME_GROUP_RESOURCES = {
    'gce-instance': (
        'Uptime check against a group of instances from Google Cloud'
        ' or Amazon Web Services.'
    ),
    'aws-elb-load-balancer': (
        'Uptime check against a group of Amazon ELB load balancers.'
    ),
}

UPTIME_PROTOCOLS = {
    'http': 'An HTTP check.',
    'https': 'An HTTPS check.',
    'tcp': 'A TCP check.',
}

UPTIME_REQUEST_METHODS = {
    'get': 'HTTP GET method',
    'post': 'HTTP POST method',
}

UPTIME_CONTENT_TYPES = {
    'unspecified': 'Not specified',
    'url-encoded': 'URL encoded',
    'user-provided': 'User provided',
}

UPTIME_STATUS_CLASSES = {
    '1xx': 'Any response code from 100-199 inclusive',
    '2xx': 'Any response code from 200-299 inclusive',
    '3xx': 'Any response code from 300-399 inclusive',
    '4xx': 'Any response code from 400-499 inclusive',
    '5xx': 'Any response code from 500-599 inclusive',
    'any': 'Any response code',
}

UPTIME_PERIODS = {
    '1': 'One minute',
    '5': 'Five minutes',
    '10': 'Ten minutes',
    '15': 'Fifteen minutes',
}

UPTIME_REGIONS = {
    'usa-oregon': 'us-west1',
    'usa-virginia': 'us-east4',
    'usa-iowa': 'us-central1',
    'europe': 'europe-west1',
    'south-america': 'southamerica-east1',
    'asia-pacific': 'asia-southeast1',
}

UPTIME_MATCHER_TYPES = {
    'contains-string': 'Response contains string',
    'not-contains-string': 'Response does not contain string',
    'matches-regex': 'Response matches regex',
    'not-matches-regex': 'Response does not match regex',
    'matches-json-path': 'Response matches at JSONPath',
    'not-matches-json-path': 'Response does not match at JSONPath',
}

UPTIME_JSON_MATCHER_TYPES = {
    'exact-match': 'Response matches exact string at JSONPath',
    'regex-match': 'Response matches regex at JSONPath',
}

UPTIME_SERVICE_AGENT_TYPES = {
    'oidc-token': 'OIDC Token authentication',
}


def AddFileMessageFlag(parser, resource, flag=None):
  """Adds flags for specifying a message as a file to the parser."""
  parser.add_argument(
      '--{}-from-file'.format(flag or resource),
      type=arg_parsers.FileContents(),
      help='The path to a JSON or YAML file containing the {}.'.format(
          resource))


def AddMessageFlags(parser, resource, flag=None):
  """Adds flags for specifying a message as a string/file to the parser."""
  message_group = parser.add_group(mutex=True)
  message_group.add_argument(
      '--{}'.format(flag or resource),
      help='The {} as a string. In either JSON or YAML format.'.format(
          resource))
  message_group.add_argument(
      '--{}-from-file'.format(flag or resource),
      type=arg_parsers.FileContents(),
      help='The path to a JSON or YAML file containing the {}.'.format(
          resource))


def AddDisplayNameFlag(parser, resource, positional=False):
  if positional:
    base.Argument(
        'display_name',
        metavar='DISPLAY_NAME',
        help='Display name for the uptime check or synthetic monitor.',
    ).AddToParser(parser)
  else:
    parser.add_argument(
        '--display-name', help='The display name for the {}.'.format(resource)
    )


def AddCombinerFlag(parser, resource):
  """Adds flags for specifying a combiner, which defines how to combine the results of multiple conditions."""
  parser.add_argument(
      '--combiner',
      choices={
          'COMBINE_UNSPECIFIED': 'An unspecified combiner',
          'AND': 'An incident is created only if '
                 'all conditions are met simultaneously. '
                 'This combiner is satisfied if all conditions are met, '
                 'even if they are met on completely different resources.',
          'OR': 'An incident is created if '
                'any of the listed conditions is met.',
          'AND_WITH_MATCHING_RESOURCE': 'Combine conditions using '
                                        'logical AND operator, '
                                        'but unlike the regular AND option, '
                                        'an incident is created '
                                        'only if all conditions '
                                        'are met simultaneously '
                                        'on at least one resource.',
      },
      help='The combiner for the {}.'.format(resource))


def AddPolicySettingsFlags(parser, update=False):
  """Adds policy settings flags to the parser."""
  policy_settings_group = parser.add_group(help="""\
      Policy Settings.
      If any of these are specified, they will overwrite fields in the
      `--policy` or `--policy-from-file` flags if specified.""")
  AddDisplayNameFlag(policy_settings_group, resource='Alert Policy')
  AddCombinerFlag(policy_settings_group, resource='Alert Policy')
  enabled_kwargs = {
      'action': arg_parsers.StoreTrueFalseAction if update else 'store_true'
  }
  if not update:
    # Can't specify default if using StoreTrueFalseAction.
    enabled_kwargs['default'] = True
  policy_settings_group.add_argument(
      '--enabled', help='If the policy is enabled.', **enabled_kwargs)

  documentation_group = policy_settings_group.add_group(help='Documentation')
  documentation_group.add_argument(
      '--documentation-format',
      default='text/markdown' if not update else None,
      help='The MIME type that should be used with `--documentation` or '
           '`--documentation-from-file`. Currently, only "text/markdown" is '
           'supported.')
  documentation_string_group = documentation_group.add_group(mutex=True)
  documentation_string_group.add_argument(
      '--documentation',
      help='The documentation to be included with the policy.')
  documentation_string_group.add_argument(
      '--documentation-from-file',
      type=arg_parsers.FileContents(),
      help='The path to a file containing the documentation to be included '
           'with the policy.')
  if update:
    repeated.AddPrimitiveArgs(
        policy_settings_group,
        'Alert Policy',
        'notification-channels',
        'Notification Channels')
    AddUpdateLabelsFlags(
        'user-labels', policy_settings_group, group_text='User Labels')
  else:
    AddCreateLabelsFlag(policy_settings_group, 'user-labels', 'policy')


def AddFieldsFlagsWithMutuallyExclusiveSettings(parser,
                                                fields_help,
                                                add_settings_func,
                                                fields_choices=None,
                                                **kwargs):
  """Adds fields flags with mutually excludisve settings."""
  update_group = parser.add_group(mutex=True)
  update_group.add_argument(
      '--fields',
      metavar='field',
      type=arg_parsers.ArgList(choices=fields_choices),
      help=fields_help)
  add_settings_func(update_group, **kwargs)


def ValidateAlertPolicyUpdateArgs(args):
  """Validate alert policy update args."""
  if args.fields and not (args.policy or args.policy_from_file):
    raise exceptions.OneOfArgumentsRequiredException(
        ['--policy', '--policy-from-file'],
        'If --fields is specified.')


def ComparisonValidator(if_value):
  """Validates and returns the comparator and value."""
  if if_value.lower() == 'absent':
    return (None, None)
  if len(if_value) < 2:
    raise exceptions.BadArgumentException('--if', 'Invalid value for flag.')
  comparator_part = if_value[0]
  threshold_part = if_value[1:]
  try:
    comparator = COMPARISON_TO_ENUM[comparator_part]
    threshold_value = float(threshold_part)

    # currently only < and > are supported
    if comparator not in ['COMPARISON_LT', 'COMPARISON_GT']:
      raise exceptions.BadArgumentException('--if',
                                            'Comparator must be < or >.')
    return comparator, threshold_value
  except KeyError:
    raise exceptions.BadArgumentException('--if',
                                          'Comparator must be < or >.')
  except ValueError:
    raise exceptions.BadArgumentException('--if',
                                          'Threshold not a value float.')


def AddConditionSettingsFlags(parser):
  """Adds policy condition flags to the parser."""
  condition_group = parser.add_group(help="""\
        Condition Settings.
        This will add a condition to the created policy. If any conditions are
        already specified, this condition will be appended.""")
  condition_group.add_argument(
      '--condition-display-name',
      help='The display name for the condition.')
  condition_group.add_argument(
      '--condition-filter',
      help='Specifies the "filter" in a metric absence or metric threshold '
           'condition.')
  condition_group.add_argument(
      '--aggregation',
      help='Specifies an Aggregation message as a JSON/YAML value to be '
           'applied to the condition. For more information about the format: '
           'https://cloud.google.com/monitoring/api/ref_v3/rest/v3/'
           'projects.alertPolicies')
  condition_group.add_argument(
      '--duration',
      type=arg_parsers.Duration(),
      help='The duration (e.g. "60s", "2min", etc.) that the condition '
           'must hold in order to trigger as true.')
  AddUpdateableConditionFlags(condition_group)


def AddUpdateableConditionFlags(parser):
  """Adds flags for condition settings that are updateable to the parser."""
  parser.add_argument(
      '--if',
      dest='if_value',  # To avoid specifying args.if.
      type=ComparisonValidator,
      help='One of "absent", "< THRESHOLD", "> THRESHOLD" where "THRESHOLD" is '
           'an integer or float.')
  trigger_group = parser.add_group(mutex=True)
  trigger_group.add_argument(
      '--trigger-count',
      type=int,
      help='The absolute number of time series that must fail the predicate '
           'for the condition to be triggered.')
  trigger_group.add_argument(
      '--trigger-percent',
      type=float,
      help='The percentage of time series that must fail the predicate for '
           'the condition to be triggered.')


def ValidateNotificationChannelUpdateArgs(args):
  """Validate notification channel update args."""
  if (args.fields and
      not (args.channel_content or args.channel_content_from_file)):
    raise exceptions.OneOfArgumentsRequiredException(
        ['--channel-content', '--channel-content-from-file'],
        'If --fields is specified.')


def AddNotificationChannelSettingFlags(parser, update=False):
  """Adds flags for channel settings to the parser."""
  channel_group = parser.add_group(help='Notification channel settings')
  AddDisplayNameFlag(channel_group, 'channel')
  channel_group.add_argument(
      '--description',
      help='An optional description for the channel.')
  channel_group.add_argument(
      '--type',
      help='The type of the notification channel. This field matches the '
           'value of the NotificationChannelDescriptor type field.')

  enabled_kwargs = {
      'action': arg_parsers.StoreTrueFalseAction if update else 'store_true'
  }
  if not update:
    # Can't specify default if using StoreTrueFalseAction.
    enabled_kwargs['default'] = True
  channel_group.add_argument(
      '--enabled',
      help='Whether notifications are forwarded to the described channel.',
      **enabled_kwargs)
  if update:
    AddUpdateLabelsFlags(
        'user-labels', channel_group, group_text='User Labels')
    AddUpdateLabelsFlags(
        'channel-labels', channel_group, validate_values=False,
        group_text='Configuration Fields: Key-Value pairs that define the '
                   'channel and its behavior.')
  else:
    AddCreateLabelsFlag(channel_group, 'user-labels', 'channel')
    AddCreateLabelsFlag(
        channel_group, 'channel-labels', 'channel', validate_values=False,
        extra_message='These are configuration fields that define the channel '
                      'and its behavior.')


def AddCreateLabelsFlag(parser, labels_name, resource_name, extra_message='',
                        validate_values=True, skip_extra_message=False):
  """Add create labels flags."""
  if not skip_extra_message:
    extra_message += ('If the {0} was given as a JSON/YAML object from a '
                      'string or file, this flag will replace the labels value '
                      'in the given {0}.'.format(resource_name))
  labels_util.GetCreateLabelsFlag(
      extra_message=extra_message,
      labels_name=labels_name,
      validate_values=validate_values).AddToParser(parser)


def AddUpdateLabelsFlags(labels_name, parser, group_text='',
                         validate_values=True):
  """Add update labels flags."""
  labels_group = parser.add_group(group_text)
  labels_util.GetUpdateLabelsFlag(
      '', labels_name=labels_name,
      validate_values=validate_values).AddToParser(labels_group)
  remove_group = labels_group.add_group(mutex=True)
  labels_util.GetRemoveLabelsFlag(
      '', labels_name=labels_name).AddToParser(remove_group)
  labels_util.GetClearLabelsFlag(
      labels_name=labels_name).AddToParser(remove_group)


def GetMonitoredResourceContainerNameFlag(verb):
  """Flag for managing a monitored resource container."""
  return base.Argument(
      'monitored_resource_container_name',
      metavar='MONITORED_RESOURCE_CONTAINER_NAME',
      completer=completers.MonitoredResourceContainerCompleter,
      help=(
          'Monitored resource container (example - projects/PROJECT_ID) project'
          ' you want to {0}.'.format(verb)
      ),
  )


def AddCriteriaPoliciesFlag(parser, resource):
  parser.add_argument(
      '--criteria-policies',
      metavar='CRITERIA_POLICIES',
      type=arg_parsers.ArgList(min_length=1, max_length=16),
      help=(
          'The policies that the {} applies to. Exactly 1 alert policy is'
          ' required if `criteria-filter` is specified at the same time.'
          .format(resource)
      ),
  )


def AddCriteriaFilterFlag(parser, resource):
  parser.add_argument(
      '--criteria-filter',
      metavar='CRITERIA_FILTER',
      type=str,
      help=(
          'Optional. When you define a {}, you can also define a filter for'
          ' that snooze. The filter is a string containing one or more'
          ' key-value pairs. The string uses the standard'
          ' https://google.aip.dev/160 filter syntax. If you define a filter'
          ' for a snooze, then the snooze can only apply to one alert policy.'
          " When the snooze is active, incidents won't be created when the"
          ' incident would have key-value pairs (labels) that match those'
          ' specified by the filter in the snooze. Snooze filters support'
          ' resource, metric, and metadata labels. If multiple labels are used,'
          ' then they must be connected with an AND operator. For example:'
          ' resource.labels.instance_id="1234567890" AND'
          ' resource.labels.zone="us-central1-a" AND'
          ' metric.labels.instance_name="test_group" AND'
          ' metadata.user_labels.foo="bar" AND'
          ' metadata.system_labels.region="us-central1"'.format(resource)
      ),
  )


def AddStartTimeFlag(parser, resource):
  parser.add_argument(
      '--start-time',
      type=arg_parsers.Datetime.Parse,
      help='The start time for the {}.'.format(resource))


def AddEndTimeFlag(parser, resource):
  parser.add_argument(
      '--end-time',
      type=arg_parsers.Datetime.Parse,
      help='The end time for the {}.'.format(resource))


def AddSnoozeSettingsFlags(parser, update=False):
  """Adds snooze settings flags to the parser."""
  snooze_settings_group = parser.add_group(help="""\
      Snooze Settings.
      If any of these are specified, they will overwrite fields in the
      `--snooze-from-file` flags if specified.""")
  AddDisplayNameFlag(snooze_settings_group, resource='Snooze')
  if not update:
    AddCriteriaPoliciesFlag(snooze_settings_group, resource='Snooze')
    AddCriteriaFilterFlag(snooze_settings_group, resource='Snooze')
  AddStartTimeFlag(snooze_settings_group, resource='Snooze')
  AddEndTimeFlag(snooze_settings_group, resource='Snooze')


def AddUptimeSettingsFlags(parser, update=False):
  """Adds uptime check settings flags to the parser."""
  if not update:
    AddUptimeResourceFlags(parser)

  AddUptimeProtocolFlags(parser, update)
  AddUptimeRunFlags(parser, update)
  AddUptimeMatcherFlags(parser)


def AddUptimeResourceFlags(parser):
  """Adds uptime check resource settings flags to the parser."""
  uptime_resource_group = parser.add_group(
      help='Uptime check resource.',
      mutex=True,
      required=True,
  )
  monitored_resource_group = uptime_resource_group.add_group(
      help='Monitored resource'
  )
  monitored_resource_group.add_argument(
      '--resource-type',
      help='Type of monitored resource, defaults to `uptime-url`.',
      choices=UPTIME_MONITORED_RESOURCES,
  )
  base.Argument(
      '--resource-labels',
      metavar='KEY=VALUE',
      type=arg_parsers.ArgDict(key_type=str, value_type=str),
      action=arg_parsers.UpdateAction,
      required=True,
      help=(
          """Values for all of the labels listed in the associated monitored resource descriptor.
            See https://cloud.google.com/monitoring/api/resources for more information and allowed
            keys."""
      ),
  ).AddToParser(monitored_resource_group)
  group_resource_group = uptime_resource_group.add_group(
      help='Monitored resource group'
  )
  group_resource_group.add_argument(
      '--group-type',
      help=(
          'The resource type of the group members, defaults to `gce-instance`.'
      ),
      choices=UPTIME_GROUP_RESOURCES,
  )
  group_resource_group.add_argument(
      '--group-id',
      help='The group of resources being monitored.',
      required=True,
      type=str,
  )
  uptime_resource_group.add_argument(
      '--synthetic-target',
      help="""The target of the Synthetic Monitor.
        This is the fully qualified GCFv2 resource name.
        """,
      type=str,
  )


def AddUptimeProtocolFlags(parser, update=False):
  """Adds uptime check protocol settings flags to the parser."""
  uptime_protocol_group = parser.add_group(
      help='Uptime check protocol settings.'
  )
  if not update:
    uptime_protocol_group.add_argument(
        '--protocol',
        help='The protocol of the request, defaults to `http`.',
        choices=UPTIME_PROTOCOLS,
    )
  uptime_protocol_group.add_argument(
      '--port',
      help="""The port on the server against which to run the check.
        Defaults to `80` when `--protocol` is `http`.
        Defaults to `443` when `--protocol` is `https`.
        Required if `--protocol` is `tcp`.""",
      type=arg_parsers.BoundedInt(lower_bound=1, upper_bound=65535),
  )
  uptime_protocol_group.add_argument(
      '--pings-count',
      help='Number of ICMP pings to send alongside the request.',
      type=arg_parsers.BoundedInt(lower_bound=1, upper_bound=3),
  )
  uptime_protocol_group.add_argument(
      '--request-method',
      help="""The HTTP request method to use, defaults to `get`.
        Can only be set if `--protocol` is `http` or `https`.""",
      choices=UPTIME_REQUEST_METHODS,
  )
  uptime_protocol_group.add_argument(
      '--path',
      help="""The path to the page against which to run the check, defaults to `/`.
        Can only be set if `--protocol` is `http` or `https`.""",
      type=str,
  )
  uptime_protocol_group.add_argument(
      '--username',
      help="""The username to use when authenticating with the HTTP server.
        Can only be set if `--protocol` is `http` or `https`.""",
      type=str,
  )
  uptime_protocol_group.add_argument(
      '--password',
      help="""The password to use when authenticating with the HTTP server.
        Can only be set if `--protocol` is `http` or `https`.""",
      type=str,
  )
  uptime_protocol_group.add_argument(
      '--mask-headers',
      help="""Whether to encrypt the header information, defaults to `false`.
        Can only be set if `--protocol` is `http` or `https`.""",
      type=bool,
  )
  uptime_service_agent_auth_group = uptime_protocol_group.add_group(
      help='Uptime check service agent authorization.'
  )
  uptime_service_agent_auth_group.add_argument(
      '--service-agent-auth',
      help="""The type of authentication to use for the HTTP request.
        Can only be set if `--protocol` is `https`.""",
      choices=UPTIME_SERVICE_AGENT_TYPES)
  if update:
    uptime_headers_group = uptime_protocol_group.add_group(
        help='Uptime check headers.'
    )
    base.Argument(
        '--update-headers',
        metavar='KEY=VALUE',
        type=arg_parsers.ArgDict(key_type=str, value_type=str),
        action=arg_parsers.UpdateAction,
        help=("""The list of headers to add to the uptime check. Any existing
              headers with matching "key" are overridden by the provided
              values."""),
    ).AddToParser(uptime_headers_group)
    uptime_remove_header_group = uptime_headers_group.add_group(
        help='Uptime check remove headers.',
        mutex=True,
    )
    uptime_remove_header_group.add_argument(
        '--remove-headers',
        metavar='KEY',
        help="""The list of header keys to remove from the uptime check.""",
        type=arg_parsers.ArgList(str),
    )
    uptime_remove_header_group.add_argument(
        '--clear-headers',
        help="""Clear all headers on the uptime check.""",
        type=bool,
    )
  else:
    base.Argument(
        '--headers',
        metavar='KEY=VALUE',
        type=arg_parsers.ArgDict(key_type=str, value_type=str),
        action=arg_parsers.UpdateAction,
        help=(
            """The list of headers to send as part of the uptime check
              request. Can only be set if `--protocol` is `http` or `https`."""
        ),
    ).AddToParser(uptime_protocol_group)
  uptime_protocol_group.add_argument(
      '--content-type',
      help="""The content type header to use for the check, defaults to `unspecified`.
        Can only be set if `--protocol` is `http` or `https`.""",
      choices=UPTIME_CONTENT_TYPES,
  )
  uptime_protocol_group.add_argument(
      '--custom-content-type',
      help="""A user-provided content type header to use for the check.
        Can only be set if `--protocol` is `http` or `https`.""",
      type=str,
  )
  uptime_protocol_group.add_argument(
      '--validate-ssl',
      help="""Whether to include SSL certificate validation as a part of the uptime check,
        defaults to `false`.
        Can only be set if `--protocol` is `http` or `https`.""",
      type=bool,
  )
  uptime_protocol_group.add_argument(
      '--body',
      help="""The request body associated with the HTTP POST request.
        Can only be set if `--protocol` is `http` or `https`.""",
      type=str,
  )
  uptime_status_group = uptime_protocol_group.add_group(
      help='Uptime check status.',
      mutex=True,
  )
  if update:
    uptime_status_classes_group = uptime_status_group.add_group(
        help='Uptime check status classes.',
        mutex=True,
    )
    uptime_status_classes_group.add_argument(
        '--set-status-classes',
        metavar='status-class',
        help="""List of HTTP status classes. The uptime check will only pass if the response
                code is contained in this list.""",
        type=arg_parsers.ArgList(choices=UPTIME_STATUS_CLASSES),
    )
    uptime_status_classes_group.add_argument(
        '--add-status-classes',
        metavar='status-class',
        help="""The list of HTTP status classes to add to the uptime check.""",
        type=arg_parsers.ArgList(choices=UPTIME_STATUS_CLASSES),
    )
    uptime_status_classes_group.add_argument(
        '--remove-status-classes',
        metavar='status-class',
        help="""The list of HTTP status classes to remove from the uptime check.""",
        type=arg_parsers.ArgList(choices=UPTIME_STATUS_CLASSES),
    )
    uptime_status_classes_group.add_argument(
        '--clear-status-classes',
        help="""Clear all HTTP status classes on the uptime check. Setting this
            flag is the same as selecting only the `2xx` status class.""",
        type=bool,
    )
    uptime_status_codes_group = uptime_status_group.add_group(
        help='Uptime check status codes.',
        mutex=True,
    )
    uptime_status_codes_group.add_argument(
        '--set-status-codes',
        metavar='status-code',
        help="""List of HTTP status codes. The uptime check will only pass if the response
                code is present in this list.""",
        type=arg_parsers.ArgList(int),
    )
    uptime_status_codes_group.add_argument(
        '--add-status-codes',
        metavar='status-code',
        help="""The list of HTTP status codes to add to the uptime check.""",
        type=arg_parsers.ArgList(int),
    )
    uptime_status_codes_group.add_argument(
        '--remove-status-codes',
        metavar='status-code',
        help="""The list of HTTP status codes to remove from the uptime check.""",
        type=arg_parsers.ArgList(int),
    )
    uptime_status_codes_group.add_argument(
        '--clear-status-codes',
        help="""Clear all HTTP status codes on the uptime check. Setting this
            flag is the same as selecting only the `2xx` status class.""",
        type=bool,
    )
  else:
    uptime_status_group.add_argument(
        '--status-classes',
        metavar='status-class',
        help="""List of HTTP status classes. The uptime check only passes when the response
              code is contained in this list. Defaults to `2xx`.
              Can only be set if `--protocol` is `http` or `https`.""",
        type=arg_parsers.ArgList(choices=UPTIME_STATUS_CLASSES),
    )
    uptime_status_group.add_argument(
        '--status-codes',
        metavar='status-code',
        help="""List of HTTP Status Codes. The uptime check will only pass if the response code
              is present in this list.
              Can only be set if `--protocol` is `http` or `https`.""",
        type=arg_parsers.ArgList(int),
    )


def AddUptimeRunFlags(parser, update=False):
  """Adds uptime check run flags to the parser."""
  uptime_settings_group = parser.add_group(help='Settings.')
  uptime_settings_group.add_argument(
      '--period',
      help='''The time between uptime check or synthetic monitor executions in
              minutes, defaults to `1`. Can be set for synthetic monitors.''',
      choices=UPTIME_PERIODS,
  )
  uptime_settings_group.add_argument(
      '--timeout',
      help=(
          'The maximum amount of time in seconds to wait for the request to'
          ' complete, defaults to `60`. Can be set for synthetic monitors.'
      ),
      type=arg_parsers.BoundedInt(lower_bound=1, upper_bound=60),
  )
  if update:
    AddDisplayNameFlag(
        uptime_settings_group,
        resource='uptime check or synthetic monitor',
        positional=False,
    )
    uptime_regions_group = uptime_settings_group.add_group(
        help="""Uptime check selected regions.""",
        mutex=True,
    )
    uptime_regions_group.add_argument(
        '--set-regions',
        metavar='region',
        help="""The list of regions from which the check is run. At least 3 regions must be
            selected.""",
        type=arg_parsers.ArgList(choices=UPTIME_REGIONS),
    )
    uptime_regions_group.add_argument(
        '--add-regions',
        metavar='region',
        help="""The list of regions to add to the uptime check.""",
        type=arg_parsers.ArgList(choices=UPTIME_REGIONS),
    )
    uptime_regions_group.add_argument(
        '--remove-regions',
        metavar='region',
        help="""The list of regions to remove from the uptime check.""",
        type=arg_parsers.ArgList(choices=UPTIME_REGIONS),
    )
    uptime_regions_group.add_argument(
        '--clear-regions',
        help="""Clear all regions on the uptime check. This setting acts the same as if all available
            regions were selected.""",
        type=bool,
    )
  else:
    uptime_settings_group.add_argument(
        '--regions',
        metavar='field',
        help="""The list of regions from which the check is run. At least 3 regions must be selected.
            Defaults to all available regions.""",
        type=arg_parsers.ArgList(choices=UPTIME_REGIONS),
    )
  if update:
    AddUpdateLabelsFlags(
        'user-labels',
        uptime_settings_group,
        'User labels. Can be set for synthetic monitors.',
    )
  else:
    AddCreateLabelsFlag(
        uptime_settings_group,
        'user-labels',
        'User labels. Can be set for synthetic monitors.',
        skip_extra_message=True,
    )


def AddUptimeMatcherFlags(parser):
  """Adds uptime check matcher flags to the parser."""
  uptime_matcher_group = parser.add_group(help='Uptime check matcher settings.')
  uptime_matcher_group.add_argument(
      '--matcher-content',
      required=True,
      type=str,
      help='String, regex or JSON content to match.',
  )
  uptime_matcher_group.add_argument(
      '--matcher-type',
      choices=UPTIME_MATCHER_TYPES,
      help="""The type of content matcher that is applied to the server output, defaults to
        `contains-string`.""",
  )
  uptime_json_matcher_group = uptime_matcher_group.add_group(
      help='Uptime check matcher settings for JSON responses.'
  )
  uptime_json_matcher_group.add_argument(
      '--json-path',
      type=str,
      required=True,
      help="""JSONPath within the response output pointing to the expected content to match.
            Only used if `--matcher-type` is `matches-json-path` or `not-matches-json-path`.""",
  )
  uptime_json_matcher_group.add_argument(
      '--json-path-matcher-type',
      choices=UPTIME_JSON_MATCHER_TYPES,
      help="""The type of JSONPath match that is applied to the JSON output, defaults to
            `exact-match`.
            Only used if `--matcher-type` is `matches-json-path` or `not-matches-json-path`.""",
  )


def ValidateSnoozeUpdateArgs(args):
  """Validate snooze update args."""
  if args.fields and not args.snooze_from_file:
    raise exceptions.OneOfArgumentsRequiredException(
        ['--snooze-from-file'], 'If --fields is specified.'
    )


def ValidateSnoozeInterval(
    snooze,
    display_name=None,
    start_time=None,
    end_time=None,
):
  """Validate snooze reference interval."""
  snooze_past = False
  end_time_ref = times.ParseDateTime(snooze.interval.endTime)
  if end_time_ref < times.Now():
    snooze_past = True

  if snooze_past:
    if display_name is not None:
      raise exceptions.InvalidArgumentException(
          '--display-name',
          'Expired snoozes can no longer be updated.',
      )
    elif start_time is not None:
      raise exceptions.InvalidArgumentException(
          '--start-time',
          'Expired snoozes can no longer be updated.',
      )
    elif end_time is not None:
      raise exceptions.InvalidArgumentException(
          '--end-time',
          'Expired snoozes can no longer be updated.',
      )


def AddMigrateFlags(parser):
  """Adds migrate flags to the parser."""
  migrate_group = parser.add_group()
  migrate_group.add_argument(
      '--policies-from-prometheus-alert-rules-yaml',
      metavar='PROMETHEUS_ALERT_RULE_FILE_PATHS',
      type=arg_parsers.ArgList(arg_parsers.FileContents()),
      help=(
          'One or more Prometheus alert rule YAML files (separated by commas if'
          ' multiple) to be converted to Cloud Alerting Policies. Example: '
          '--policies-from-prometheus-alert-rules-yaml=rules_1.yaml,rules_2.yaml'
      ),
  )
  migrate_group.add_argument(
      '--channels-from-prometheus-alertmanager-yaml',
      metavar='PROMETHEUS_ALERT_MANAGER_FILE_PATH',
      type=arg_parsers.FileContents(),
      help=(
          'Prometheus alert manager YAML file to be converted to Cloud'
          ' Monitoring notification channels. Specifying this flag with the'
          ' --policies-from-prometheus-alert-rules-yaml flag puts the newly'
          " created notification channels into the translated Alert Policies'"
          ' definition.'
      ),
  )
