# -*- 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 __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

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',
}


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):
  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):
  """Add create labels flags."""
  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 GetMonitoredProjectIDNumberFlag(verb):
  """Flag for managing a monitored project."""
  return base.Argument(
      'monitored_project',
      metavar='MONITORED_PROJECT_ID_OR_NUMBER',
      completer=completers.MonitoredProjectCompleter,
      help='Monitored project ID or number for the 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.'.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 start 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')
  AddStartTimeFlag(snooze_settings_group, resource='Snooze')
  AddEndTimeFlag(snooze_settings_group, resource='Snooze')


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.',
      )
