# -*- coding: utf-8 -*- #
# Copyright 2020 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 to update a trigger."""


from googlecloudsdk.api_lib.eventarc import triggers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.eventarc import flags
from googlecloudsdk.command_lib.eventarc import types
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import log

_DETAILED_HELP = {
    'DESCRIPTION':
        '{description}',
    'EXAMPLES':
        """ \
        To update the trigger ``my-trigger'' by setting its destination Cloud Run service to ``my-service'', run:

          $ {command} my-trigger --destination-run-service=my-service
        """,
}


@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Update(base.UpdateCommand):
  """Update an Eventarc trigger."""

  detailed_help = _DETAILED_HELP

  @classmethod
  def Args(cls, parser):
    flags.AddTriggerResourceArg(parser, 'The trigger to update.', required=True)
    flags.AddEventFiltersArg(parser, cls.ReleaseTrack())
    flags.AddEventFiltersPathPatternArg(parser, cls.ReleaseTrack())
    flags.AddEventDataContentTypeArg(parser, cls.ReleaseTrack())
    flags.AddUpdateDestinationArgs(parser, cls.ReleaseTrack())
    base.ASYNC_FLAG.AddToParser(parser)

    service_account_group = parser.add_mutually_exclusive_group()
    flags.AddServiceAccountArg(service_account_group)
    flags.AddClearServiceAccountArg(service_account_group)

    flags.AddTriggerRetryPolicyArgs(parser, with_clear=True)

    labels_util.AddUpdateLabelsFlags(parser)

  def Run(self, args):
    """Run the update command."""
    client = triggers.TriggersClientV1()
    trigger_ref = args.CONCEPTS.trigger.Parse()
    event_filters = flags.GetEventFiltersArg(args, self.ReleaseTrack())
    event_filters_path_pattern = flags.GetEventFiltersPathPatternArg(
        args, self.ReleaseTrack())
    event_data_content_type = flags.GetEventDataContentTypeArg(
        args, self.ReleaseTrack()
    )

    original_trigger = client.Get(trigger_ref)
    labels_update_result = labels_util.Diff.FromUpdateArgs(args).Apply(
        client.LabelsValueClass(), original_trigger.labels
    )

    update_mask = client.BuildUpdateMask(
        event_filters=event_filters is not None,
        event_filters_path_pattern=event_filters_path_pattern is not None,
        event_data_content_type=event_data_content_type is not None,
        service_account=args.IsSpecified('service_account')
        or args.clear_service_account,
        destination_run_service=args.IsSpecified('destination_run_service'),
        destination_run_job=args.IsSpecified('destination_run_job'),
        destination_run_path=args.IsSpecified('destination_run_path')
        or args.clear_destination_run_path,
        destination_run_region=args.IsSpecified('destination_run_region'),
        destination_gke_namespace=args.IsSpecified('destination_gke_namespace'),
        destination_gke_service=args.IsSpecified('destination_gke_service'),
        destination_gke_path=args.IsSpecified('destination_gke_path')
        or args.clear_destination_gke_path,
        destination_workflow=args.IsSpecified('destination_workflow'),
        destination_workflow_location=args.IsSpecified(
            'destination_workflow_location'
        ),
        destination_function=args.IsSpecified('destination_function'),
        destination_function_location=args.IsSpecified(
            'destination_function_location'
        ),
        max_retry_attempts=args.IsSpecified('max_retry_attempts')
        or args.clear_max_retry_attempts,
        labels=labels_update_result.needs_update,
    )
    # The type can't be updated, so it's safe to use the original trigger's
    # type.
    # In the async case, this is the only way to get the type.
    self._event_type = client.GetEventType(original_trigger)
    destination_message = None
    if (args.IsSpecified('destination_run_service') or
        args.IsSpecified('destination_run_job') or
        args.IsSpecified('destination_run_region') or
        args.IsSpecified('destination_run_path') or
        args.clear_destination_run_path):
      destination_message = client.BuildCloudRunDestinationMessage(
          args.destination_run_service, args.destination_run_job,
          args.destination_run_path, args.destination_run_region)
    elif (args.IsSpecified('destination_gke_namespace') or
          args.IsSpecified('destination_gke_service') or
          args.IsSpecified('destination_gke_path') or
          args.clear_destination_gke_path):
      destination_message = client.BuildGKEDestinationMessage(
          None, None, args.destination_gke_namespace,
          args.destination_gke_service, args.destination_gke_path)
    elif (args.IsSpecified('destination_workflow') or
          args.IsSpecified('destination_workflow_location')):
      location = self.GetWorkflowDestinationLocation(args, original_trigger)
      workflow = self.GetWorkflowDestination(args, original_trigger)
      destination_message = client.BuildWorkflowDestinationMessage(
          trigger_ref.Parent().Parent().Name(), workflow, location)
    elif (args.IsSpecified('destination_function') or
          args.IsSpecified('destination_function_location')):
      location = self.GetFunctionDestinationLocation(args, original_trigger)
      function = self.GetFunctionDestination(args, original_trigger)
      destination_message = client.BuildFunctionDestinationMessage(
          trigger_ref.Parent().Parent().Name(), function, location)
    trigger_message = client.BuildTriggerMessage(
        trigger_ref,
        event_filters,
        event_filters_path_pattern,
        event_data_content_type,
        args.service_account,
        destination_message,
        None,
        None,
        args.max_retry_attempts,
        labels_update_result.GetOrNone(),
    )
    operation = client.Patch(trigger_ref, trigger_message, update_mask)
    if args.async_:
      return operation
    return client.WaitFor(operation, 'Updating', trigger_ref)

  def Epilog(self, resources_were_displayed):
    if resources_were_displayed and types.IsAuditLogType(self._event_type):
      log.warning(
          'It may take up to {} minutes for the update to take full effect.'
          .format(triggers.MAX_ACTIVE_DELAY_MINUTES))

  def GetWorkflowDestinationLocation(self, args, old_trigger):
    if args.IsSpecified('destination_workflow_location'):
      return args.destination_workflow_location
    if old_trigger.destination.workflow:
      return old_trigger.destination.workflow.split('/')[3]
    raise exceptions.InvalidArgumentException(
        '--destination-workflow',
        'The specified trigger is not for a workflow destination.')

  def GetWorkflowDestination(self, args, old_trigger):
    if args.IsSpecified('destination_workflow'):
      return args.destination_workflow
    if old_trigger.destination.workflow:
      return old_trigger.destination.workflow.split('/')[5]
    raise exceptions.InvalidArgumentException(
        '--destination-workflow-location',
        'The specified trigger is not for a workflow destination.')

  def GetFunctionDestinationLocation(self, args, old_trigger):
    if args.IsSpecified('destination_function_location'):
      return args.destination_function_location
    if old_trigger.destination.cloudFunction:
      return old_trigger.destination.cloudFunction.split('/')[3]
    raise exceptions.InvalidArgumentException(
        '--destination-function',
        'The specified trigger is not for a function destination.')

  def GetFunctionDestination(self, args, old_trigger):
    if args.IsSpecified('destination_function'):
      return args.destination_function
    if old_trigger.destination.cloudFunction:
      return old_trigger.destination.cloudFunction.split('/')[5]
    raise exceptions.InvalidArgumentException(
        '--destination-function-location',
        'The specified trigger is not for a function destination.')
