# Copyright 2018 Google Inc. 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.
"""General utilties for Cloud Source commands."""

from __future__ import absolute_import
from __future__ import unicode_literals

from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources

_API_NAME = 'sourcerepo'
_API_VERSION = 'v1'

_MESSAGES = apis.GetMessagesModule(_API_NAME, _API_VERSION)


class InvalidTopicError(exceptions.Error):
  """Raised when a topic cannot be found within the repo."""


def CreateProjectResource(args):
  return resources.REGISTRY.Create(
      'sourcerepo.projects',
      projectsId=args.project or properties.VALUES.core.project.GetOrFail())


def CreateTopicResource(topic_name, topic_project):
  return resources.REGISTRY.Create(
      'pubsub.projects.topics',
      projectsId=topic_project,
      topicsId=topic_name,
  )


def ParseProjectConfigWithPushblock(args):
  project_ref = CreateProjectResource(args)
  project_name = project_ref.RelativeName()

  enable_pushblock = args.enable_pushblock
  return _MESSAGES.ProjectConfig(
      enablePrivateKeyCheck=enable_pushblock,
      name=project_name,
      pubsubConfigs=None  # No need to specify when only update key check.
  )


def ParseSourceRepoWithModifiedTopic(args, repo):
  """Parse and create a new Repo message with modified topic."""
  topic_name = GetTopicName(args)
  if args.add_topic:
    new_config = _ParsePubsubConfig(topic_name, args.message_format,
                                    args.service_account)
    return _AddTopicToResource(
        topic_name, new_config, repo, resource_name='repo')
  elif args.remove_topic:
    return _RemoveTopicFromResource(topic_name, repo, resource_name='repo')
  elif args.update_topic:
    return _UpdateTopicInResource(topic_name, args, repo, resource_name='repo')

  return repo


def ParseProjectConfigWithModifiedTopic(args, project_config):
  """Parse and create a new ProjectConfig message with modified topic."""
  topic_name = GetTopicName(args)
  if args.add_topic:
    new_config = _ParsePubsubConfig(topic_name, args.message_format,
                                    args.service_account)
    return _AddTopicToResource(
        topic_name, new_config, project_config, resource_name='project')
  elif args.remove_topic:
    return _RemoveTopicFromResource(
        topic_name, project_config, resource_name='project')
  elif args.update_topic:
    return _UpdateTopicInResource(
        topic_name, args, project_config, resource_name='project')

  return project_config


def GetTopicName(args):
  """Get the topic name based on project and topic_project flags."""
  if args.add_topic:
    topic_ref = args.CONCEPTS.add_topic.Parse()
  elif args.remove_topic:
    topic_ref = args.CONCEPTS.remove_topic.Parse()
  else:
    topic_ref = args.CONCEPTS.update_topic.Parse()

  return topic_ref.RelativeName()


def _AddTopicToResource(topic_name, new_config, resource, resource_name):
  """Add the PubsubConfig message to Repo/ProjectConfig message."""
  if resource.pubsubConfigs is None:
    config_additional_properties = []
  else:
    config_additional_properties = resource.pubsubConfigs.additionalProperties

  resource_msg_module = _MESSAGES.ProjectConfig
  if resource_name == 'repo':
    resource_msg_module = _MESSAGES.Repo

  config_additional_properties.append(
      resource_msg_module.PubsubConfigsValue.AdditionalProperty(
          key=topic_name, value=new_config))

  return resource_msg_module(
      name=resource.name,
      pubsubConfigs=resource_msg_module.PubsubConfigsValue(
          additionalProperties=config_additional_properties))


def _RemoveTopicFromResource(topic_name, resource, resource_name):
  """Remove the topic from the Repo/ProjectConfig message."""
  if resource.pubsubConfigs is None:
    raise InvalidTopicError('Invalid topic [{0}]: No topics are configured '
                            'in the {1}.'.format(topic_name, resource_name))

  config_additional_properties = resource.pubsubConfigs.additionalProperties
  for i, config in enumerate(config_additional_properties):
    if config.key == topic_name:
      del config_additional_properties[i]
      break
  else:
    raise InvalidTopicError('Invalid topic [{0}]: You must specify a '
                            'topic that is already configured in the {1}.'
                            .format(topic_name, resource_name))

  resource_msg_module = _MESSAGES.ProjectConfig
  if resource_name == 'repo':
    resource_msg_module = _MESSAGES.Repo

  return resource_msg_module(
      name=resource.name,
      pubsubConfigs=resource_msg_module.PubsubConfigsValue(
          additionalProperties=config_additional_properties))


def _UpdateTopicInResource(topic_name, args, resource, resource_name):
  """Update the topic in the configuration and return a new repo message."""

  if resource.pubsubConfigs is None:
    raise InvalidTopicError('Invalid topic [{0}]: No topics are configured '
                            'in the {1}.'.format(topic_name, resource_name))

  config_additional_properties = resource.pubsubConfigs.additionalProperties
  for i, config in enumerate(config_additional_properties):
    if config.key == topic_name:
      config_additional_properties[i].value = _UpdateConfigWithArgs(
          config.value, args)
      break
  else:
    raise InvalidTopicError('Invalid topic [{0}]: You must specify a '
                            'topic that is already configured in the {1}.'
                            .format(topic_name, resource_name))

  resource_msg_module = _MESSAGES.ProjectConfig
  if resource_name == 'repo':
    resource_msg_module = _MESSAGES.Repo

  return resource_msg_module(
      name=resource.name,
      pubsubConfigs=resource_msg_module.PubsubConfigsValue(
          additionalProperties=config_additional_properties))


def _ParsePubsubConfig(topic_name, message_format=None, service_account=None):
  """Parse and create PubsubConfig message."""
  message_format_enums = _MESSAGES.PubsubConfig.MessageFormatValueValuesEnum
  if message_format == 'protobuf':
    parsed_message_format = message_format_enums.PROTOBUF
  else:
    # By default, the message format is 'json'.
    parsed_message_format = message_format_enums.JSON

  return _MESSAGES.PubsubConfig(
      messageFormat=parsed_message_format,
      serviceAccountEmail=service_account,
      topic=topic_name)


def _GetMessageFormatString(pubsub_config):
  message_format_type = getattr(pubsub_config, 'messageFormat')
  message_format_enums = _MESSAGES.PubsubConfig.MessageFormatValueValuesEnum
  if message_format_type == message_format_enums.PROTOBUF:
    return 'protobuf'

  return 'json'


def _UpdateConfigWithArgs(pubsub_config, args):
  message_format = args.message_format or _GetMessageFormatString(pubsub_config)
  service_account = args.service_account or getattr(pubsub_config,
                                                    'serviceAccountEmail')
  topic_name = pubsub_config.topic

  return _ParsePubsubConfig(topic_name, message_format, service_account)
