# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Common utility functions to consturct compute future reservations message."""


from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute import resource_manager_tags_utils
from googlecloudsdk.command_lib.compute.reservations import util as reservation_util
from googlecloudsdk.core.util import times


def MakeFutureReservationMessageFromArgs(messages, resources, args,
                                         future_reservation_ref):
  """Construct future reservation message from args passed in."""
  local_ssds = reservation_util.MakeLocalSsds(messages,
                                              getattr(args, 'local_ssd', None))
  accelerators = reservation_util.MakeGuestAccelerators(
      messages, getattr(args, 'accelerator', None))

  allocated_instance_properties = MakeAllocatedInstanceProperties(
      messages,
      args.machine_type,
      args.min_cpu_platform,
      local_ssds,
      accelerators,
      getattr(args, 'location_hint', None),
      getattr(args, 'maintenance_freeze_duration', None),
      getattr(args, 'maintenance_interval', None),
  )

  source_instance_template_ref = (
      reservation_util.ResolveSourceInstanceTemplate(args, resources)
      if getattr(args, 'source_instance_template', None)
      else None
  )

  sku_properties = MakeSpecificSKUPropertiesMessage(
      messages,
      allocated_instance_properties,
      args.total_count,
      source_instance_template_ref,
  )

  aggregate_reservation_properties = MakeAggregateReservationPropertiesMessage(
      messages,
      getattr(args, 'tpu_version', None),
      getattr(args, 'chip_count', None),
      getattr(args, 'workload_type', None),
  )

  time_window = MakeTimeWindowMessage(messages, args.start_time,
                                      getattr(args, 'end_time', None),
                                      getattr(args, 'duration', None))
  share_settings = MakeShareSettings(messages, args,
                                     getattr(args, 'share_setting', None))
  planning_status = MakePlanningStatus(messages,
                                       getattr(args, 'planning_status', None))

  enable_auto_delete_reservations = None
  if args.IsSpecified('auto_delete_auto_created_reservations'):
    enable_auto_delete_reservations = getattr(
        args, 'auto_delete_auto_created_reservations'
    )

  auto_created_reservations_delete_time = None
  if args.IsSpecified('auto_created_reservations_delete_time'):
    auto_created_reservations_delete_time = getattr(
        args, 'auto_created_reservations_delete_time'
    )
  auto_created_reservations_duration = None
  if args.IsSpecified('auto_created_reservations_duration'):
    auto_created_reservations_duration = getattr(
        args, 'auto_created_reservations_duration'
    )

  require_specific_reservation = getattr(
      args, 'require_specific_reservation', None
  )

  reservation_name = getattr(args, 'reservation_name', None)

  deployment_type = None
  if args.IsKnownAndSpecified('deployment_type'):
    deployment_type = MakeDeploymentType(messages,
                                         getattr(args, 'deployment_type', None))

  commitment_info = MakeCommitmentInfo(messages, args)
  scheduling_type = None
  if args.IsKnownAndSpecified('scheduling_type'):
    scheduling_type = MakeSchedulingType(
        messages, getattr(args, 'scheduling_type', None)
    )

  reservation_mode = None
  if args.IsKnownAndSpecified('reservation_mode'):
    reservation_mode = MakeReservationMode(
        messages, getattr(args, 'reservation_mode', None)
    )

  enable_emergent_maintenance = None
  if args.IsKnownAndSpecified('enable_emergent_maintenance'):
    enable_emergent_maintenance = getattr(
        args, 'enable_emergent_maintenance', None
    )

  resource_manager_tags = None
  if args.IsKnownAndSpecified('resource_manager_tags'):
    resource_manager_tags = MakeResourceManagerTags(
        messages, getattr(args, 'resource_manager_tags', None)
    )

  return MakeFutureReservationMessage(
      messages,
      future_reservation_ref.Name(),
      time_window,
      share_settings,
      planning_status,
      aggregate_reservation_properties,
      sku_properties,
      enable_auto_delete_reservations,
      auto_created_reservations_delete_time,
      auto_created_reservations_duration,
      require_specific_reservation,
      reservation_name,
      deployment_type,
      commitment_info,
      scheduling_type,
      reservation_mode,
      enable_emergent_maintenance,
      resource_manager_tags,
  )


def MakeAllocatedInstanceProperties(messages,
                                    machine_type,
                                    min_cpu_platform,
                                    local_ssds,
                                    accelerators,
                                    location_hint=None,
                                    freeze_duration=None,
                                    freeze_interval=None):
  """Constructs an instance propteries for reservations message object."""
  if machine_type is None:
    return None

  prop_msgs = (
      messages.AllocationSpecificSKUAllocationReservedInstanceProperties)
  instance_properties = prop_msgs(
      machineType=machine_type,
      guestAccelerators=accelerators,
      minCpuPlatform=min_cpu_platform,
      localSsds=local_ssds)
  if location_hint:
    instance_properties.locationHint = location_hint
  if freeze_duration:
    instance_properties.maintenanceFreezeDurationHours = freeze_duration // 3600
  if freeze_interval:
    instance_properties.maintenanceInterval = (
        messages.AllocationSpecificSKUAllocationReservedInstanceProperties
        .MaintenanceIntervalValueValuesEnum(freeze_interval))
  return instance_properties


def MakeSpecificSKUPropertiesMessage(
    messages,
    instance_properties,
    total_count,
    source_instance_template_ref=None,
):
  """Constructs a specific sku properties message object if any is specified."""
  if instance_properties is None and source_instance_template_ref is None:
    return None

  properties = None
  source_instance_template_url = None
  if source_instance_template_ref:
    source_instance_template_url = source_instance_template_ref.SelfLink()
  else:
    properties = instance_properties
  return messages.FutureReservationSpecificSKUProperties(
      totalCount=total_count,
      sourceInstanceTemplate=source_instance_template_url,
      instanceProperties=properties)


def MakeAggregateReservationPropertiesMessage(
    messages, tpu_version, chip_count, workload_type
):
  """Constructs an aggregate reservation properties message object."""
  if not tpu_version:
    return None

  # Static mapping of TPU version to VM family.
  tpu_version_to_vm_family = {
      'V5E': (
          messages.AllocationAggregateReservation.VmFamilyValueValuesEnum.VM_FAMILY_CLOUD_TPU_LITE_POD_SLICE_CT5LP
      ),
      'V5P': (
          messages.AllocationAggregateReservation.VmFamilyValueValuesEnum.VM_FAMILY_CLOUD_TPU_POD_SLICE_CT5P
      ),
      'V6E': (
          messages.AllocationAggregateReservation.VmFamilyValueValuesEnum.VM_FAMILY_CLOUD_TPU_LITE_POD_SLICE_CT6E
      ),
      'TPU7X': (
          messages.AllocationAggregateReservation.VmFamilyValueValuesEnum.VM_FAMILY_CLOUD_TPU_POD_SLICE_TPU7X
      ),
  }

  reserved_resources = []
  accelerator = (
      messages.AllocationAggregateReservationReservedResourceInfoAccelerator(
          acceleratorCount=chip_count,
      )
  )
  reserved_resources.append(
      messages.AllocationAggregateReservationReservedResourceInfo(
          accelerator=accelerator
      )
  )
  aggregate_reservation_properties = messages.AllocationAggregateReservation(
      vmFamily=messages.AllocationAggregateReservation.VmFamilyValueValuesEnum(
          tpu_version_to_vm_family[tpu_version]
      ),
      reservedResources=reserved_resources,
  )

  if workload_type:
    aggregate_reservation_properties.workloadType = (
        messages.AllocationAggregateReservation.WorkloadTypeValueValuesEnum(
            workload_type
        )
    )
  return aggregate_reservation_properties


def MakeTimeWindowMessage(messages, start_time, end_time, duration):
  """Constructs the time window message object."""
  if end_time:
    return messages.FutureReservationTimeWindow(
        startTime=start_time, endTime=end_time)
  else:
    return messages.FutureReservationTimeWindow(
        startTime=start_time, duration=messages.Duration(seconds=duration))


def MakeShareSettings(messages, args, setting_configs):
  """Constructs the share settings message object."""
  if setting_configs:
    if setting_configs == 'local':
      if args.IsSpecified('share_with'):
        raise exceptions.InvalidArgumentException(
            '--share_with',
            'The scope this reservation is to be shared with must not be '
            'specified with share setting local.')
      return messages.ShareSettings(shareType=messages.ShareSettings
                                    .ShareTypeValueValuesEnum.LOCAL)
    if setting_configs == 'projects':
      if not args.IsSpecified('share_with'):
        raise exceptions.InvalidArgumentException(
            '--share_with',
            'The projects this reservation is to be shared with must be '
            'specified.')
      return messages.ShareSettings(
          shareType=messages.ShareSettings.ShareTypeValueValuesEnum
          .SPECIFIC_PROJECTS,
          projectMap=MakeProjectMapFromProjectList(
              messages, getattr(args, 'share_with', None)))
    if setting_configs == 'folders':
      return messages.ShareSettings(
          shareType=messages.ShareSettings.ShareTypeValueValuesEnum
          .DIRECT_PROJECTS_UNDER_SPECIFIC_FOLDERS,
          folderMap=MakeFolderMapFromFolderList(
              messages, getattr(args, 'share_with', None)))
  else:
    return None


def MakeProjectMapFromProjectList(messages, projects):
  additional_properties = []
  for project in projects:
    additional_properties.append(
        messages.ShareSettings.ProjectMapValue.AdditionalProperty(
            key=project,
            value=messages.ShareSettingsProjectConfig(projectId=project)))
  return messages.ShareSettings.ProjectMapValue(
      additionalProperties=additional_properties)


def MakeFolderMapFromFolderList(messages, folders):
  additional_properties = []
  if folders:
    for folder in folders:
      additional_properties.append(
          messages.ShareSettings.FolderMapValue.AdditionalProperty(
              key=folder,
              value=messages.ShareSettingsFolderConfig(folderId=folder),
          )
      )
  return messages.ShareSettings.FolderMapValue(
      additionalProperties=additional_properties
  )


def MakePlanningStatus(messages, planning_status):
  """Constructs the planning status enum value."""
  if planning_status:
    if planning_status == 'SUBMITTED':
      return messages.FutureReservation.PlanningStatusValueValuesEnum.SUBMITTED
    if planning_status == 'DRAFT':
      return messages.FutureReservation.PlanningStatusValueValuesEnum.DRAFT
  return None


def MakeDeploymentType(messages, deployment_type):
  """Constructs the deployment type enum value."""
  if deployment_type:
    if deployment_type == 'DENSE':
      return messages.FutureReservation.DeploymentTypeValueValuesEnum.DENSE
    if deployment_type == 'FLEXIBLE':
      return messages.FutureReservation.DeploymentTypeValueValuesEnum.FLEXIBLE
  return None


def MakeCommitmentInfo(messages, args):
  """Constructs the commitment info message object."""
  commitment_name = getattr(args, 'commitment_name', None)
  commitment_plan = None
  if args.IsKnownAndSpecified('commitment_plan'):
    commitment_plan = MakeCommitmentPlan(messages,
                                         getattr(args, 'commitment_plan', None))
  previous_commitment_terms = None
  if args.IsKnownAndSpecified('previous_commitment_terms'):
    previous_commitment_terms = MakePreviousCommitmentTerms(
        messages, getattr(args, 'previous_commitment_terms', None)
    )

  if any([commitment_name, commitment_plan, previous_commitment_terms]):
    return messages.FutureReservationCommitmentInfo(
        commitmentName=commitment_name,
        commitmentPlan=commitment_plan,
        previousCommitmentTerms=previous_commitment_terms,
    )
  return None


def MakeCommitmentPlan(messages, commitment_plan):
  """Constructs the commitment plan enum value."""
  if commitment_plan:
    if commitment_plan == 'TWELVE_MONTH':
      return (messages.FutureReservationCommitmentInfo.CommitmentPlanValueValuesEnum.
              TWELVE_MONTH)
    if commitment_plan == 'THIRTY_SIX_MONTH':
      return (messages.FutureReservationCommitmentInfo.CommitmentPlanValueValuesEnum
              .THIRTY_SIX_MONTH)
  return None


def MakePreviousCommitmentTerms(messages, previous_commitment_terms):
  """Constructs the previous commitment terms enum value."""
  if previous_commitment_terms:
    if previous_commitment_terms == 'EXTEND':
      return (messages.FutureReservationCommitmentInfo.PreviousCommitmentTermsValueValuesEnum
              .EXTEND)
  return None


def MakeSchedulingType(messages, scheduling_type):
  """Constructs the scheduling type enum value."""
  if scheduling_type:
    if scheduling_type == 'GROUPED':
      return messages.FutureReservation.SchedulingTypeValueValuesEnum.GROUPED
    if scheduling_type == 'INDEPENDENT':
      return (messages.FutureReservation.SchedulingTypeValueValuesEnum
              .INDEPENDENT)
  return None


def MakeReservationMode(messages, reservation_mode):
  """Constructs the reservation mode enum value."""
  if reservation_mode:
    if reservation_mode == 'CALENDAR':
      return messages.FutureReservation.ReservationModeValueValuesEnum.CALENDAR
    if reservation_mode == 'DEFAULT':
      return messages.FutureReservation.ReservationModeValueValuesEnum.DEFAULT
  return None


def MakeResourceManagerTags(messages, resource_manager_tags):
  """Creates a future reservation params object for resource manager tags."""
  if resource_manager_tags is None:
    return None
  resource_manager_tags_map = (
      resource_manager_tags_utils.GetResourceManagerTags(resource_manager_tags)
  )
  params = messages.FutureReservationParams
  additional_properties = [
      params.ResourceManagerTagsValue.AdditionalProperty(key=key, value=value)
      for key, value in sorted(resource_manager_tags_map.items())
  ]
  return params(
      resourceManagerTags=params.ResourceManagerTagsValue(
          additionalProperties=additional_properties
      )
  )


def MakeFutureReservationMessage(
    messages,
    future_reservation_name,
    time_window,
    share_settings,
    planning_status,
    aggregate_reservation_properties=None,
    sku_properties=None,
    enable_auto_delete_reservations=None,
    auto_created_reservations_delete_time=None,
    auto_created_reservations_duration=None,
    require_specific_reservation=None,
    reservation_name=None,
    deployment_type=None,
    commitment_info=None,
    scheduling_type=None,
    reservation_mode=None,
    enable_emergent_maintenance=None,
    resource_manager_tags=None,
):
  """Constructs a future reservation message object."""
  future_reservation_message = messages.FutureReservation(
      name=future_reservation_name,
      timeWindow=time_window,
      planningStatus=planning_status,
  )

  if aggregate_reservation_properties:
    future_reservation_message.aggregateReservation = (
        aggregate_reservation_properties
    )
  if sku_properties:
    future_reservation_message.specificSkuProperties = sku_properties

  if share_settings:
    future_reservation_message.shareSettings = share_settings

  if enable_auto_delete_reservations is not None:
    future_reservation_message.autoDeleteAutoCreatedReservations = (
        enable_auto_delete_reservations
    )

  if auto_created_reservations_delete_time is not None:
    future_reservation_message.autoCreatedReservationsDeleteTime = (
        times.FormatDateTime(auto_created_reservations_delete_time)
    )
  if auto_created_reservations_duration is not None:
    future_reservation_message.autoCreatedReservationsDuration = (
        messages.Duration(seconds=auto_created_reservations_duration)
    )
  if require_specific_reservation is not None:
    future_reservation_message.specificReservationRequired = (
        require_specific_reservation
    )

  if reservation_name is not None:
    future_reservation_message.reservationName = reservation_name
  if deployment_type is not None:
    future_reservation_message.deploymentType = deployment_type
  if commitment_info is not None:
    future_reservation_message.commitmentInfo = commitment_info
  if scheduling_type is not None:
    future_reservation_message.schedulingType = scheduling_type
  if reservation_mode is not None:
    future_reservation_message.reservationMode = reservation_mode
  if enable_emergent_maintenance is not None:
    future_reservation_message.enableEmergentMaintenance = (
        enable_emergent_maintenance
    )
  if resource_manager_tags is not None:
    future_reservation_message.params = resource_manager_tags

  return future_reservation_message
