# -*- coding: utf-8 -*- #
# Copyright 2016 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.

"""Flags and helpers for the compute disks commands."""


import argparse
from googlecloudsdk.calliope import actions
from googlecloudsdk.command_lib.compute import completers as compute_completers
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.kms import resource_args as kms_resource_args
from googlecloudsdk.command_lib.util import completers
from googlecloudsdk.core import properties

_DETAILED_SOURCE_SNAPSHOT_HELP = """\
      Source snapshot used to create the disks. It is safe to
      delete a snapshot after a disk has been created from the
      snapshot. In such cases, the disks will no longer reference
      the deleted snapshot. To get a list of snapshots in your
      current project, run `gcloud compute snapshots list`. A
      snapshot from an existing disk can be created using the
      `gcloud compute disks snapshot` command. This flag is mutually
      exclusive with *--image*.

      When using this option, the size of the disks must be at least
      as large as the snapshot size. Use *--size* to adjust the
      size of the disks.
"""

_DETAILED_SOURCE_INSTANT_SNAPSHOT_HELP = """\
      Name of the source instant snapshot used to create the disks.
"""

_DETAILED_SOURCE_INSTANT_SNAPSHOT_PROJECT_HELP = """\
      The project containing the instant snapshot used to create the disks.
"""

_SOURCE_DISK_DETAILED_HELP = """\
      Source disk used to create the disk(s). It is safe to
      delete a source disk after a disk has been created from the
      source disk. To get a list of disks in your current project,
      run `gcloud compute disks list`. This flag is mutually
      exclusive with *--image* and *--source-snapshot*.

      When using this option, the size of the disks must be at least
      as large as the source disk size. Use *--size* to adjust the
      size of the disks.

      The source disk must be in the same zone/region as the disk to be created.
"""

_SOURCE_DISK_ZONE_EXPLANATION = """\
      Zone of the source disk. This argument is not required if the target disk
      is in the same zone as the source disk.
"""

_SOURCE_DISK_REGION_EXPLANATION = """\
      Region of the source disk. This argument is not required if the target
      disk is in the same region as the source disk.
"""

_ASYNC_PRIMARY_DISK_HELP = """\
      Primary disk for asynchronous replication. This flag is required when
      creating a secondary disk.
"""

_ASYNC_PRIMARY_DISK_ZONE_EXPLANATION = """\
      Zone of the primary disk for asynchronous replication. The primary and
      secondary disks must not be in the same region.
"""

_ASYNC_PRIMARY_DISK_REGION_EXPLANATION = """\
      Region of the primary disk for asynchronous replication. The primary and
      secondary disks must not be in the same region.
"""

_ASYNC_SECONDARY_DISK_HELP = """\
      Secondary disk for asynchronous replication. This flag is required when
      starting replication.
"""

_ASYNC_SECONDARY_DISK_ZONE_EXPLANATION = """\
      Zone of the secondary disk for asynchronous replication.
"""

_ASYNC_SECONDARY_DISK_REGION_EXPLANATION = """\
      Region of the secondary disk for asynchronous replication.
"""

_ASYNC_SECONDARY_DISK_PROJECT_EXPLANATION = """\
      Project of the secondary disk for asynchronous replication.
"""

_ASYNC_PRIMARY_DISK_PROJECT_EXPLANATION = """\
      Project of the primary disk for asynchronous replication.
"""

DEFAULT_LIST_FORMAT = """\
    table(
      name,
      zone.basename(),
      sizeGb,
      type.basename(),
      status
    )"""


MULTISCOPE_LIST_FORMAT = """
    table(
      name,
      location(),
      location_scope(),
      sizeGb,
      type.basename(),
      status
      )"""


class SnapshotsCompleter(compute_completers.ListCommandCompleter):

  def __init__(self, **kwargs):
    super(SnapshotsCompleter, self).__init__(
        collection='compute.snapshots',
        list_command='compute snapshots list --uri',
        **kwargs)


class SnapshotGroupsCompleter(compute_completers.ListCommandCompleter):

  def __init__(self, **kwargs):
    super(SnapshotGroupsCompleter, self).__init__(
        collection='compute.snapshotGroups',
        list_command='beta compute snapshot-groups list --uri',
        api_version='beta',
        **kwargs)


class SnapshotsCompleterAlpha(completers.MultiResourceCompleter):

  def __init__(self, **kwargs):
    super(SnapshotsCompleterAlpha, self).__init__(
        completers=[RegionSnapshotsCompleter, GlobalSnapshotsCompleter],
        **kwargs
    )


class GlobalSnapshotsCompleter(compute_completers.ListCommandCompleter):

  def __init__(self, **kwargs):
    super(GlobalSnapshotsCompleter, self).__init__(
        collection='compute.snapshots',
        list_command='compute snapshots list --uri',
        api_version='alpha',
        **kwargs
    )


class RegionSnapshotsCompleter(compute_completers.ListCommandCompleter):

  def __init__(self, **kwargs):
    super(RegionSnapshotsCompleter, self).__init__(
        collection='compute.regionSnapshots',
        list_command='compute regionSnapshots list --uri',
        api_version='alpha',
        **kwargs
    )


def MakeDiskArgZonal(plural):
  return compute_flags.ResourceArgument(
      resource_name='disk',
      completer=compute_completers.DisksCompleter,
      plural=plural,
      name='DISK_NAME',
      zonal_collection='compute.disks',
      zone_explanation=compute_flags.ZONE_PROPERTY_EXPLANATION)


def MakeDiskArg(plural):
  return compute_flags.ResourceArgument(
      resource_name='disk',
      completer=compute_completers.DisksCompleter,
      plural=plural,
      name='DISK_NAME',
      zonal_collection='compute.disks',
      regional_collection='compute.regionDisks',
      zone_explanation=compute_flags.ZONE_PROPERTY_EXPLANATION,
      region_explanation=compute_flags.REGION_PROPERTY_EXPLANATION)


def AddMultiWriterFlag(parser):
  return parser.add_argument(
      '--multi-writer',
      action='store_true',
      help="""
      Create the disk in multi-writer mode so that it can be attached
      with read-write access to two VMs. The multi-writer feature requires
      specialized filesystems, among other restrictions. For more information,
      see
      https://cloud.google.com/compute/docs/disks/sharing-disks-between-vms.
      """)


def AddEnableConfidentialComputeFlag(parser):
  return parser.add_argument(
      '--confidential-compute',
      action='store_true',
      help="""
      Creates the disk with confidential compute mode enabled. Encryption with a Cloud KMS key is required to enable this option.
      """,
  )


def AddStopGroupAsyncReplicationArgs(parser):
  """Adds stop group async replication specific arguments to parser."""
  parser.add_argument(
      'DISK_CONSISTENCY_GROUP_POLICY',
      help='URL of the disk consistency group resource policy. The resource'
           'policy is always in the region of the primary disks.'
  )

  help_text = '{0} of the consistency group\'s primary or secondary disks. {1}'
  scope_parser = parser.add_mutually_exclusive_group()
  scope_parser.add_argument(
      '--zone',
      completer=compute_completers.ZonesCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.zone),
      help=help_text.format('Zone', compute_flags.ZONE_PROPERTY_EXPLANATION))
  scope_parser.add_argument(
      '--region',
      completer=compute_completers.RegionsCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.region),
      help=help_text.format(
          'Region',
          compute_flags.REGION_PROPERTY_EXPLANATION))


def AddBulkCreateArgs(parser):
  """Adds bulk create specific arguments to parser."""
  parser.add_argument(
      '--source-consistency-group-policy',
      help='''
      URL of the format regions/<REGION>/resourcePolicies/<RESOURCE_POLICY> of the source consistency group resource policy. The resource policy
      is always in the same region as the source disks.
      ''',
      # This argument is optional because we now support bulk insert from
      # multiple source types.
      required=False)

  help_text = """Target {0} of the created disks, which currently must be the same as the source {0}. {1}"""
  scope_parser = parser.add_mutually_exclusive_group(required=True)
  scope_parser.add_argument(
      '--zone',
      completer=compute_completers.ZonesCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.zone),
      help=help_text.format('zone', compute_flags.ZONE_PROPERTY_EXPLANATION))
  scope_parser.add_argument(
      '--region',
      completer=compute_completers.RegionsCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.region),
      help=help_text.format('region',
                            compute_flags.REGION_PROPERTY_EXPLANATION))


def AddBulkCreateArgsAlpha(parser):
  """Adds bulk create specific arguments to parser."""
  parser.add_argument(
      '--source-consistency-group-policy',
      help='''
      URL of the source consistency group resource policy. The resource policy
      is always the same region as the source disks.
      ''',
      # This argument is optional because we now support bulk insert from
      # multiple source types.
      required=False)

  help_text = """Target {0} of the created disks, which currently must be the same as the source {0}. {1}"""
  scope_parser = parser.add_mutually_exclusive_group(required=True)
  scope_parser.add_argument(
      '--zone',
      completer=compute_completers.ZonesCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.zone),
      help=help_text.format('zone', compute_flags.ZONE_PROPERTY_EXPLANATION))
  scope_parser.add_argument(
      '--region',
      completer=compute_completers.RegionsCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.region),
      help=help_text.format('region',
                            compute_flags.REGION_PROPERTY_EXPLANATION))


def AddBulkCreateArgsBeta(parser: argparse.ArgumentParser) -> None:
  """Adds bulk create specific arguments to the parser."""
  parser.add_argument(
      '--source-consistency-group-policy',
      help='''
      URL of the format regions/<REGION>/resourcePolicies/<RESOURCE_POLICY> of the source consistency group resource policy. The resource policy
      is always in the same region as the source disks.
      ''',
      # This argument is optional because we now support bulk insert from
      # multiple source types.
      required=False)

  help_text = """The {scope} in which to create the disks, which must be the same as the source {scope}. {explanation}"""
  scope_parser = parser.add_mutually_exclusive_group(required=True)
  scope_parser.add_argument(
      '--zone',
      completer=compute_completers.ZonesCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.zone),
      help=help_text.format(
          scope='zone', explanation=compute_flags.ZONE_PROPERTY_EXPLANATION
      ),
  )
  scope_parser.add_argument(
      '--region',
      completer=compute_completers.RegionsCompleter,
      action=actions.StoreProperty(properties.VALUES.compute.region),
      help=help_text.format(
          scope='region', explanation=compute_flags.REGION_PROPERTY_EXPLANATION
      ),
  )


def AddProvisionedIopsFlag(parser, arg_parsers):
  return parser.add_argument(
      '--provisioned-iops',
      type=arg_parsers.BoundedInt(),
      help=(
          'Provisioned IOPS of disk to create. Only for use with disks of type '
          'pd-extreme and hyperdisk-extreme.'
      ),
  )


def AddProvisionedThroughputFlag(parser, arg_parsers):
  return parser.add_argument(
      '--provisioned-throughput',
      type=arg_parsers.BoundedInt(),
      help=(
          'Provisioned throughput of disk to create. The throughput unit is  '
          'MB per sec.  Only for use with disks of type hyperdisk-throughput.'))


def AddArchitectureFlag(parser, messages):
  architecture_enum_type = messages.Disk.ArchitectureValueValuesEnum
  excluded_enums = [architecture_enum_type.ARCHITECTURE_UNSPECIFIED.name]
  architecture_choices = sorted(
      [e for e in architecture_enum_type.names() if e not in excluded_enums])
  return parser.add_argument(
      '--architecture',
      choices=architecture_choices,
      help=(
          'Specifies the architecture or processor type that this disk can '
          'support. For available processor types on Compute Engine, '
          'see https://cloud.google.com/compute/docs/cpu-platforms.'
      ),
  )


def AddAccessModeFlag(parser, messages):
  if hasattr(messages.Disk, 'AccessModeValueValuesEnum'):
    access_mode_enum_type = messages.Disk.AccessModeValueValuesEnum
    return parser.add_argument(
        '--access-mode',
        choices=access_mode_enum_type.names(),
        help=(
            'Specifies how VMs attached to the disk can access the data on the'
            ' disk. To grant read-only access to multiple VMs attached to the'
            ' disk, set access-mode to READ_ONLY_MANY.'
            ' To grant read-write access'
            ' to only one VM attached to the disk, use READ_WRITE_SINGLE.'
            ' READ_WRITE_SINGLE is used if omitted.'
        ),
    )


def AddLocationHintArg(parser):
  parser.add_argument(
      '--location-hint',
      hidden=True,
      help="""\
      Used by internal tools to control sub-zone location of the disk.
      """)


def MakeSecondaryDiskArg(required=False):
  return compute_flags.ResourceArgument(
      resource_name='async secondary disk',
      name='--secondary-disk',
      completer=compute_completers.DisksCompleter,
      zonal_collection='compute.disks',
      regional_collection='compute.regionDisks',
      short_help='Secondary disk for asynchronous replication.',
      detailed_help=_ASYNC_SECONDARY_DISK_HELP,
      plural=False,
      required=required,
      scope_flags_usage=compute_flags.ScopeFlagsUsage
      .GENERATE_DEDICATED_SCOPE_FLAGS,
      zone_help_text=_ASYNC_SECONDARY_DISK_ZONE_EXPLANATION,
      region_help_text=_ASYNC_SECONDARY_DISK_REGION_EXPLANATION)


def AddSecondaryDiskProject(parser, category=None):
  parser.add_argument(
      '--secondary-disk-project',
      category=category,
      help=_ASYNC_SECONDARY_DISK_PROJECT_EXPLANATION,
  )


def AddPrimaryDiskProject(parser, category=None):
  parser.add_argument(
      '--primary-disk-project',
      category=category,
      help=_ASYNC_PRIMARY_DISK_PROJECT_EXPLANATION,
  )


def AddKeepOldDiskArgs(parser):
  """Adds keep old disk argument group to parser."""
  group = parser.add_group()
  group.add_argument(
      '--keep-old-disk',
      action='store_true',
      help=(
          'If true, the old disk will be kept after the conversion. '
          'The old disk will be renamed to the original disk name with a '
          'suffix.'
      ),
  )
  group.add_argument(
      '--target-disk-name',
      help=(
          'Specifies the name of the new disk, '
          'it can only be used with --keep-old-disk.'
          ' For details on the naming convention for this '
          'resource, refer to: '
          'https://cloud.google.com/compute/docs/'
          'naming-resources'
      ),
  )


def AddGuestOsFeatureArgs(parser, messages):
  group = parser.add_group()
  guest_os_feature_choices = [
      messages.GuestOsFeature.TypeValueValuesEnum.GVNIC.name
  ]
  group.add_argument(
      '--add-guest-os-features',
      choices=guest_os_feature_choices,
      help=(
          'Specifies guest OS features to add to the disk. Refer to'
          ' https://cloud.google.com/compute/docs/images/create-custom#guest-os-features'
          ' for a list of available options.'
      ),
  )


def AddSourceInstantSnapshotProject(parser, category=None):
  parser.add_argument(
      '--source-instant-snapshot-project',
      category=category,
      help=_DETAILED_SOURCE_INSTANT_SNAPSHOT_PROJECT_HELP,
  )

SOURCE_SNAPSHOT_ARG = compute_flags.ResourceArgument(
    resource_name='snapshot',
    completer=SnapshotsCompleter,
    name='--source-snapshot',
    plural=False,
    required=False,
    global_collection='compute.snapshots',
    short_help='Source snapshot used to create the disks.',
    detailed_help=_DETAILED_SOURCE_SNAPSHOT_HELP,
)

SOURCE_SNAPSHOT_ARG_ALPHA = compute_flags.ResourceArgument(
    resource_name='snapshot',
    completer=SnapshotsCompleterAlpha,
    name='--source-snapshot',
    plural=False,
    required=False,
    global_collection='compute.snapshots',
    regional_collection='compute.regionSnapshots',
    short_help='Source snapshot used to create the disks.',
    detailed_help=_DETAILED_SOURCE_SNAPSHOT_HELP,
)

SOURCE_INSTANT_SNAPSHOT_ARG = compute_flags.ResourceArgument(
    resource_name='source instant snapshot',
    completer=compute_completers.InstantSnapshotsCompleter,
    name='--source-instant-snapshot',
    zonal_collection='compute.instantSnapshots',
    regional_collection='compute.regionInstantSnapshots',
    plural=False,
    required=False,
    short_help='Name of the source instant snapshot used to create the disks.',
    detailed_help=_DETAILED_SOURCE_INSTANT_SNAPSHOT_HELP,
    scope_flags_usage=compute_flags.ScopeFlagsUsage.USE_EXISTING_SCOPE_FLAGS,
)

SOURCE_INSTANT_SNAPSHOT_GROUP_ARG = compute_flags.ResourceArgument(
    resource_name='source instant snapshot group',
    name='--source-instant-snapshot-group',
    completer=compute_completers.InstantSnapshotGroupsCompleter,
    short_help=(
        'URL of the format'
        ' regions/<REGION>/instantSnapshotGroups/<INSTANT_SNAPSHOT_GROUP> or'
        ' zones/<ZONE>/instantSnapshotGroups/<INSTANT_SNAPSHOT_GROUP> of'
        ' the source consistency group of instant snapshots used to create the'
        ' disks.'
    ),
    zonal_collection='compute.instantSnapshotGroups',
    regional_collection='compute.regionInstantSnapshotGroups',
    required=False,
)

SOURCE_SNAPSHOT_GROUP_ARG = compute_flags.ResourceArgument(
    resource_name='source snapshot group',
    name='--source-snapshot-group',
    completer=SnapshotGroupsCompleter,
    short_help='Source snapshot group used to create the disks.',
    global_collection='compute.snapshotGroups',
    required=False,
)

SOURCE_DISK_ARG = compute_flags.ResourceArgument(
    resource_name='source disk',
    name='--source-disk',
    completer=compute_completers.DisksCompleter,
    short_help='Source disk used to create the disks. Source disk must be in'
    ' the same zone/region as the disk to be created.',
    detailed_help=_SOURCE_DISK_DETAILED_HELP,
    zonal_collection='compute.disks',
    regional_collection='compute.regionDisks',
    required=False,
    zone_help_text=_SOURCE_DISK_ZONE_EXPLANATION,
    region_help_text=_SOURCE_DISK_REGION_EXPLANATION)

ASYNC_PRIMARY_DISK_ARG = compute_flags.ResourceArgument(
    resource_name='async primary disk',
    name='--primary-disk',
    completer=compute_completers.DisksCompleter,
    zonal_collection='compute.disks',
    regional_collection='compute.regionDisks',
    short_help='Primary disk for asynchronous replication. This option creates'
    ' a secondary disk for a given primary disk.',
    detailed_help=_ASYNC_PRIMARY_DISK_HELP,
    plural=False,
    required=False,
    scope_flags_usage=compute_flags.ScopeFlagsUsage
    .GENERATE_DEDICATED_SCOPE_FLAGS,
    zone_help_text=_ASYNC_PRIMARY_DISK_ZONE_EXPLANATION,
    region_help_text=_ASYNC_PRIMARY_DISK_REGION_EXPLANATION)

STORAGE_POOL_ARG = compute_flags.ResourceArgument(
    resource_name='storage pool',
    name='--storage-pool',
    short_help=('Specifies the URI of the storage pool in which the disk is '
                'created.'),
    zonal_collection='compute.storagePools',
    plural=False,
    required=False,
    scope_flags_usage=compute_flags.ScopeFlagsUsage.USE_EXISTING_SCOPE_FLAGS)


def AddKmsKeyArg(parser):
  kms_resource_args.AddKmsKeyResourceArg(
      parser, 'disk', region_fallthrough=True)


def AddSourceMachineImageNameArg(parser):
  # TODO: b/421424530 - switch to compute_flags.ResourceArgument when
  # disks.insert goes GA. compute_flags.ResourceArgument does not support
  # the hidden flag.
  parser.add_argument(
      '--source-machine-image',
      help="""\
        Specifies the URI of the source machine image containing the disk to
        restore. Requires *--source-machine-image-disk-device-name* with the
        disk to restores device name.
      """,
  )


def AddSourceMachineImageDiskDeviceNameArg(parser):
  parser.add_argument(
      '--source-machine-image-disk-device-name',
      help="""\
        Specifies the name of the disk to be restored from the source machine
        image. Requires *--source-machine-image* with the URI of the source
        machine image.
      """,
  )
