# -*- coding: utf-8 -*- #
# Copyright 2014 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 for creating forwarding rules."""


import ipaddress
import re

from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import constants
from googlecloudsdk.api_lib.compute import forwarding_rules_utils as utils
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.compute.forwarding_rules import exceptions as fw_exceptions
from googlecloudsdk.command_lib.compute.forwarding_rules import flags
from googlecloudsdk.core import log
import six
from six.moves import range  # pylint: disable=redefined-builtin


def _Args(parser, support_all_protocol, support_external_passthrough):
  """Add the flags to create a forwarding rule."""

  flags.AddCreateArgs(
      parser,
      include_psc_google_apis=True,
      include_target_service_attachment=True,
      include_external_passthrough=support_external_passthrough,
  )
  flags.AddIPProtocols(parser, support_all_protocol)
  flags.AddDescription(parser)
  flags.AddPortsAndPortRange(parser)
  flags.AddNetworkTier(parser, for_update=False)
  flags.AddAllowGlobalAccess(parser)
  flags.AddAllowPscGlobalAccess(parser)
  flags.AddSourceIpRanges(parser)
  flags.AddDisableAutomateDnsZone(parser)
  flags.AddIsMirroringCollector(parser)
  flags.AddServiceDirectoryRegistration(parser)

  parser.add_argument(
      '--service-label',
      help='(Only for Internal Load Balancing): '
      'https://cloud.google.com/load-balancing/docs/dns-names/\n'
      'The DNS label to use as the prefix of the fully qualified domain '
      'name for this forwarding rule. The full name will be internally '
      'generated and output as dnsName. If this field is not specified, '
      'no DNS record will be generated and no DNS name will be output. '
      'You cannot use the `--service-label` flag  if the forwarding rule '
      'references an internal IP address that has the '
      '`--purpose=SHARED_LOADBALANCER_VIP` flag set.')
  forwarding_rule_arg = flags.ForwardingRuleArgument()
  forwarding_rule_arg.AddArgument(parser, operation_type='create')
  parser.display_info.AddCacheUpdater(flags.ForwardingRulesCompleter)
  return forwarding_rule_arg


class CreateHelper(object):
  """Helper class to create a forwarding rule."""

  FORWARDING_RULE_ARG = None

  def __init__(
      self,
      holder,
      support_all_protocol,
      support_sd_registration_for_regional,
      support_external_passthrough,
  ):
    self._holder = holder
    self._support_all_protocol = support_all_protocol
    self._support_sd_registration_for_regional = (
        support_sd_registration_for_regional
    )
    self._support_external_passthrough = support_external_passthrough

  @classmethod
  def Args(cls, parser, support_all_protocol, support_external_passthrough):
    """Inits the class args for supported features."""
    cls.FORWARDING_RULE_ARG = _Args(
        parser, support_all_protocol, support_external_passthrough
    )

  def ConstructProtocol(self, messages, args):
    if args.ip_protocol:
      return messages.ForwardingRule.IPProtocolValueValuesEnum(args.ip_protocol)
    else:
      return

  def Run(self, args):
    """Issues requests necessary to create Forwarding Rules."""
    client = self._holder.client

    forwarding_rule_ref = self.FORWARDING_RULE_ARG.ResolveAsResource(
        args,
        self._holder.resources,
        scope_lister=compute_flags.GetDefaultScopeLister(client))

    if forwarding_rule_ref.Collection() == 'compute.globalForwardingRules':
      requests = self._CreateGlobalRequests(client, self._holder.resources,
                                            args, forwarding_rule_ref)
    elif forwarding_rule_ref.Collection() == 'compute.forwardingRules':
      requests = self._CreateRegionalRequests(client, self._holder.resources,
                                              args, forwarding_rule_ref)

    return client.MakeRequests(requests)

  def _CreateGlobalRequests(self, client, resources, args, forwarding_rule_ref):
    """Create a globally scoped request."""

    is_psc_google_apis = (
        hasattr(args, 'target_google_apis_bundle')
        and args.target_google_apis_bundle
    )

    sd_registration = None
    if hasattr(args, 'service_directory_registration'
              ) and args.service_directory_registration:
      if not is_psc_google_apis:
        raise exceptions.InvalidArgumentException(
            '--service-directory-registration',
            'Can only be specified for regional forwarding rules or Private Service Connect forwarding rules targeting a Google APIs bundle.'
        )
      # Parse projects/../locations/..
      match = re.match(
          r'^projects/([^/]+)/locations/([^/]+)(?:/namespaces/([^/]+))?$',
          args.service_directory_registration)
      if not match:
        raise exceptions.InvalidArgumentException(
            '--service-directory-registration',
            'Must be of the form projects/PROJECT/locations/REGION or projects/PROJECT/locations/REGION/namespaces/NAMESPACE'
        )
      project = match.group(1)
      region = match.group(2)
      namespace = match.group(3)

      if project != forwarding_rule_ref.project:
        raise exceptions.InvalidArgumentException(
            '--service-directory-registration',
            'Must be in the same project as the forwarding rule.')

      sd_registration = client.messages.ForwardingRuleServiceDirectoryRegistration(
          serviceDirectoryRegion=region, namespace=namespace)

    ports_all_specified, range_list = _ExtractPortsAndAll(args.ports)
    # All global forwarding rules must use EXTERNAL or INTERNAL_SELF_MANAGED
    # schemes presently.
    load_balancing_scheme = _GetLoadBalancingScheme(
        args,
        client.messages,
        is_psc_google_apis,
        self._support_external_passthrough,
    )
    # Omit setting port_range for Global NetLB. _MakeSingleUnifiedPortRange does
    # not permit discontinguous port ranges, which are allowed for Global NetLB.
    port_range = None
    if not (
        self._support_external_passthrough
        and load_balancing_scheme
        == client.messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL_PASSTHROUGH
    ):
      port_range = _MakeSingleUnifiedPortRange(args.port_range, range_list)
    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL):
      raise fw_exceptions.ArgumentError(
          'You cannot specify internal [--load-balancing-scheme] for a global '
          'forwarding rule.')

    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL_SELF_MANAGED):
      if (not args.target_http_proxy and not args.target_https_proxy and
          not args.target_grpc_proxy and not args.target_tcp_proxy):
        target_error_message_with_tcp = (
            'You must specify either [--target-http-proxy], '
            '[--target-https-proxy], [--target-grpc-proxy] '
            'or [--target-tcp-proxy] for an '
            'INTERNAL_SELF_MANAGED [--load-balancing-scheme].')
        raise fw_exceptions.ArgumentError(target_error_message_with_tcp)

      if args.subnet:
        raise fw_exceptions.ArgumentError(
            'You cannot specify [--subnet] for an INTERNAL_SELF_MANAGED '
            '[--load-balancing-scheme].')

      if not args.address:
        raise fw_exceptions.ArgumentError(
            'You must specify [--address] for an INTERNAL_SELF_MANAGED '
            '[--load-balancing-scheme]')

    if is_psc_google_apis:
      rule_name = forwarding_rule_ref.Name()
      if len(rule_name) > 20 or rule_name[0].isdigit(
      ) or not rule_name.isalnum():
        raise fw_exceptions.ArgumentError(
            'A forwarding rule to Google APIs must have a name that is between '
            ' 1-20 characters long, alphanumeric, starting with a letter.')

      if port_range:
        raise exceptions.InvalidArgumentException(
            '--ports',
            '[--ports] is not allowed for PSC-GoogleApis forwarding rules.')
      if load_balancing_scheme:
        raise exceptions.InvalidArgumentException(
            '--load-balancing-scheme',
            'The --load-balancing-scheme flag is not allowed for PSC-GoogleApis'
            ' forwarding rules.')

      if args.target_google_apis_bundle in flags.PSC_GOOGLE_APIS_BUNDLES:
        target_as_str = args.target_google_apis_bundle
      else:
        bundles_list = ', '.join(flags.PSC_GOOGLE_APIS_BUNDLES)
        raise exceptions.InvalidArgumentException(
            '--target-google-apis-bundle',
            'The valid values for target-google-apis-bundle are: ' +
            bundles_list)
    else:
      target_ref = utils.GetGlobalTarget(
          resources, args, self._support_external_passthrough
      )
      target_as_str = target_ref.SelfLink()

      if (
          self._support_external_passthrough
          and load_balancing_scheme
          == client.messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL_PASSTHROUGH
      ):
        # This is a Global NetLB forwarding rule using a backend service.
        pass
      else:
        # L7XLB in Premium Tier.
        # Validate the port flags.
        if ports_all_specified:
          raise exceptions.InvalidArgumentException(
              '--ports',
              '[--ports] cannot be set to ALL for global forwarding rules.',
          )
        if not port_range:
          raise exceptions.InvalidArgumentException(
              '--ports', '[--ports] is required for global forwarding rules.'
          )

    if hasattr(args, 'ip_addresses') and args.ip_addresses:
      if (
          load_balancing_scheme
          != client.messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL_PASSTHROUGH
      ):
        raise exceptions.InvalidArgumentException(
            '--ip-addresses',
            '--ip-addresses can only be specified for EXTERNAL_PASSTHROUGH load'
            ' balancing scheme.',
        )
      if len(args.ip_addresses) not in [1, 2]:
        raise exceptions.InvalidArgumentException(
            '--ip-addresses',
            'Exactly one or two IP addresses must be specified.',
        )

    protocol = self.ConstructProtocol(client.messages, args)

    address = self._ResolveAddress(resources, args,
                                   compute_flags.compute_scope.ScopeEnum.GLOBAL,
                                   forwarding_rule_ref)

    ip_addresses = []
    if hasattr(args, 'ip_addresses'):
      ip_addresses = self._ResolveIpAddresses(
          resources, args, forwarding_rule_ref
      )

    forwarding_rule = client.messages.ForwardingRule(
        description=args.description,
        name=forwarding_rule_ref.Name(),
        IPAddress=address,
        IPProtocol=protocol,
        portRange=port_range,
        target=target_as_str,
        networkTier=_ConstructNetworkTier(client.messages, args),
        loadBalancingScheme=load_balancing_scheme)
    if ip_addresses:
      forwarding_rule.IPAddresses = ip_addresses

    self._ProcessCommonArgs(client, resources, args, forwarding_rule_ref,
                            forwarding_rule)
    if sd_registration:
      forwarding_rule.serviceDirectoryRegistrations.append(sd_registration)

    if args.IsSpecified('allow_global_access'):
      forwarding_rule.allowGlobalAccess = args.allow_global_access

    if (
        self._support_external_passthrough
        and load_balancing_scheme
        == client.messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL_PASSTHROUGH
    ):
      # This is a Global NetLB forwarding rule using a backend service.
      # The backendService field must be specified instead of target,
      # and the ports arguments must be resolved to the correct fields.
      forwarding_rule.backendService = forwarding_rule.target
      forwarding_rule.target = None
      forwarding_rule.portRange = None
      _SetForwardingRulePortsForNetLbBackendService(
          forwarding_rule, ports_all_specified, range_list, args.port_range
      )

      # The ipCollection field must be set if provided (for BYOIP PDP).
      if args.IsSpecified('ip_collection'):
        forwarding_rule.ipCollection = (
            flags.GetIpCollectionArg(self._support_external_passthrough)
            .ResolveAsResource(
                args,
                resources,
                default_scope=compute_flags.compute_scope.ScopeEnum.GLOBAL,
            )
            .SelfLink()
        )

    request = client.messages.ComputeGlobalForwardingRulesInsertRequest(
        forwardingRule=forwarding_rule, project=forwarding_rule_ref.project)

    return [(client.apitools_client.globalForwardingRules, 'Insert', request)]

  def _CreateRegionalRequests(self, client, resources, args,
                              forwarding_rule_ref):
    """Create a regionally scoped request."""
    is_psc_ilb = (
        hasattr(args, 'target_service_attachment')
        and args.target_service_attachment
    )

    target_ref, region_ref = utils.GetRegionalTarget(
        client, resources, args, forwarding_rule_ref
    )

    if not args.region and region_ref:
      args.region = region_ref

    protocol = self.ConstructProtocol(client.messages, args)

    address = self._ResolveAddress(resources, args,
                                   compute_flags.compute_scope.ScopeEnum.REGION,
                                   forwarding_rule_ref)

    load_balancing_scheme = _GetLoadBalancingScheme(
        args, client.messages, is_psc_ilb, self._support_external_passthrough
    )

    if is_psc_ilb and load_balancing_scheme:
      raise exceptions.InvalidArgumentException(
          '--load-balancing-scheme',
          'The --load-balancing-scheme flag is not allowed for PSC-ILB '
          'forwarding rules.')

    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL):
      if args.port_range:
        raise fw_exceptions.ArgumentError(
            'You cannot specify [--port-range] for a forwarding rule '
            'whose [--load-balancing-scheme] is internal, '
            'please use [--ports] flag instead.')

    if (load_balancing_scheme == client.messages.ForwardingRule
        .LoadBalancingSchemeValueValuesEnum.INTERNAL_SELF_MANAGED):
      raise fw_exceptions.ArgumentError(
          'You cannot specify an INTERNAL_SELF_MANAGED '
          '[--load-balancing-scheme] for a regional forwarding rule.')

    if hasattr(args, 'ip_addresses') and args.ip_addresses:
      raise exceptions.InvalidArgumentException(
          '--ip-addresses',
          '--ip-addresses can only be used for global forwarding rules using '
          'the EXTERNAL_PASSTHROUGH load balancing scheme.',
      )

    forwarding_rule = client.messages.ForwardingRule(
        description=args.description,
        name=forwarding_rule_ref.Name(),
        IPAddress=address,
        IPProtocol=protocol,
        networkTier=_ConstructNetworkTier(client.messages, args),
        loadBalancingScheme=load_balancing_scheme)
    if args.source_ip_ranges:
      forwarding_rule.sourceIpRanges = args.source_ip_ranges

    self._ProcessCommonArgs(client, resources, args, forwarding_rule_ref,
                            forwarding_rule)

    ports_all_specified, range_list = _ExtractPortsAndAll(args.ports)

    if target_ref.Collection() == 'compute.regionBackendServices':
      # A FR pointing to a BS has no target attribute.
      forwarding_rule.backendService = target_ref.SelfLink()
      forwarding_rule.target = None
    else:
      # A FR pointing to anything not a BES has a target attribute.
      forwarding_rule.backendService = None
      forwarding_rule.target = target_ref.SelfLink()

    if ((target_ref.Collection() == 'compute.regionBackendServices' or
         target_ref.Collection() == 'compute.targetInstances') and
        args.load_balancing_scheme == 'INTERNAL'):
      # This is for L4ILB and internal protocol forwarding.
      # API fields allPorts, ports, and portRange are mutually exclusive.
      # API field portRange is not valid for this case.
      # Use of L3_DEFAULT implies all ports even if allPorts is unset.
      if ports_all_specified:
        forwarding_rule.allPorts = True
      elif range_list:
        forwarding_rule.ports = [
            six.text_type(p) for p in _GetPortList(range_list)
        ]
    elif ((target_ref.Collection() == 'compute.regionTargetHttpProxies' or
           target_ref.Collection() == 'compute.regionTargetHttpsProxies') and
          args.load_balancing_scheme == 'INTERNAL'):
      # This is a legacy configuration for L7ILB.
      forwarding_rule.ports = [
          six.text_type(p) for p in _GetPortList(range_list)
      ]
    elif args.load_balancing_scheme == 'INTERNAL':
      # There are currently no other valid combinations of targets with scheme
      # internal. With scheme internal, targets must presently be a regional
      # backend service (L4ILB) or a target instance (protocol forwarding).
      raise exceptions.InvalidArgumentException(
          '--load-balancing-scheme',
          'Only target instances and backend services should be specified as '
          'a target for internal load balancing.')
    elif args.load_balancing_scheme == 'INTERNAL_MANAGED':
      # This is L7ILB.
      forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
          args.port_range, range_list)
    elif args.load_balancing_scheme == 'EXTERNAL_MANAGED':
      # This is regional L7XLB.
      forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
          args.port_range, range_list)
    elif ((target_ref.Collection() == 'compute.regionBackendServices') and
          ((args.load_balancing_scheme == 'EXTERNAL') or
           (not args.load_balancing_scheme))):
      # This is NetLB using a backend service. Scheme is either explicitly
      # EXTERNAL or not supplied (EXTERNAL is the default scheme).
      # API fields allPorts, ports, and portRange are mutually exclusive.
      # All three API fields are valid for this case.
      # Use of L3_DEFAULT implies all ports even if allPorts is unset.
      _SetForwardingRulePortsForNetLbBackendService(
          forwarding_rule, ports_all_specified, range_list, args.port_range
      )
    elif ((target_ref.Collection() == 'compute.targetPool' or
           target_ref.Collection() == 'compute.targetInstances') and
          ((args.load_balancing_scheme == 'EXTERNAL') or
           (not args.load_balancing_scheme))):
      # This is NetLB using a target pool or external protocol forwarding.
      # Scheme is either explicitly EXTERNAL or not supplied (EXTERNAL is the
      # default scheme).
      # API fields allPorts, ports, and portRange are mutually exclusive.
      # API field ports is not valid for this case.
      # Use of L3_DEFAULT implies all ports by definition.
      if ports_all_specified:
        forwarding_rule.allPorts = True
      else:
        forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
            args.port_range, range_list)
    else:
      # All other regional forwarding rules with load balancing scheme EXTERNAL.
      forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
          args.port_range, range_list)

    if hasattr(args, 'service_label'):
      forwarding_rule.serviceLabel = args.service_label

    if args.IsSpecified('allow_global_access'):
      forwarding_rule.allowGlobalAccess = args.allow_global_access

    if args.IsSpecified('allow_psc_global_access'):
      forwarding_rule.allowPscGlobalAccess = args.allow_psc_global_access

    if args.IsSpecified('ip_collection'):
      forwarding_rule.ipCollection = (
          flags.GetIpCollectionArg(self._support_external_passthrough)
          .ResolveAsResource(args, resources)
          .SelfLink()
      )

    if args.IsSpecified('disable_automate_dns_zone'):
      forwarding_rule.noAutomateDnsZone = args.disable_automate_dns_zone

    if hasattr(args, 'is_mirroring_collector'):
      forwarding_rule.isMirroringCollector = args.is_mirroring_collector

    if hasattr(args, 'service_directory_registration'
              ) and args.service_directory_registration:
      if is_psc_ilb:
        # Parse projects/../locations/../namespaces/..
        match = re.match(
            r'^projects/([^/]+)/locations/([^/]+)/namespaces/([^/]+)$',
            args.service_directory_registration)
        if not match:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'If set, must be of the form projects/PROJECT/locations/REGION/namespaces/NAMESPACE'
          )
        project = match.group(1)
        region = match.group(2)

        if project != forwarding_rule_ref.project or region != forwarding_rule_ref.region:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'Service Directory registration must be in the same project and region as the forwarding rule.'
          )

        sd_registration = client.messages.ForwardingRuleServiceDirectoryRegistration(
            namespace=match.group(3))
        forwarding_rule.serviceDirectoryRegistrations.append(sd_registration)
      else:
        if not self._support_sd_registration_for_regional:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              """flag is available in one or more alternate release tracks. Try:

  gcloud alpha compute forwarding-rules create --service-directory-registration
  gcloud beta compute forwarding-rules create --service-directory-registration"""
          )
        # Parse projects/../locations/../namespaces/../services/..
        match = re.match(
            r'^projects/([^/]+)/locations/([^/]+)/namespaces/([^/]+)/services/([^/]+)$',
            args.service_directory_registration)
        if not match:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'Must be of the form projects/PROJECT/locations/REGION/namespaces/NAMESPACE/services/SERVICE'
          )
        project = match.group(1)
        region = match.group(2)

        if project != forwarding_rule_ref.project or region != forwarding_rule_ref.region:
          raise exceptions.InvalidArgumentException(
              '--service-directory-registration',
              'Service Directory registration must be in the same project and region as the forwarding rule.'
          )

        sd_registration = client.messages.ForwardingRuleServiceDirectoryRegistration(
            namespace=match.group(3), service=match.group(4))
        forwarding_rule.serviceDirectoryRegistrations.append(sd_registration)

    request = client.messages.ComputeForwardingRulesInsertRequest(
        forwardingRule=forwarding_rule,
        project=forwarding_rule_ref.project,
        region=forwarding_rule_ref.region)

    return [(client.apitools_client.forwardingRules, 'Insert', request)]

  def _ResolveAddress(self, resources, args, scope, forwarding_rule_ref):
    """Resolve address resource."""

    # Address takes either an ip address or an address resource. If parsing as
    # an IP address fails, then we resolve as a resource.
    address = args.address
    if address is not None:
      try:
        # ipaddress only allows unicode input
        ipaddress.ip_network(six.text_type(args.address))
      except ValueError:
        # TODO(b/37086838): Make sure global/region settings are inherited by
        # address resource.
        if scope == compute_flags.compute_scope.ScopeEnum.REGION:
          if not args.global_address and not args.address_region:
            if forwarding_rule_ref.Collection() == 'compute.forwardingRules':
              args.address_region = forwarding_rule_ref.region
        address_ref = flags.AddressArg(
            self._support_external_passthrough
        ).ResolveAsResource(args, resources, default_scope=scope)
        address = address_ref.SelfLink()

    return address

  def _ResolveIpAddresses(self, resources, args, forwarding_rule_ref):
    """Resolve ip_addresses field for Global NetLB (see go/global-netlb-ug)."""
    if not args.ip_addresses:
      return []

    resolved_addresses = []
    for address in args.ip_addresses:
      try:
        # ipaddress only allows unicode input
        ipaddress.ip_network(six.text_type(address))
        resolved_addresses.append(address)
      except ValueError:
        # Resolve as resource.
        # We use a temporary attribute on args to use ResolveAsResource
        # effectively or just use the registry directly.
        # registry.Parse is safer here.
        params = {'project': forwarding_rule_ref.project}
        collection = 'compute.globalAddresses'

        address_ref = resources.Parse(
            address, collection=collection, params=params
        )
        resolved_addresses.append(address_ref.SelfLink())

    return resolved_addresses

  def _ProcessCommonArgs(self, client, resources, args, forwarding_rule_ref,
                         forwarding_rule):
    """Processes common arguments for global and regional commands.

    Args:
      client: The client used by gcloud.
      resources: The resource parser.
      args: The arguments passed to the gcloud command.
      forwarding_rule_ref: The forwarding rule reference.
      forwarding_rule: The forwarding rule to set properties on.
    """

    if args.ip_version:
      forwarding_rule.ipVersion = (
          client.messages.ForwardingRule.IpVersionValueValuesEnum(
              args.ip_version))
    if args.network:
      forwarding_rule.network = flags.NetworkArg().ResolveAsResource(
          args, resources).SelfLink()
    if args.subnet:
      if (not args.subnet_region and
          forwarding_rule_ref.Collection() == 'compute.forwardingRules'):
        args.subnet_region = forwarding_rule_ref.region
      forwarding_rule.subnetwork = flags.SUBNET_ARG.ResolveAsResource(
          args, resources).SelfLink()


@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.PREVIEW)
@base.UniverseCompatible
class Create(base.CreateCommand):
  """Create a forwarding rule to direct network traffic to a load balancer."""
  _support_all_protocol = False
  _support_sd_registration_for_regional = False
  _support_external_passthrough = False

  @classmethod
  def Args(cls, parser):
    CreateHelper.Args(
        parser, cls._support_all_protocol, cls._support_external_passthrough
    )

  def Run(self, args):
    holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
    return CreateHelper(
        holder,
        self._support_all_protocol,
        self._support_sd_registration_for_regional,
        self._support_external_passthrough,
    ).Run(args)


@base.ReleaseTracks(base.ReleaseTrack.BETA)
class CreateBeta(Create):
  """Create a forwarding rule to direct network traffic to a load balancer."""
  _support_all_protocol = False
  _support_sd_registration_for_regional = True
  _support_external_passthrough = True


@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class CreateAlpha(CreateBeta):
  """Create a forwarding rule to direct network traffic to a load balancer."""
  _support_all_protocol = True
  _support_sd_registration_for_regional = True
  _support_external_passthrough = True


Create.detailed_help = {
    'DESCRIPTION': ("""
*{{command}}* is used to create a forwarding rule. {overview}

When creating a forwarding rule, exactly one of  ``--target-instance'',
``--target-pool'', ``--target-http-proxy'', ``--target-https-proxy'',
``--target-grpc-proxy'', ``--target-ssl-proxy'', ``--target-tcp-proxy'',
``--target-vpn-gateway'', ``--backend-service'' or ``--target-google-apis-bundle''
must be specified.""".format(overview=flags.FORWARDING_RULES_OVERVIEW)),
    'EXAMPLES':
        """
    To create a global forwarding rule that will forward all traffic on port
    8080 for IP address ADDRESS to a target http proxy PROXY, run:

      $ {command} RULE_NAME --global --target-http-proxy=PROXY --ports=8080 --address=ADDRESS

    To create a regional forwarding rule for the subnet SUBNET_NAME on the
    default network that will forward all traffic on ports 80-82 to a
    backend service SERVICE_NAME, run:

      $ {command} RULE_NAME --load-balancing-scheme=INTERNAL --backend-service=SERVICE_NAME --subnet=SUBNET_NAME --network=default --region=REGION --ports=80-82
"""
}

CreateBeta.detailed_help = Create.detailed_help
CreateAlpha.detailed_help = CreateBeta.detailed_help


def _UnifyPortRangeFromListOfRanges(ports_range_list):
  """Return a single port range by combining a list of port ranges."""
  if not ports_range_list:
    return None, None
  ports = sorted(ports_range_list)
  combined_port_range = ports.pop(0)
  for port_range in ports_range_list:
    try:
      combined_port_range = combined_port_range.Combine(port_range)
    except arg_parsers.Error:
      raise exceptions.InvalidArgumentException(
          '--ports', 'Must specify consecutive ports at this time.')
  return combined_port_range


def _ExtractPortsAndAll(ports_with_all):
  if ports_with_all:
    return ports_with_all.all_specified, ports_with_all.ranges
  else:
    return False, []


def _MakeSingleUnifiedPortRange(arg_port_range, range_list_from_arg_ports):
  """Reconciles the deprecated --port-range arg with ranges from --ports arg."""
  if arg_port_range:
    log.warning(
        'The --port-range flag is deprecated. Use equivalent --ports=%s'
        ' flag.', arg_port_range)
    return six.text_type(arg_port_range)
  elif range_list_from_arg_ports:
    range_list = _UnifyPortRangeFromListOfRanges(range_list_from_arg_ports)
    return six.text_type(range_list) if range_list else None


def _GetPortList(range_list):
  """Creates list of singleton port numbers from list of ports and ranges."""
  ports = []
  for port_range in range_list:
    ports.extend(list(range(port_range.start, port_range.end + 1)))
  return sorted(ports)


def _SetForwardingRulePortsForNetLbBackendService(
    forwarding_rule, ports_all_specified, range_list, arg_port_range
):
  """Set the appropriate port field for a NetLB FR with backend service.

  Args:
    forwarding_rule: The forwarding rule to set properties on.
    ports_all_specified: Whether the --all-ports flag was specified.
    range_list: The list of ranges from the --ports flag.
    arg_port_range: The port range from the --port-range flag.
  """
  # This is a NetLB forwarding rule with a backend service.
  # API fields allPorts, ports, and portRange are mutually exclusive.
  # API field ports is not valid for this case.
  # Use of L3_DEFAULT implies all ports by definition.
  if ports_all_specified:
    forwarding_rule.allPorts = True
  elif range_list:
    if len(range_list) > 1:
      # More than one port, potentially discontiguous, from --ports= flag.
      forwarding_rule.ports = [
          six.text_type(p) for p in _GetPortList(range_list)
      ]
    else:
      # Exactly one value from --ports= flag. Might be a single port (80);
      # might be a range (80-90). Since it might be a range, the portRange
      # API attribute is more appropriate.
      forwarding_rule.portRange = six.text_type(range_list[0])
  elif arg_port_range:
    forwarding_rule.portRange = _MakeSingleUnifiedPortRange(
        arg_port_range, range_list
    )


def _GetLoadBalancingScheme(
    args, messages, is_psc, include_external_passthrough
):
  """Get load balancing scheme."""
  if not args.load_balancing_scheme:
    # The default is EXTERNAL for non-PSC forwarding rules.
    return None if is_psc else messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL
  if args.load_balancing_scheme == 'INTERNAL':
    return messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.INTERNAL
  elif args.load_balancing_scheme == 'EXTERNAL':
    return messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL
  elif args.load_balancing_scheme == 'EXTERNAL_MANAGED':
    return messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL_MANAGED
  elif args.load_balancing_scheme == 'INTERNAL_SELF_MANAGED':
    return (messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum
            .INTERNAL_SELF_MANAGED)
  elif args.load_balancing_scheme == 'INTERNAL_MANAGED':
    return (messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum
            .INTERNAL_MANAGED)
  elif (
      include_external_passthrough
      and args.load_balancing_scheme == 'EXTERNAL_PASSTHROUGH'
  ):
    return (
        messages.ForwardingRule.LoadBalancingSchemeValueValuesEnum.EXTERNAL_PASSTHROUGH
    )
  return None


def _ConstructNetworkTier(messages, args):
  """Get network tier."""
  if args.network_tier:
    network_tier = args.network_tier.upper()
    if network_tier in constants.NETWORK_TIER_CHOICES_FOR_INSTANCE:
      return messages.ForwardingRule.NetworkTierValueValuesEnum(
          args.network_tier)
    else:
      raise exceptions.InvalidArgumentException(
          '--network-tier',
          'Invalid network tier [{tier}]'.format(tier=network_tier))
  else:
    return
