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

"""Utils for IAP commands."""

from googlecloudsdk.api_lib.iap import util as iap_api
from googlecloudsdk.calliope import exceptions as calliope_exc
from googlecloudsdk.command_lib.iam import iam_util
from googlecloudsdk.command_lib.iap import exceptions as iap_exc
from googlecloudsdk.core import properties


APP_ENGINE_RESOURCE_TYPE = 'app-engine'
BACKEND_SERVICES_RESOURCE_TYPE = 'backend-services'
WEB_RESOURCE_TYPE = 'iap_web'
COMPUTE_RESOURCE_TYPE = 'compute'
ORG_RESOURCE_TYPE = 'organization'
FOLDER_RESOURCE_TYPE = 'folder'
FORWARDING_RULE_RESOURCE_TYPE = 'forwarding-rule'
CLOUD_RUN_RESOURCE_TYPE = 'cloud-run'
AGENT_REGISTRY_RESOURCE_TYPE = 'agent-registry'
WEB_ENABLE_DISABLE_RESOURCE_TYPE_ENUM = (
    APP_ENGINE_RESOURCE_TYPE,
    BACKEND_SERVICES_RESOURCE_TYPE,
)
IAM_RESOURCE_TYPE_ENUM = (
    APP_ENGINE_RESOURCE_TYPE,
    BACKEND_SERVICES_RESOURCE_TYPE,
    FORWARDING_RULE_RESOURCE_TYPE,
    CLOUD_RUN_RESOURCE_TYPE,
)
IAM_RESOURCE_TYPE_ENUM_WITH_AGENT_REGISTRY = (
    APP_ENGINE_RESOURCE_TYPE,
    BACKEND_SERVICES_RESOURCE_TYPE,
    FORWARDING_RULE_RESOURCE_TYPE,
    CLOUD_RUN_RESOURCE_TYPE,
    AGENT_REGISTRY_RESOURCE_TYPE,
)
SETTING_RESOURCE_TYPE_ENUM = (
    APP_ENGINE_RESOURCE_TYPE,
    WEB_RESOURCE_TYPE,
    COMPUTE_RESOURCE_TYPE,
    ORG_RESOURCE_TYPE,
    FOLDER_RESOURCE_TYPE,
    BACKEND_SERVICES_RESOURCE_TYPE,
    FORWARDING_RULE_RESOURCE_TYPE,
    CLOUD_RUN_RESOURCE_TYPE,
)
IAP_TCP_IAM_RESOURCE_TYPE_ENUM = (
    CLOUD_RUN_RESOURCE_TYPE,
)


def AddIamDestGroupArgs(parser):
  """Adds DestGroup args for managing IAM policies.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  parser.add_argument(
      '--dest-group',
      required=True,
      help='Name of the Destination Group.')
  parser.add_argument(
      '--region',
      required=True,
      help='Region of the Destination Group.')


def AddDestGroupArgs(parser):
  """Adds DestGroup args for managing the resource.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  parser.add_argument(
      'group_name',
      help='Name of the Destination Group.')
  parser.add_argument(
      '--region',
      metavar='REGION',
      required=True,
      help='Region of the Destination Group.')


def AddDestGroupCreateIpAndFqdnArgs(parser):
  """Adds IP and FQDN args for DestGroup Create command.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  parser.add_argument(
      '--ip-range-list',
      help='List of ip-ranges in the Destination Group.')
  parser.add_argument(
      '--fqdn-list',
      help='List of FQDNs in the Destination Group.')


def AddDestGroupUpdateIpAndFqdnArgs(parser):
  """Adds IP and FQDN args for DestGroup Update command.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  group = parser.add_group(required=True)
  group.add_argument(
      '--ip-range-list',
      help='List of ip-ranges in the Destination Group.')
  group.add_argument(
      '--fqdn-list',
      help='List of FQDNs in the Destination Group.')


def AddDestGroupListRegionArgs(parser):
  """Adds Region arg for DestGroup List command.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  parser.add_argument(
      '--region',
      metavar='REGION',
      required=False,
      help='Region of the Destination Group, will list all regions by default',
      default='-',
  )


def AddIapIamResourceArgs(
    parser, support_agent_registry=False
):
  """Adds flags for an IAP IAM resource.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
    support_agent_registry: bool, whether to support agent registry.
  """
  group = parser.add_group()

  choices = (
      IAM_RESOURCE_TYPE_ENUM_WITH_AGENT_REGISTRY
      if support_agent_registry
      else IAM_RESOURCE_TYPE_ENUM
  )
  group.add_argument(
      '--resource-type',
      choices=choices,
      help='Resource type of the IAP resource.',
  )
  group.add_argument('--service', help='Service name.')

  group.add_argument(
      '--region',
      help=(
          'Region name. Not applicable for `resource-type=app-engine`. Required'
          ' when `resource-type=backend-services` and regional scoped. Not'
          ' applicable for global backend-services. Required when'
          ' `resource-type=cloud-run`.'
      ),
  )
  group.add_argument(
      '--version',
      help=(
          'Service version. Should only be specified with '
          '`--resource-type=app-engine`.'
      ),
  )

  if support_agent_registry:
    agent_group = group.add_mutually_exclusive_group()
    agent_group.add_argument(
        '--agent',
        help=(
            'Agent ID for the agent-registry resource type.'
        ),
    )
    agent_group.add_argument(
        '--mcp-server',
        help=(
            'MCP server ID for the agent-registry resource type.'
        ),
    )
    agent_group.add_argument(
        '--endpoint',
        help=(
            'Endpoint ID for the agent-registry resource type.'
        ),
    )


def AddIapResourceArgs(parser):
  """Adds flags for an IAP resource.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  group = parser.add_group()
  group.add_argument(
      '--resource-type',
      required=True,
      choices=WEB_ENABLE_DISABLE_RESOURCE_TYPE_ENUM,
      help='Resource type of the IAP resource.')
  group.add_argument(
      '--service',
      help='Service name. Required with `--resource-type=backend-services`.')
  group.add_argument(
      '--region',
      help=(
          "Region name. Not applicable for ``app-engine''. Optional when"
          " ``resource-type'' is ``compute''."
      ),
  )


def AddIapSettingArg(
    parser
):
  """Adds flags for an IAP settings resource.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  group = parser.add_group()
  group.add_argument('--organization', help='Organization ID.')
  group.add_argument('--folder', help='Folder ID.')
  group.add_argument('--project', help='Project ID.')

  group.add_argument(
      '--resource-type',
      choices=SETTING_RESOURCE_TYPE_ENUM,
      help=(
          'Resource type of the IAP resource. For Backend Services, you can'
          ' use both `compute` and `backend-services` as resource type.'
      ),
  )

  group.add_argument(
      '--service',
      help=(
          'Service name. Optional when `resource-type` is `compute` or'
          ' `app-engine`.'
      ),
  )

  group.add_argument(
      '--region',
      help=(
          'Region name. Not applicable for `app-engine`. Required when'
          ' `resource-type=compute` and regional scoped. Not'
          ' applicable for global scoped compute. Required when'
          ' `resource-type=cloud-run`.'
      ),
  )

  group.add_argument(
      '--version',
      help=(
          'Version name. Not applicable for `compute`. Optional when'
          ' `resource-type=app-engine`.'
      ),
  )


def AddOauthClientArgs(parser):
  """Adds OAuth client args.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  group = parser.add_group()
  group.add_argument(
      '--oauth2-client-id',
      required=True,
      help='OAuth 2.0 client ID to use.')
  group.add_argument(
      '--oauth2-client-secret',
      required=True,
      help='OAuth 2.0 client secret to use.')


def AddIapTcpIamResourceArgs(
    parser,
):
  """Adds flags for an IAP TCP IAM resource.
  """
  group = parser.add_group()
  group.add_argument(
      '--resource-type',
      choices=IAP_TCP_IAM_RESOURCE_TYPE_ENUM,
      help='Resource type of the IAP resource.',
  )
  cr_group = group.add_mutually_exclusive_group()
  cr_group.add_argument('--service', help='Service name.')
  cr_group.add_argument('--instance', help='Instance ID.')
  group.add_argument(
      '--region',
      help=(
          'Region name. Required when `--service` or `--instance` is specified '
          'for `resource-type=cloud-run`.'
      ),
  )


def AddAddIamPolicyBindingArgs(parser):
  iam_util.AddArgsForAddIamPolicyBinding(
      parser,
      add_condition=True)


def AddRemoveIamPolicyBindingArgs(parser):
  iam_util.AddArgsForRemoveIamPolicyBinding(
      parser,
      add_condition=True)


def AddIAMPolicyFileArg(parser):
  """Adds flags for an IAM policy file.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  parser.add_argument(
      'policy_file', help='JSON or YAML file containing the IAM policy.')


def AddIapSettingFileArg(parser):
  """Add flags for the IAP setting file.

  Args:
    parser: An argparse.ArgumentParser-like object. It is mocked out in order to
      capture some information, but behaves like an ArgumentParser.
  """
  parser.add_argument(
      'setting_file',
      help="""JSON or YAML file containing the IAP resource settings.

JSON example:

```
{
  "access_settings": {
    "oauth_settings": {
      "login_hint": {
        "value": "test_hint"
      }
    },
    "gcip_settings": {
      "tenant_ids": [
        "tenant1-p9puj",
        "tenant2-y8rxc"
      ],
      "login_page_uri": {
        "value": "https://test.com/?apiKey=abcd_efgh"
      }
    },
    "cors_settings": {
      "allow_http_options": {
        "value": true
      }
    }
  },
  "application_settings": {
    "csm_settings": {
      "rctoken_aud": {
        "value": "test_aud"
      }
    }
  }
}
```

YAML example:

```
accessSettings :
  oauthSettings:
    loginHint: test_hint
  gcipSettings:
    tenantIds:
    - tenant1-p9puj
    - tenant2-y8rxc
    loginPageUri: https://test.com/?apiKey=abcd_efgh
  corsSettings:
    allowHttpOptions: true
applicationSettings:
  csmSettings:
    rctokenAud: test_aud
```
""")


def ParseIapIamResource(
    release_track,
    args,
    support_agent_registry=False
):
  """Parse an IAP IAM resource from the input arguments.

  Args:
    release_track: base.ReleaseTrack, release track of command.
    args: an argparse namespace. All the arguments that were provided to this
      command invocation.
    support_agent_registry: bool, whether to support agent registry.

  Raises:
    calliope_exc.InvalidArgumentException: if a provided argument does not apply
        to the specified resource type.
    iap_exc.InvalidIapIamResourceError: if an IapIamResource could not be parsed
        from the arguments.

  Returns:
    The specified IapIamResource
  """
  project = properties.VALUES.core.project.GetOrFail()
  if not args.resource_type:
    if args.service:
      raise calliope_exc.InvalidArgumentException(
          '--service',
          '`--service` cannot be specified without `--resource-type`.',
      )
    if args.version:
      raise calliope_exc.InvalidArgumentException(
          '--version',
          '`--version` cannot be specified without `--resource-type`.',
      )
    if support_agent_registry and args.region:
      return iap_api.IAPWebLocation(
          release_track, project, args.region
      )
    return iap_api.IAPWeb(release_track, project)
  elif args.resource_type == APP_ENGINE_RESOURCE_TYPE:
    if args.region:
      raise calliope_exc.InvalidArgumentException(
          '--region',
          '`--region` cannot be specified for `--resource-type=app-engine`.',
      )
    if args.service and args.version:
      return iap_api.AppEngineServiceVersion(
          release_track, project, args.service, args.version
      )
    elif args.service:
      return iap_api.AppEngineService(release_track, project, args.service)
    if args.version:
      raise calliope_exc.InvalidArgumentException(
          '--version', '`--version` cannot be specified without `--service`.'
      )
    return iap_api.AppEngineApplication(release_track, project)
  elif args.resource_type == BACKEND_SERVICES_RESOURCE_TYPE:
    if args.version:
      raise calliope_exc.InvalidArgumentException(
          '--version',
          '`--version` cannot be specified for '
          '`--resource-type=backend-services`.',
      )
    if args.region:
      if args.service:
        return iap_api.BackendService(
            release_track, project, args.region, args.service
        )
      else:
        return iap_api.BackendServices(release_track, project, args.region)
    elif args.service:
      return iap_api.BackendService(release_track, project, None, args.service)
    return iap_api.BackendServices(release_track, project, None)
  elif args.resource_type == FORWARDING_RULE_RESOURCE_TYPE:
    if args.version:
      raise calliope_exc.InvalidArgumentException(
          '--version',
          '`--version` cannot be specified for '
          '`--resource-type=forwarding-rule`.',
      )
    if args.service:
      return iap_api.ForwardingRule(release_track, project, args.region,
                                    args.service)
    else:
      return iap_api.ForwardingRules(release_track, project, args.region)
  elif args.resource_type == CLOUD_RUN_RESOURCE_TYPE:
    if args.version:
      raise calliope_exc.InvalidArgumentException(
          '--version',
          '`--version` cannot be specified for '
          '`--resource-type=cloud-run`.',
      )
    if not args.region:
      raise calliope_exc.InvalidArgumentException(
          '--region',
          '`--region` must be specified for '
          '`--resource-type=cloud-run`.',
      )
    if args.service:
      return iap_api.CloudRun(release_track, project, args.region, args.service)
    else:
      return iap_api.CloudRuns(release_track, project, args.region)
  elif (
      support_agent_registry
      and args.resource_type == AGENT_REGISTRY_RESOURCE_TYPE
  ):
    if args.version:
      raise calliope_exc.InvalidArgumentException(
          '--version',
          '`--version` cannot be specified for '
          '`--resource-type=agent-registry`.',
      )
    if args.service:
      raise calliope_exc.InvalidArgumentException(
          '--service',
          '`--service` cannot be specified for '
          '`--resource-type=agent-registry`.',
      )
    if args.agent:
      return iap_api.AgentRegistryAgent(
          release_track,
          project,
          agent_id=args.agent,
          location_id=args.region,
      )
    elif args.mcp_server:
      return iap_api.AgentRegistryMcpServer(
          release_track,
          project,
          mcp_server_id=args.mcp_server,
          location_id=args.region,
      )
    elif args.endpoint:
      return iap_api.AgentRegistryEndpoint(
          release_track,
          project,
          endpoint_id=args.endpoint,
          location_id=args.region,
      )
    else:
      return iap_api.AgentRegistry(
          release_track,
          project,
          location_id=args.region,
      )

  # This shouldn't be reachable, based on the IAP IAM resource parsing logic.
  raise iap_exc.InvalidIapIamResourceError('Could not parse IAP IAM resource.')


def ParseIapResource(release_track, args):
  """Parse an IAP resource from the input arguments.

  Args:
    release_track: base.ReleaseTrack, release track of command.
    args: an argparse namespace. All the arguments that were provided to this
      command invocation.

  Raises:
    calliope_exc.InvalidArgumentException: if `--version` was specified with
        resource type 'backend-services'.
    iap_exc.InvalidIapIamResourceError: if an IapIamResource could not be parsed
        from the arguments.

  Returns:
    The specified IapIamResource
  """
  project = properties.VALUES.core.project.GetOrFail()
  if args.resource_type:
    if args.resource_type == APP_ENGINE_RESOURCE_TYPE:
      if args.service:
        raise calliope_exc.InvalidArgumentException(
            '--service',
            '`--service` cannot be specified for '
            '`--resource-type=app-engine`.')
      return iap_api.AppEngineApplication(
          release_track,
          project)
    elif args.resource_type == BACKEND_SERVICES_RESOURCE_TYPE:
      if not args.service:
        raise calliope_exc.RequiredArgumentException(
            '--service',
            '`--service` must be specified for '
            '`--resource-type=backend-services`.')

      return iap_api.BackendService(
          release_track, project, args.region, args.service
      )

  raise iap_exc.InvalidIapIamResourceError('Could not parse IAP resource.')


def ParseIapSettingsResource(
    release_track,
    args,
):
  """Parse an IAP setting resource from the input arguments.

  Args:
    release_track: base.ReleaseTrack, release track of command.
    args: an argparse namespace. All the arguments that were provided to this
      command invocation.

  Raises:
    calliope_exc.InvalidArgumentException: if `--version` was specified with
        resource type 'backend-services'.

  Returns:
    The specified IapSettingsResource
  """
  if args.organization:
    if args.resource_type:
      raise calliope_exc.InvalidArgumentException(
          '--resource-type',
          '`--resource-type` should not be specified at organization level',
      )
    if args.project:
      raise calliope_exc.InvalidArgumentException(
          '--project',
          '`--project` should not be specified at organization level',
      )
    return iap_api.IapSettingsResource(
        release_track, 'organizations/{0}'.format(args.organization)
    )
  if args.folder:
    if args.resource_type:
      raise calliope_exc.InvalidArgumentException(
          '--resource-type',
          '`--resource-type` should not be specified at folder level',
      )
    if args.project:
      raise calliope_exc.InvalidArgumentException(
          '--project', '`--project` should not be specified at folder level'
      )
    return iap_api.IapSettingsResource(
        release_track, 'folders/{0}'.format(args.folder)
    )
  if args.project:
    if args.service and not args.resource_type:
      raise calliope_exc.InvalidArgumentException(
          '--service',
          '`--service` cannot be specified without `--resource-type`.')
    if args.region and not args.resource_type:
      raise calliope_exc.InvalidArgumentException(
          '--region',
          '`--region` cannot be specified without `--resource-type`.')
    if args.version and not args.resource_type:
      raise calliope_exc.InvalidArgumentException(
          '--version',
          '`--version` cannot be specified without `--resource-type`.')

    if not args.resource_type:
      return iap_api.IapSettingsResource(
          release_track, 'projects/{0}'.format(args.project)
      )
    else:
      if args.resource_type == WEB_RESOURCE_TYPE:
        return iap_api.IapSettingsResource(
            release_track, 'projects/{0}/iap_web'.format(args.project)
        )
      elif args.resource_type == APP_ENGINE_RESOURCE_TYPE:
        if not args.service:
          return iap_api.IapSettingsResource(
              release_track,
              'projects/{0}/iap_web/appengine-{1}'.format(
                  args.project, args.project
              ),
          )
        else:
          if args.version:
            return iap_api.IapSettingsResource(
                release_track,
                'projects/{0}/iap_web/appengine-{1}/services/{2}/versions/{3}'
                .format(args.project, args.project, args.service, args.version),
            )
          else:
            return iap_api.IapSettingsResource(
                release_track,
                'projects/{0}/iap_web/appengine-{1}/services/{2}'.format(
                    args.project, args.project, args.service
                ),
            )
      elif (
          args.resource_type == COMPUTE_RESOURCE_TYPE
          or args.resource_type == BACKEND_SERVICES_RESOURCE_TYPE
      ):
        path = ['projects', args.project, 'iap_web']
        if args.region:
          path.append('compute-{}'.format(args.region))
        else:
          path.append('compute')
        if args.service:
          path.extend(['services', args.service])
        return iap_api.IapSettingsResource(release_track, '/'.join(path))
      elif (args.resource_type == FORWARDING_RULE_RESOURCE_TYPE):
        path = ['projects', args.project, 'iap_web']
        if args.version:
          raise calliope_exc.InvalidArgumentException(
              '--version',
              '`--version` cannot be specified for '
              '`--resource-type=forwarding-rule`.',
          )
        if args.region:
          path.append('forwarding_rule-{}'.format(args.region))
        else:
          path.append('forwarding_rule')
        if args.service:
          path.extend(['services', args.service])
        return iap_api.IapSettingsResource(release_track, '/'.join(path))
      elif (args.resource_type == CLOUD_RUN_RESOURCE_TYPE):
        path = ['projects', args.project, 'iap_web']
        if args.version:
          raise calliope_exc.InvalidArgumentException(
              '--version',
              '`--version` cannot be specified for '
              '`--resource-type=cloud-run`.',
          )
        if not args.region:
          raise calliope_exc.InvalidArgumentException(
              '--region',
              '`--region` must be specified for '
              '`--resource-type=cloud-run`.',
          )
        path.append('cloud_run-{}'.format(args.region))
        if args.service:
          path.extend(['services', args.service])
        return iap_api.IapSettingsResource(release_track, '/'.join(path))
      else:
        raise iap_exc.InvalidIapIamResourceError(
            'Unsupported IAP settings resource type.')

  raise iap_exc.InvalidIapIamResourceError(
      'Could not parse IAP settings resource.')


def ParseIapDestGroupResource(release_track, args):
  """Parses an IAP TCP DestGroup resource from the input arguments.

  Args:
    release_track: base.ReleaseTrack, release track of command.
    args: an argparse namespace. All the arguments that were provided to this
      command invocation.

  Returns:
    The specified IAP TCP DestGroup resource.
  """
  project = properties.VALUES.core.project.GetOrFail()
  group = getattr(args, 'group_name', None)
  if group is None:
    group = args.dest_group
  return iap_api.IapTunnelDestGroupResource(release_track, project, args.region,
                                            group)


def ParseIapDestGroupResourceWithNoGroupId(release_track, args):
  """Parses an IAP TCP Tunnel resource from the input arguments.

  Args:
    release_track: base.ReleaseTrack, release track of command.
    args: an argparse namespace. All the arguments that were provided to this
      command invocation.

  Returns:
    The specified IAP TCP Tunnel resource.
  """
  project = properties.VALUES.core.project.GetOrFail()
  return iap_api.IapTunnelDestGroupResource(release_track, project, args.region)


def ParseIapTcpIamResource(release_track, args):
  """Parses an IAP TCP IAM resource from the input arguments."""
  project = properties.VALUES.core.project.GetOrFail()
  if args.resource_type != CLOUD_RUN_RESOURCE_TYPE:
    raise calliope_exc.InvalidArgumentException(
        '--resource-type',
        '`--resource-type` must be specified for IAP TCP IAM resources.',
    )
  if not args.service and not args.instance:
    return iap_api.IapTcpIamResources(
        release_track, project, tunnel_type='cloudRun', region=args.region
    )
  if not args.region:
    flag = '--service' if args.service else '--instance'
    raise calliope_exc.InvalidArgumentException(
        '--region',
        '`--region` must be specified for `--resource-type=cloud-run` with {}.'
        .format(flag),
    )
  return iap_api.IapTcpIamResource(
      release_track,
      project,
      tunnel_type='cloudRun',
      region=args.region,
      service=args.service,
      instance=args.instance,
  )

