# -*- 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.
"""Update a new certificate template."""


from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.privateca import exceptions as privateca_exceptions
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import log


@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
  r"""Update a certificate template."""

  detailed_help = {
      'DESCRIPTION': """Update a certificate template.""",
      'EXAMPLES': """\
        To update a template named "dns-restricted" with new default x509 extensions:

          $ {command} dns-restricted --location=us-west1 --predefined-values-file=x509_parameters.yaml

        To update a template named "dns-restricted" to allow requestors to specify subject:

          $ {command} dns-restricted --location=us-west1 --copy-subject

        To update a template named "dns-restricted" with allowed extension
        'base-key-usage' to allow requestors to specify additional x509 extension 'extended-key-usage':

          $ {command} dns-restricted --location=us-west1 --copy-known-extensions=base-key-usage,extended-key-usage

        To update a template named "mtls-restricted" with allowed OID
        '1.1' to allow requestors to specify alternative OIDS '2.2,3.3':

          $ {command} mtls-restricted --location=us-west1 --copy-extensions-by-oid=2.2,3.3
       """,
  }

  def _UpdateCertificateTemplateFromArgs(self, args, current_labels):
    """Creates a Certificate template object and update mask from Certificate template update flags.

    Requires that args has 'description', 'copy-sans', 'copy-subject',
    'predefined-values-file', 'copy-known-extensions', 'copy-extensions-by-oid',
    and update labels flags registered.

    Args:
      args: The parser that contains the flag values.
      current_labels: The current set of labels for the Certificate Template.

    Returns:
      A tuple with the Certificate template object to update with and the list
      of
      strings representing the update mask, respectively.
    """
    messages = privateca_base.GetMessagesModule('v1')
    template_to_update = messages.CertificateTemplate()
    update_mask = []

    # We'll parse the identity constraints if any of the flags are specified,
    # but only include the paths in the update masks of the flags that were
    # explicitly specified.
    if (
        args.IsSpecified('copy_sans')
        or args.IsSpecified('copy_subject')
        or args.IsSpecified('identity_cel_expression')
    ):
      template_to_update.identityConstraints = flags.ParseIdentityConstraints(
          args
      )
      if args.IsSpecified('copy_sans'):
        update_mask.append(
            'identity_constraints.allow_subject_alt_names_passthrough'
        )
      if args.IsSpecified('copy_subject'):
        update_mask.append('identity_constraints.allow_subject_passthrough')
      if args.IsSpecified('identity_cel_expression'):
        update_mask.append('identity_constraints.cel_expression')

    if args.IsSpecified('predefined_values_file'):
      template_to_update.predefinedValues = flags.ParsePredefinedValues(args)
      update_mask.append('predefined_values')

    if args.IsSpecified('description'):
      template_to_update.description = args.description
      update_mask.append('description')

    known_exts_flags = args.IsSpecified(
        'copy_known_extensions'
    ) or args.IsSpecified('drop_known_extensions')
    oid_exts_flags = args.IsSpecified(
        'copy_extensions_by_oid'
    ) or args.IsSpecified('drop_oid_extensions')
    if known_exts_flags or oid_exts_flags:
      # Parse all extension flags into a CertificateExtensionConstraints
      # message.
      template_to_update.passthroughExtensions = (
          flags.ParseExtensionConstraints(args)
      )
      if known_exts_flags:
        update_mask.append('passthrough_extensions.known_extensions')
      if oid_exts_flags:
        update_mask.append('passthrough_extensions.additional_extensions')

    labels_diff = labels_util.Diff.FromUpdateArgs(args)
    labels_update = labels_diff.Apply(
        messages.CaPool.LabelsValue, current_labels
    )
    if labels_update.needs_update:
      template_to_update.labels = labels_update.labels
      update_mask.append('labels')

    if not update_mask:
      raise privateca_exceptions.NoUpdateException(
          'No updates found for the requested certificate template.'
      )

    return template_to_update, update_mask

  @staticmethod
  def Args(parser):
    resource_args.AddCertificateTemplatePositionalResourceArg(
        parser, 'to update'
    )
    base.Argument(
        '--description', help='A text description for the Certificate Template.'
    ).AddToParser(parser)
    flags.AddPredefinedValuesFileFlag(parser)
    flags.AddIdentityConstraintsFlags(parser, require_passthrough_flags=False)
    flags.AddExtensionConstraintsFlagsForUpdate(parser)
    labels_util.AddUpdateLabelsFlags(parser)

  def Run(self, args):
    client = privateca_base.GetClientInstance('v1')
    messages = privateca_base.GetMessagesModule('v1')

    cert_template_ref = args.CONCEPTS.certificate_template.Parse()
    template_name = cert_template_ref.RelativeName()

    current_cert_template = client.projects_locations_certificateTemplates.Get(
        messages.PrivatecaProjectsLocationsCertificateTemplatesGetRequest(
            name=template_name
        )
    )

    cert_template_to_update, update_mask = (
        self._UpdateCertificateTemplateFromArgs(
            args, current_cert_template.labels
        )
    )

    # Confirm that the result of this update is intended to be identity
    # reflection, if applicable.
    flags.ValidateIdentityConstraints(
        args,
        existing_copy_subj=current_cert_template.identityConstraints.allowSubjectPassthrough,
        existing_copy_sans=current_cert_template.identityConstraints.allowSubjectAltNamesPassthrough,
        for_update=True,
    )

    operation = client.projects_locations_certificateTemplates.Patch(
        messages.PrivatecaProjectsLocationsCertificateTemplatesPatchRequest(
            name=template_name,
            certificateTemplate=cert_template_to_update,
            updateMask=','.join(update_mask),
            requestId=request_utils.GenerateRequestId(),
        )
    )

    cert_template_response = operations.Await(
        operation, 'Updating Certificate Template.', api_version='v1'
    )
    cert_template = operations.GetMessageFromResponse(
        cert_template_response, messages.CertificateTemplate
    )

    log.status.Print(
        'Updated Certificate Template [{}].'.format(cert_template.name)
    )
