# -*- coding: utf-8 -*- #
# Copyright 2017 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 for sql instance commands."""


import re
from typing import Any, Optional

from googlecloudsdk.api_lib.sql import constants
from googlecloudsdk.api_lib.sql import exceptions as sql_exceptions
from googlecloudsdk.api_lib.sql import instance_prop_reducers as reducers
from googlecloudsdk.api_lib.sql import instances as api_util
from googlecloudsdk.api_lib.sql import validate
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib import info_holder
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import execution_utils
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
import six

DEFAULT_RELEASE_TRACK = base.ReleaseTrack.GA

# Argument mapping to internal storage type.
STORAGE_TYPE_MAPPING = {
    'HDD': 'PD_HDD',
    'SSD': 'PD_SSD',
    'HYPERDISK_BALANCED': 'HYPERDISK_BALANCED',
}


def GetInstanceRef(args, client):
  """Validates and returns the instance reference."""
  validate.ValidateInstanceName(args.instance)
  return client.resource_parser.Parse(
      args.instance,
      params={'project': properties.VALUES.core.project.GetOrFail},
      collection='sql.instances',
  )


def GetDatabaseArgs(args, flags):
  """Gets the args for specifying a database during instance connection."""
  command_line_args = []
  if args.IsSpecified('database'):
    try:
      command_line_args.extend([flags['database'], args.database])
    except KeyError:
      raise exceptions.InvalidArgumentException(
          '--database', 'This instance does not support the database argument.'
      )
  return command_line_args


def ConnectToInstance(cmd_args, sql_user):
  """Connects to the instance using the relevant CLI."""
  try:
    log.status.write(
        'Connecting to database with SQL user [{0}].'.format(sql_user)
    )
    execution_utils.Exec(cmd_args)
  except OSError:
    log.error('Failed to execute command "{0}"'.format(' '.join(cmd_args)))
    log.Print(info_holder.InfoHolder())


def _GetAndValidateCmekKeyName(args, is_primary):
  """Parses the CMEK resource arg, makes sure the key format was correct."""
  kms_ref = args.CONCEPTS.kms_key.Parse()
  if kms_ref:
    # Since CMEK is required for replicas of CMEK primaries, this prompt is only
    # actionable for primary instances.
    if is_primary:
      _ShowCmekPrompt()
    return kms_ref.RelativeName()
  else:
    # Check for partially specified disk-encryption-key.
    for keyword in [
        'disk-encryption-key',
        'disk-encryption-key-keyring',
        'disk-encryption-key-location',
        'disk-encryption-key-project',
    ]:
      if getattr(args, keyword.replace('-', '_'), None):
        raise exceptions.InvalidArgumentException(
            '--disk-encryption-key', 'not fully specified.'
        )


def _GetZone(args):
  return args.zone or args.gce_zone


def _GetSecondaryZone(args):
  if 'secondary_zone' in args:
    return args.secondary_zone

  return None


def DoesEnterprisePlusReplicaInferTierForDatabaseType(replica_database_version):
  """Checks if the replica inherits the tier of the primary for E+ instances.

  Ideally, this would be the case for all database versions. However, changing
  it now is technically a breaking change, so we are only adding support for new
  database types going forward.

  Args:
    replica_database_version: The database version of the replica.

  Returns:
    True if the replica infers the tier from the primary (database version is
    PG16+).
  """
  if replica_database_version.startswith('POSTGRES_'):
    # Extract the database major version from the database version string:
    # POSTGRES_16 -> 16
    database_major_version = int(
        re.search(r'^POSTGRES_(\d+).*', replica_database_version).group(1)
    )
    return database_major_version > 15
  if replica_database_version.startswith('MYSQL_'):
    # Extract the database major version from the database version string:
    # MYSQL_8_4 -> 8.4
    match = re.search(r'^MYSQL_(\d+)_(\d+).*', replica_database_version)
    database_major_version = (int(match.group(1)), int(match.group(2)))
    return database_major_version > (8, 0)
  return False


def _IsAlpha(release_track):
  return release_track == base.ReleaseTrack.ALPHA


def IsBetaOrNewer(release_track):
  return release_track == base.ReleaseTrack.BETA or _IsAlpha(release_track)


def _ParseActivationPolicy(sql_messages, policy):
  if policy:
    return sql_messages.Settings.ActivationPolicyValueValuesEnum.lookup_by_name(
        policy.replace('-', '_').upper()
    )
  return None


def _ParseAvailabilityType(sql_messages, availability_type):
  if availability_type:
    return sql_messages.Settings.AvailabilityTypeValueValuesEnum.lookup_by_name(
        availability_type.upper()
    )
  return None


def ParseDatabaseVersion(sql_messages, database_version):
  if database_version:
    return sql_messages.DatabaseInstance.DatabaseVersionValueValuesEnum.lookup_by_name(
        database_version.upper()
    )
  return None


def _ParsePricingPlan(sql_messages, pricing_plan):
  if pricing_plan:
    return sql_messages.Settings.PricingPlanValueValuesEnum.lookup_by_name(
        pricing_plan.upper()
    )
  return None


def _ParseReplicationType(sql_messages, replication):
  if replication:
    return sql_messages.Settings.ReplicationTypeValueValuesEnum.lookup_by_name(
        replication.upper()
    )
  return None


def _ParseStorageType(sql_messages, storage_type):
  if storage_type:
    return sql_messages.Settings.DataDiskTypeValueValuesEnum.lookup_by_name(
        storage_type.upper()
    )
  return None


def _ParseEdition(sql_messages, edition):
  if edition:
    return sql_messages.Settings.EditionValueValuesEnum.lookup_by_name(
        edition.replace('-', '_').upper()
    )
  return None


def _ParseInstanceType(sql_messages, instance_type):
  if instance_type:
    return sql_messages.DatabaseInstance.InstanceTypeValueValuesEnum.lookup_by_name(
        instance_type.replace('-', '_').upper()
    )
  return None


def _ParseBackendType(sql_messages, backend_type):
  if backend_type:
    return (
        sql_messages.DatabaseInstance.BackendTypeValueValuesEnum.lookup_by_name(
            backend_type.replace('-', '_').upper()
        )
    )
  return None


# The caller must make sure that the connector_enforcement is specified.
def _ParseConnectorEnforcement(sql_messages, connector_enforcement):
  return (
      sql_messages.Settings.ConnectorEnforcementValueValuesEnum.lookup_by_name(
          connector_enforcement.upper()
      )
  )


def _ParseDataApiAccess(sql_messages, data_api_access):
  return (
      sql_messages.Settings.DataApiAccessValueValuesEnum.lookup_by_name(
          data_api_access.upper()
      )
  )


def _ParseSslMode(sql_messages, ssl_mode):
  return sql_messages.IpConfiguration.SslModeValueValuesEnum.lookup_by_name(
      ssl_mode.upper()
  )


def _ParseServerCaMode(sql_messages, server_ca_mode):
  return (
      sql_messages.IpConfiguration.ServerCaModeValueValuesEnum.lookup_by_name(
          server_ca_mode.upper()
      )
  )


def _ParseServerCertificateRotationMode(sql_messages, server_certificate_rotation_mode):
  return (
      sql_messages.IpConfiguration.ServerCertificateRotationModeValueValuesEnum.lookup_by_name(
          server_certificate_rotation_mode.upper()
      )
  )


def _ParseMsdtcRpcAuthMode(
    sql_messages: Any, msdtc_rpc_auth_mode: Optional[str]
) -> Optional[Any]:
  if msdtc_rpc_auth_mode:
    return sql_messages.Settings.MsdtcRpcAuthModeValueValuesEnum.lookup_by_name(
        msdtc_rpc_auth_mode.replace('-', '_').upper()
    )
  return None


# TODO(b/122660263): Remove when V1 instances are no longer supported.
def ShowV1DeprecationWarning(plural=False):
  message = (
      'Upgrade your First Generation instance{} to Second Generation before we '
      'auto-upgrade {} on March 4, 2020, ahead of the full decommission of '
      'First Generation on March 25, 2020.'
  )
  if plural:
    log.warning(message.format('s', 'them'))
  else:
    log.warning(message.format('', 'it'))


def ShowZoneDeprecationWarnings(args):
  """Show warnings if both region and zone are specified or neither is.

  Args:
      args: argparse.Namespace, The arguments that the command was invoked with.
  """

  region_specified = args.IsSpecified('region')
  zone_specified = args.IsSpecified('gce_zone') or args.IsSpecified('zone')

  # TODO(b/73362371): Remove this check; user must specify a location flag.
  if not (region_specified or zone_specified):
    log.warning(
        'Starting with release 233.0.0, you will need to specify '
        'either a region or a zone to create an instance.'
    )


def ShowCmekWarning(resource_type_label, instance_type_label=None):
  if instance_type_label is None:
    log.warning(
        'Your {} will be encrypted with a customer-managed key. If anyone '
        'destroys this key, all data encrypted with it will be permanently '
        'lost.'.format(resource_type_label)
    )
  else:
    log.warning(
        "Your {} will be encrypted with {}'s customer-managed encryption key. "
        'If anyone destroys this key, all data encrypted with it will be '
        'permanently lost.'.format(resource_type_label, instance_type_label)
    )


def _ShowCmekPrompt():
  log.warning(
      'You are creating a Cloud SQL instance encrypted with a customer-managed '
      'key. If anyone destroys a customer-managed key, all data encrypted with '
      'it will be permanently lost.\n'
  )
  console_io.PromptContinue(cancel_on_no=True)


def _ShowFailoverReplicaDeprecationWarning():
  log.warning(
      'Failover replicas will soon be deprecated and support will be '
      'discontinued. We recommend migrating to the new '
      'high availability configuration, based on regional persistent '
      'disks (RePD). For more information, see '
      'https://cloud.google.com/sql/docs/mysql/configure-legacy-ha#update-from-legacy'
  )


def _ShowAcceleratedReplicaModeWarning():
  log.warning(
      'Enabling accelerated replica mode on a replica may cause it to be '
      'recreated in the event of an unplanned restart.'
  )


class _BaseInstances(object):
  """Common utility functions for sql instance commands."""

  @classmethod
  def _ConstructBaseSettingsFromArgs(
      cls,
      sql_messages,
      args,
      instance=None,
      release_track=DEFAULT_RELEASE_TRACK,
  ):
    """Constructs instance settings from the command line arguments.

    Args:
      sql_messages: module, The messages module that should be used.
      args: argparse.Namespace, The arguments that this command was invoked
        with.
      instance: sql_messages.DatabaseInstance, The original instance, for
        settings that depend on the previous state.
      release_track: base.ReleaseTrack, the release track that this was run
        under.

    Returns:
      A settings object representing the instance settings.

    Raises:
      ToolException: An error other than http error occurred while executing the
          command.
    """

    # This code is shared by create and patch, but these args don't exist in
    # create anymore, so insert them here to avoid regressions below.
    if 'authorized_gae_apps' not in args:
      args.authorized_gae_apps = None
    if 'follow_gae_app' not in args:
      args.follow_gae_app = None
    if 'pricing_plan' not in args:
      args.pricing_plan = 'PER_USE'
    if not args.IsKnownAndSpecified('replication'):
      args.replication = None

    settings = sql_messages.Settings(
        kind='sql#settings',
        tier=reducers.MachineType(instance, args.tier, args.memory, args.cpu),
        pricingPlan=_ParsePricingPlan(sql_messages, args.pricing_plan),
        replicationType=_ParseReplicationType(sql_messages, args.replication),
        activationPolicy=_ParseActivationPolicy(
            sql_messages, args.activation_policy
        ),
    )

    if args.authorized_gae_apps:
      settings.authorizedGaeApplications = args.authorized_gae_apps

    if any([
        args.assign_ip is not None,
        args.require_ssl is not None,
        args.authorized_networks,
    ]):
      settings.ipConfiguration = sql_messages.IpConfiguration()
      if args.assign_ip is not None:
        cls.SetIpConfigurationEnabled(settings, args.assign_ip)

      if args.authorized_networks:
        cls.SetAuthorizedNetworks(
            settings, args.authorized_networks, sql_messages.AclEntry
        )

      if args.require_ssl is not None:
        settings.ipConfiguration.requireSsl = args.require_ssl

    if any([args.follow_gae_app, _GetZone(args), _GetSecondaryZone(args)]):
      settings.locationPreference = sql_messages.LocationPreference(
          kind='sql#locationPreference',
          followGaeApplication=args.follow_gae_app,
          zone=_GetZone(args),
          secondaryZone=_GetSecondaryZone(args),
      )

    if args.storage_size:
      settings.dataDiskSizeGb = int(args.storage_size / constants.BYTES_TO_GB)

    if args.IsKnownAndSpecified('storage_provisioned_iops'):
      settings.dataDiskProvisionedIops = int(args.storage_provisioned_iops)

    if args.IsKnownAndSpecified('storage_provisioned_throughput'):
      settings.dataDiskProvisionedThroughput = int(
          args.storage_provisioned_throughput
      )

    if args.storage_auto_increase is not None:
      settings.storageAutoResize = args.storage_auto_increase

    if args.IsSpecified('availability_type'):
      settings.availabilityType = _ParseAvailabilityType(
          sql_messages, args.availability_type
      )

    if args.IsSpecified('network'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.privateNetwork = reducers.PrivateNetworkUrl(
          args.network
      )

    if args.IsKnownAndSpecified('enable_google_private_path'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.enablePrivatePathForGoogleCloudServices = (
          args.enable_google_private_path
      )

    if args.IsKnownAndSpecified('enable_private_service_connect'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscEnabled = (
          args.enable_private_service_connect
      )

    if args.IsSpecified('allowed_psc_projects'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.allowedConsumerProjects = (
          args.allowed_psc_projects
      )

    if args.IsKnownAndSpecified('enable_psc_auto_dns'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscAutoDnsEnabled = (
          args.enable_psc_auto_dns
      )

    if args.IsKnownAndSpecified('enable_psc_write_endpoint_dns'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscWriteEndpointDnsEnabled = (
          args.enable_psc_write_endpoint_dns
      )

    if args.IsKnownAndSpecified('clear_allowed_psc_projects'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.allowedConsumerProjects = []

    if args.IsKnownAndSpecified('psc_network_attachment_uri'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.networkAttachmentUri = (
          args.psc_network_attachment_uri
      )

    if args.IsKnownAndSpecified('clear_psc_network_attachment_uri'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.networkAttachmentUri = ''

    if args.deletion_protection is not None:
      settings.deletionProtectionEnabled = args.deletion_protection

    if args.IsSpecified('connector_enforcement'):
      settings.connectorEnforcement = _ParseConnectorEnforcement(
          sql_messages, args.connector_enforcement
      )

    if args.IsKnownAndSpecified('recreate_replicas_on_primary_crash'):
      settings.recreateReplicasOnPrimaryCrash = (
          args.recreate_replicas_on_primary_crash
      )

    if args.IsSpecified('edition'):
      settings.edition = _ParseEdition(sql_messages, args.edition)

    if args.IsKnownAndSpecified('enable_data_cache'):
      if not settings.dataCacheConfig:
        settings.dataCacheConfig = sql_messages.DataCacheConfig()
      settings.dataCacheConfig.dataCacheEnabled = args.enable_data_cache

    if args.IsSpecified('ssl_mode'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.sslMode = _ParseSslMode(
          sql_messages, args.ssl_mode
      )

    if args.enable_google_ml_integration is not None:
      settings.enableGoogleMlIntegration = args.enable_google_ml_integration

    if args.enable_dataplex_integration is not None:
      settings.enableDataplexIntegration = args.enable_dataplex_integration

    if args.IsKnownAndSpecified('server_ca_mode'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.serverCaMode = _ParseServerCaMode(
          sql_messages, args.server_ca_mode
      )

    if args.retain_backups_on_delete is not None:
      settings.retainBackupsOnDelete = args.retain_backups_on_delete

    if args.IsKnownAndSpecified('msdtc_enabled'):
      settings.msdtcEnabled = args.msdtc_enabled

    if (
        args.IsKnownAndSpecified('msdtc_host_mappings')
        and args.msdtc_host_mappings is not None
    ):
      settings.hostMappings = [
          sql_messages.HostMapping(ipAddress=ip, hostname=name)
          for ip, name in args.msdtc_host_mappings.items()
      ]

    if args.IsKnownAndSpecified('msdtc_rpc_auth_mode'):
      settings.msdtcRpcAuthMode = _ParseMsdtcRpcAuthMode(
          sql_messages, args.msdtc_rpc_auth_mode
      )

    if args.IsKnownAndSpecified('enable_auto_upgrade_minor_version'):
      settings.autoUpgradeEnabled = True

    if args.IsKnownAndSpecified('server_ca_pool'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.serverCaPool = args.server_ca_pool
    # Customers can set `--server-ca-pool=''` to explicitly clear the
    # server_ca_pool field.
    if args.IsKnownAndSpecified('server_ca_pool') and args.server_ca_pool:
      if (
          settings.ipConfiguration.serverCaMode
          != sql_messages.IpConfiguration.ServerCaModeValueValuesEnum.CUSTOMER_MANAGED_CAS_CA
      ):
        raise sql_exceptions.ArgumentError(
            '`--server-ca-pool` can only be specified when the server CA mode'
            ' is CUSTOMER_MANAGED_CAS_CA.'
        )
    elif (
        settings.ipConfiguration
        and settings.ipConfiguration.serverCaMode
        == sql_messages.IpConfiguration.ServerCaModeValueValuesEnum.CUSTOMER_MANAGED_CAS_CA
    ):
      raise exceptions.RequiredArgumentException(
          '--server-ca-pool',
          (
              'To create an instance with server CA mode '
              'CUSTOMER_MANAGED_CAS_CA, [--server-ca-pool] must be '
              'specified.'
          ),
      )

    if args.IsKnownAndSpecified('server_certificate_rotation_mode'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.serverCertificateRotationMode = (
          _ParseServerCertificateRotationMode(
              sql_messages, args.server_certificate_rotation_mode
          )
      )

    if args.IsKnownAndSpecified('custom_subject_alternative_names'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.customSubjectAlternativeNames = (
          args.custom_subject_alternative_names
      )

    if args.IsKnownAndSpecified('clear_custom_subject_alternative_names'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.customSubjectAlternativeNames = []

    if args.IsKnownAndSpecified('data_api_access'):
      settings.dataApiAccess = _ParseDataApiAccess(
          sql_messages, args.data_api_access
      )

    if args.IsKnownAndSpecified('storage_auto_increase_limit'):
      # Resize limit should be settable if the original instance has resize
      # turned on, or if the instance to be created has resize flag.
      if (instance and instance.settings.storageAutoResize) or (
          args.storage_auto_increase
      ):
        # If the limit is set to None, we want it to be set to 0. This is a
        # backend requirement.
        settings.storageAutoResizeLimit = args.storage_auto_increase_limit or 0
      else:
        raise exceptions.RequiredArgumentException(
            '--storage-auto-increase',
            (
                'To set the storage capacity limit '
                'using [--storage-auto-increase-limit], '
                '[--storage-auto-increase] must be enabled.'
            ),
        )

    # BETA args.
    if IsBetaOrNewer(release_track):
      if args.replication_lag_max_seconds_for_recreate is not None:
        settings.replicationLagMaxSeconds = (
            args.replication_lag_max_seconds_for_recreate
        )

      if args.IsKnownAndSpecified('enable_db_aligned_atomic_writes'):
        if not settings.dbAlignedAtomicWritesConfig:
          settings.dbAlignedAtomicWritesConfig = (
              sql_messages.DbAlignedAtomicWritesConfig()
          )
        settings.dbAlignedAtomicWritesConfig.dbAlignedAtomicWrites = (
            args.enable_db_aligned_atomic_writes
        )

      if args.IsKnownAndSpecified('enable_accelerated_replica_mode'):
        _ShowAcceleratedReplicaModeWarning()
        settings.acceleratedReplicaMode = args.enable_accelerated_replica_mode

    return settings

  @classmethod
  def _ConstructCreateSettingsFromArgs(
      cls,
      sql_messages,
      args,
      instance=None,
      release_track=DEFAULT_RELEASE_TRACK,
  ):
    """Constructs create settings object from base settings and args."""
    original_settings = instance.settings if instance else None
    settings = cls._ConstructBaseSettingsFromArgs(
        sql_messages, args, instance, release_track
    )

    backup_configuration = reducers.BackupConfiguration(
        sql_messages,
        instance,
        backup_enabled=args.backup,
        backup_location=args.backup_location,
        backup_start_time=args.backup_start_time,
        enable_bin_log=args.enable_bin_log,
        enable_point_in_time_recovery=args.enable_point_in_time_recovery,
        retained_backups_count=args.retained_backups_count,
        retained_transaction_log_days=args.retained_transaction_log_days,
        patch_request=False,
    )
    if backup_configuration:
      cls.AddBackupConfigToSettings(settings, backup_configuration)

    if args and args.IsKnownAndSpecified('database_flags'):
      settings.databaseFlags = reducers.DatabaseFlags(
          sql_messages, original_settings, database_flags=args.database_flags
      )

    settings.maintenanceWindow = reducers.MaintenanceWindow(
        sql_messages,
        instance,
        maintenance_release_channel=args.maintenance_release_channel,
        maintenance_window_day=args.maintenance_window_day,
        maintenance_window_hour=args.maintenance_window_hour,
    )

    if (
        args.deny_maintenance_period_start_date
        and args.deny_maintenance_period_end_date
    ):
      settings.denyMaintenancePeriods = []
      settings.denyMaintenancePeriods.append(
          reducers.DenyMaintenancePeriod(
              sql_messages,
              instance,
              deny_maintenance_period_start_date=args.deny_maintenance_period_start_date,
              deny_maintenance_period_end_date=args.deny_maintenance_period_end_date,
              deny_maintenance_period_time=args.deny_maintenance_period_time,
          )
      )

    settings.insightsConfig = reducers.InsightsConfig(
        sql_messages,
        insights_config_query_insights_enabled=args.insights_config_query_insights_enabled,
        insights_config_enhanced_query_insights_enabled=args.insights_config_enhanced_query_insights_enabled,
        insights_config_query_string_length=args.insights_config_query_string_length,
        insights_config_record_application_tags=args.insights_config_record_application_tags,
        insights_config_record_client_address=args.insights_config_record_client_address,
        insights_config_query_plans_per_minute=args.insights_config_query_plans_per_minute,
    )

    if args.storage_type:
      settings.dataDiskType = _ParseStorageType(
          sql_messages, STORAGE_TYPE_MAPPING.get(args.storage_type)
      )

    settings.activeDirectoryConfig = reducers.ActiveDirectoryConfig(
        sql_messages,
        domain=args.active_directory_domain,
        mode=args.active_directory_mode,
        dns_servers=args.active_directory_dns_servers,
        admin_credential_secret_key=args.active_directory_secret_manager_key,
        organizational_unit=args.active_directory_organizational_unit,
    )

    if args.IsKnownAndSpecified('password_policy_min_length'):
      settings.passwordValidationPolicy = reducers.PasswordPolicy(
          sql_messages,
          password_policy_min_length=args.password_policy_min_length,
          password_policy_complexity=args.password_policy_complexity,
          password_policy_reuse_interval=args.password_policy_reuse_interval,
          password_policy_disallow_username_substring=args.password_policy_disallow_username_substring,
          password_policy_password_change_interval=args.password_policy_password_change_interval,
          enable_password_policy=args.enable_password_policy,
      )

    sql_server_password_policy = reducers.SqlServerPasswordValidationPolicy(
        sql_messages,
        sql_server_password_policy_min_length=getattr(
            args, 'sql_server_password_policy_min_length', None
        ),
        sql_server_password_policy_history_length=getattr(
            args, 'sql_server_password_policy_history_length', None
        ),
        sql_server_password_policy_minimum_age=getattr(
            args, 'sql_server_password_policy_minimum_age', None
        ),
        sql_server_password_policy_maximum_age=getattr(
            args, 'sql_server_password_policy_maximum_age', None
        ),
        sql_server_enable_password_policy=getattr(
            args, 'sql_server_enable_password_policy', None
        ),
    )
    if sql_server_password_policy is not None:
      settings.sqlServerPasswordValidationPolicy = sql_server_password_policy

    settings.sqlServerAuditConfig = reducers.SqlServerAuditConfig(
        sql_messages,
        args.audit_bucket_path,
        args.audit_retention_interval,
        args.audit_upload_interval,
    )

    if args.time_zone is not None:
      settings.timeZone = args.time_zone

    if args.IsKnownAndSpecified(
        'entra_id_tenant_id'
    ) or args.IsKnownAndSpecified('entra_id_application_id'):
      settings.entraidConfig = reducers.SqlServerEntraIdConfig(
          sql_messages,
          args.entra_id_tenant_id,
          args.entra_id_application_id,
      )

    if (
        args.IsKnownAndSpecified('threads_per_core')
        and args.threads_per_core is not None
    ):
      settings.advancedMachineFeatures = sql_messages.AdvancedMachineFeatures()
      settings.advancedMachineFeatures.threadsPerCore = args.threads_per_core

    if args.IsSpecified('psc_auto_connections'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscAutoConnections = (
          reducers.PscAutoConnections(sql_messages, args.psc_auto_connections)
      )

    if args.IsKnownAndSpecified('enable_psc_auto_connection_policy'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscAutoConnectionPolicyEnabled = (
          args.enable_psc_auto_connection_policy
      )

    final_backup_configuration = reducers.FinalBackupConfiguration(
        sql_messages,
        instance,
        final_backup_enabled=args.final_backup,
        final_backup_retention_days=args.final_backup_retention_days,
    )
    if final_backup_configuration:
      cls.AddFinalBackupConfigToSettings(settings, final_backup_configuration)

    # MCP settings.
    if args.IsKnownAndSpecified(
        'enable_connection_pooling'
    ) or args.IsKnownAndSpecified('connection_pool_flags'):
      mcp_config = reducers.ConnectionPoolConfig(
          sql_messages,
          enable_connection_pooling=args.enable_connection_pooling,
          connection_pool_flags=args.connection_pool_flags,
          clear_connection_pool_flags=None,
          current_config=None,
      )
      if mcp_config is not None:
        settings.connectionPoolConfig = mcp_config

    read_pool_auto_scale_config = reducers.ReadPoolAutoScaleConfig(
        sql_messages,
        auto_scale_enabled=args.auto_scale_enabled,
        auto_scale_min_node_count=args.auto_scale_min_node_count,
        auto_scale_max_node_count=args.auto_scale_max_node_count,
        auto_scale_target_metrics=args.auto_scale_target_metrics,
        auto_scale_disable_scale_in=args.auto_scale_disable_scale_in,
        auto_scale_in_cooldown_seconds=args.auto_scale_in_cooldown_seconds,
        auto_scale_out_cooldown_seconds=args.auto_scale_out_cooldown_seconds,
    )
    if read_pool_auto_scale_config is not None:
      settings.readPoolAutoScaleConfig = read_pool_auto_scale_config

    if args.IsKnownAndSpecified('performance_capture_config'):
      performance_capture_config = reducers.PerformanceCaptureConfig(
          sql_messages,
          performance_capture_config=args.performance_capture_config,
          current_config=None,
      )
      if performance_capture_config is not None:
        settings.performanceCaptureConfig = performance_capture_config

    # BETA args.
    if IsBetaOrNewer(release_track):
      settings.userLabels = labels_util.ParseCreateArgs(
          args, sql_messages.Settings.UserLabelsValue
      )

      if args.allocated_ip_range_name:
        if not settings.ipConfiguration:
          settings.ipConfiguration = sql_messages.IpConfiguration()
        settings.ipConfiguration.allocatedIpRange = args.allocated_ip_range_name

      db_aligned_atomic_writes_config = reducers.DbAlignedAtomicWritesConfig(
          sql_messages,
          db_aligned_atomic_writes=args.enable_db_aligned_atomic_writes,
      )
      if db_aligned_atomic_writes_config is not None:
        settings.dbAlignedAtomicWritesConfig = db_aligned_atomic_writes_config

      if args.IsKnownAndSpecified('unc_mappings'):
        settings.uncMappings = reducers.UncMappings(
            sql_messages,
            unc_mappings=args.unc_mappings,
        )

    return settings

  @classmethod
  def _ConstructPatchSettingsFromArgs(
      cls, sql_messages, args, instance, release_track=DEFAULT_RELEASE_TRACK
  ):
    """Constructs patch settings object from base settings and args."""
    original_settings = instance.settings
    settings = cls._ConstructBaseSettingsFromArgs(
        sql_messages, args, instance, release_track
    )

    if args.clear_gae_apps:
      settings.authorizedGaeApplications = []

    if any([args.follow_gae_app, _GetZone(args), _GetSecondaryZone(args)]):
      settings.locationPreference = sql_messages.LocationPreference(
          kind='sql#locationPreference',
          followGaeApplication=args.follow_gae_app,
          zone=_GetZone(args),
          secondaryZone=_GetSecondaryZone(args),
      )

    if args.clear_authorized_networks:
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      settings.ipConfiguration.authorizedNetworks = []

    if args.enable_database_replication is not None:
      settings.databaseReplicationEnabled = args.enable_database_replication

    backup_configuration = reducers.BackupConfiguration(
        sql_messages,
        instance,
        backup_enabled=not args.no_backup,
        backup_location=args.backup_location,
        backup_start_time=args.backup_start_time,
        enable_bin_log=args.enable_bin_log,
        enable_point_in_time_recovery=args.enable_point_in_time_recovery,
        retained_backups_count=args.retained_backups_count,
        retained_transaction_log_days=args.retained_transaction_log_days,
        patch_request=True,
    )

    if backup_configuration:
      cls.AddBackupConfigToSettings(settings, backup_configuration)

    if args and args.IsKnownAndSpecified('database_flags'):
      settings.databaseFlags = reducers.DatabaseFlags(
          sql_messages,
          original_settings,
          database_flags=args.database_flags,
          clear_database_flags=args.clear_database_flags,
      )

    settings.maintenanceWindow = reducers.MaintenanceWindow(
        sql_messages,
        instance,
        maintenance_release_channel=args.maintenance_release_channel,
        maintenance_window_day=args.maintenance_window_day,
        maintenance_window_hour=args.maintenance_window_hour,
    )

    if args.remove_deny_maintenance_period:
      settings.denyMaintenancePeriods = []

    if (
        args.deny_maintenance_period_start_date
        or args.deny_maintenance_period_end_date
        or args.deny_maintenance_period_time
    ):
      settings.denyMaintenancePeriods = []
      settings.denyMaintenancePeriods.append(
          reducers.DenyMaintenancePeriod(
              sql_messages,
              instance,
              deny_maintenance_period_start_date=args.deny_maintenance_period_start_date,
              deny_maintenance_period_end_date=args.deny_maintenance_period_end_date,
              deny_maintenance_period_time=args.deny_maintenance_period_time,
          )
      )

    if args.storage_type:
      settings.dataDiskType = _ParseStorageType(
          sql_messages, STORAGE_TYPE_MAPPING.get(args.storage_type)
      )

    settings.insightsConfig = reducers.InsightsConfig(
        sql_messages,
        insights_config_query_insights_enabled=args.insights_config_query_insights_enabled,
        insights_config_enhanced_query_insights_enabled=args.insights_config_enhanced_query_insights_enabled,
        insights_config_query_string_length=args.insights_config_query_string_length,
        insights_config_record_application_tags=args.insights_config_record_application_tags,
        insights_config_record_client_address=args.insights_config_record_client_address,
        insights_config_query_plans_per_minute=args.insights_config_query_plans_per_minute,
    )

    settings.activeDirectoryConfig = reducers.ActiveDirectoryConfig(
        sql_messages,
        domain=args.active_directory_domain,
        mode=args.active_directory_mode,
        dns_servers=args.active_directory_dns_servers,
        admin_credential_secret_key=args.active_directory_secret_manager_key,
        organizational_unit=args.active_directory_organizational_unit,
    )

    settings.passwordValidationPolicy = reducers.PasswordPolicy(
        sql_messages,
        password_policy_min_length=args.password_policy_min_length,
        password_policy_complexity=args.password_policy_complexity,
        password_policy_reuse_interval=args.password_policy_reuse_interval,
        password_policy_disallow_username_substring=args.password_policy_disallow_username_substring,
        password_policy_password_change_interval=args.password_policy_password_change_interval,
        enable_password_policy=args.enable_password_policy,
    )

    settings.sqlServerPasswordValidationPolicy = (
        reducers.SqlServerPasswordValidationPolicy(
            sql_messages,
            sql_server_password_policy_min_length=getattr(
                args, 'sql_server_password_policy_min_length', None
            ),
            sql_server_password_policy_history_length=getattr(
                args, 'sql_server_password_policy_history_length', None
            ),
            sql_server_password_policy_minimum_age=getattr(
                args, 'sql_server_password_policy_minimum_age', None
            ),
            sql_server_password_policy_maximum_age=getattr(
                args, 'sql_server_password_policy_maximum_age', None
            ),
            sql_server_enable_password_policy=getattr(
                args, 'sql_server_enable_password_policy', None
            ),
            clear_sql_server_password_policy=args.IsKnownAndSpecified(
                'clear_sql_server_password_policy'
            ),
        )
    )

    settings.sqlServerAuditConfig = reducers.SqlServerAuditConfig(
        sql_messages,
        bucket=args.audit_bucket_path,
        retention_interval=args.audit_retention_interval,
        upload_interval=args.audit_upload_interval,
    )

    if args.threads_per_core is not None:
      settings.advancedMachineFeatures = sql_messages.AdvancedMachineFeatures()
      settings.advancedMachineFeatures.threadsPerCore = args.threads_per_core

    if args.time_zone is not None:
      settings.timeZone = args.time_zone

    if (
        args.IsKnownAndSpecified('entra_id_tenant_id')
        or args.IsKnownAndSpecified('entra_id_application_id')
        or args.IsKnownAndSpecified('clear_entra_id_config')
    ):
      settings.entraidConfig = reducers.SqlServerEntraIdConfig(
          sql_messages,
          args.entra_id_tenant_id,
          args.entra_id_application_id,
          args.clear_entra_id_config,
      )

    final_backup_configuration = reducers.FinalBackupConfiguration(
        sql_messages,
        instance,
        final_backup_enabled=args.final_backup,
        final_backup_retention_days=args.final_backup_retention_days,
    )
    if final_backup_configuration:
      cls.AddFinalBackupConfigToSettings(settings, final_backup_configuration)

    # MCP settings.
    updated_config = reducers.ConnectionPoolConfig(
        sql_messages,
        enable_connection_pooling=args.enable_connection_pooling,
        connection_pool_flags=args.connection_pool_flags,
        clear_connection_pool_flags=args.clear_connection_pool_flags,
        current_config=original_settings.connectionPoolConfig,
    )
    if updated_config is not None:
      settings.connectionPoolConfig = updated_config

    if args.IsKnownAndSpecified('psc_auto_connections'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscAutoConnections = (
          reducers.PscAutoConnections(sql_messages, args.psc_auto_connections)
      )
    if args.IsKnownAndSpecified('clear_psc_auto_connections'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscAutoConnections = []

    if args.IsKnownAndSpecified('enable_psc_auto_dns'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscAutoDnsEnabled = (
          args.enable_psc_auto_dns
      )

    if args.IsKnownAndSpecified('enable_psc_write_endpoint_dns'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscWriteEndpointDnsEnabled = (
          args.enable_psc_write_endpoint_dns
      )

    if args.IsKnownAndSpecified('enable_psc_auto_connection_policy'):
      if not settings.ipConfiguration:
        settings.ipConfiguration = sql_messages.IpConfiguration()
      if not settings.ipConfiguration.pscConfig:
        settings.ipConfiguration.pscConfig = sql_messages.PscConfig()
      settings.ipConfiguration.pscConfig.pscAutoConnectionPolicyEnabled = (
          args.enable_psc_auto_connection_policy
      )

    read_pool_auto_scale_config = reducers.ReadPoolAutoScaleConfig(
        sql_messages,
        auto_scale_enabled=args.auto_scale_enabled,
        auto_scale_min_node_count=args.auto_scale_min_node_count,
        auto_scale_max_node_count=args.auto_scale_max_node_count,
        auto_scale_target_metrics=args.auto_scale_target_metrics,
        auto_scale_disable_scale_in=args.auto_scale_disable_scale_in,
        auto_scale_in_cooldown_seconds=args.auto_scale_in_cooldown_seconds,
        auto_scale_out_cooldown_seconds=args.auto_scale_out_cooldown_seconds,
        current_config=original_settings.readPoolAutoScaleConfig,
    )
    if read_pool_auto_scale_config is not None:
      settings.readPoolAutoScaleConfig = read_pool_auto_scale_config

    if args.IsKnownAndSpecified('performance_capture_config'):
      performance_capture_config = reducers.PerformanceCaptureConfig(
          sql_messages,
          performance_capture_config=args.performance_capture_config,
          current_config=original_settings.performanceCaptureConfig,
      )
      if performance_capture_config is not None:
        settings.performanceCaptureConfig = performance_capture_config

    # BETA args.
    if IsBetaOrNewer(release_track):
      labels_diff = labels_util.ExplicitNullificationDiff.FromUpdateArgs(args)
      labels_update = labels_diff.Apply(
          sql_messages.Settings.UserLabelsValue, instance.settings.userLabels
      )
      if labels_update.needs_update:
        settings.userLabels = labels_update.labels

      # TODO(b/199412671): merge the logic of assigning ip range to
      # _ConstructBaseSettingsFromArgs
      if args.allocated_ip_range_name:
        if not settings.ipConfiguration:
          settings.ipConfiguration = sql_messages.IpConfiguration()
        settings.ipConfiguration.allocatedIpRange = args.allocated_ip_range_name

      if args.IsKnownAndSpecified('unc_mappings'):
        settings.uncMappings = reducers.UncMappings(
            sql_messages,
            unc_mappings=args.unc_mappings,
            clear_unc_mappings=args.clear_unc_mappings,
        )

    # ALPHA args.
    if _IsAlpha(release_track):
      pass

    return settings

  @classmethod
  def _ConstructBaseInstanceFromArgs(
      cls,
      sql_messages,
      args,
      original=None,
      instance_ref=None,
      release_track=DEFAULT_RELEASE_TRACK,
  ):
    """Construct a Cloud SQL instance from command line args.

    Args:
      sql_messages: module, The messages module that should be used.
      args: argparse.Namespace, The CLI arg namespace.
      original: sql_messages.DatabaseInstance, The original instance, if some of
        it might be used to fill fields in the new one.
      instance_ref: reference to DatabaseInstance object, used to fill project
        and instance information.
      release_track: base.ReleaseTrack, the release track that this was run
        under.

    Returns:
      sql_messages.DatabaseInstance, The constructed (and possibly partial)
      database instance.

    Raises:
      ToolException: An error other than http error occurred while executing the
          command.
    """
    del args, original, release_track  # Currently unused in base function.
    instance_resource = sql_messages.DatabaseInstance(kind='sql#instance')

    if instance_ref:
      cls.SetProjectAndInstanceFromRef(instance_resource, instance_ref)

    return instance_resource

  @classmethod
  def ConstructCreateInstanceFromArgs(
      cls,
      sql_messages,
      args,
      original=None,
      instance_ref=None,
      release_track=DEFAULT_RELEASE_TRACK,
  ):
    """Constructs Instance for create request from base instance and args."""
    ShowZoneDeprecationWarnings(args)
    instance_resource = cls._ConstructBaseInstanceFromArgs(
        sql_messages, args, original, instance_ref
    )

    instance_resource.region = reducers.Region(
        args.region, _GetZone(args), _GetSecondaryZone(args)
    )
    instance_resource.databaseVersion = ParseDatabaseVersion(
        sql_messages, args.database_version
    )

    if args.IsKnownAndSpecified('database_center_integration_enabled'):
      instance_resource.databaseCenterIntegrationEnabled = (
          args.database_center_integration_enabled
      )

    if args.IsKnownAndSpecified('master_instance_name'):
      instance_resource.masterInstanceName = args.master_instance_name

    if args.IsKnownAndSpecified('backend_type'):
      instance_resource.backendType = _ParseBackendType(
          sql_messages, args.backend_type
      )
    semi_managed_params = {
        'gce_instances': 'gce_instances',
        'sql_account': 'sql_account',
        'sql_account_secret_name': 'sql_account_secret_name',
        'windows_service_account': 'windows_service_account',
        'windows_service_account_secret_name': (
            'windows_service_account_secret_name'
        ),
        'enable_semi_managed_replication': 'replication_enabled',
        'enable_semi_managed_backup': 'backup_enabled',
        'semi_managed_insights_gcs_uri': 'insights_gcs_uri',
        'semi_managed_patch_database_gcs_uri': 'patch_database_gcs_uri',
    }
    kwargs = {
        param: getattr(args, flag)
        for flag, param in semi_managed_params.items()
        if args.IsKnownAndSpecified(flag)
    }
    if kwargs or (
        args.IsKnownAndSpecified('backend_type')
        and args.backend_type == 'SEMI_MANAGED'
    ):
      instance_resource.semiManagedConfig = reducers.SemiManagedConfig(
          sql_messages, **kwargs
      )

    if args.IsKnownAndSpecified('root_password'):
      instance_resource.rootPassword = args.root_password

    # BETA: Set the host port and return early if external master instance.
    if IsBetaOrNewer(release_track) and args.IsSpecified('source_ip_address'):
      on_premises_configuration = reducers.OnPremisesConfiguration(
          sql_messages, args.source_ip_address, args.source_port
      )
      instance_resource.onPremisesConfiguration = on_premises_configuration
      return instance_resource

    instance_resource.settings = cls._ConstructCreateSettingsFromArgs(
        sql_messages, args, original, release_track
    )

    if args.IsKnownAndSpecified('enforce_new_sql_network_architecture'):
      instance_resource.sqlNetworkArchitecture = (
          sql_messages.DatabaseInstance.SqlNetworkArchitectureValueValuesEnum.NEW_NETWORK_ARCHITECTURE
      )

    if args.IsKnownAndSpecified('master_instance_name'):
      replication = (
          sql_messages.Settings.ReplicationTypeValueValuesEnum.ASYNCHRONOUS
      )
      if args.replica_type == 'FAILOVER':
        _ShowFailoverReplicaDeprecationWarning()
        instance_resource.replicaConfiguration = (
            sql_messages.ReplicaConfiguration(
                kind='sql#demoteMasterMysqlReplicaConfiguration',
                failoverTarget=True,
            )
        )
      if args.cascadable_replica:
        if instance_resource.replicaConfiguration:
          instance_resource.replicaConfiguration.cascadableReplica = (
              args.cascadable_replica
          )
        else:
          instance_resource.replicaConfiguration = (
              sql_messages.ReplicaConfiguration(
                  kind='sql#replicaConfiguration',
                  cascadableReplica=args.cascadable_replica,
              )
          )

    else:
      replication = (
          sql_messages.Settings.ReplicationTypeValueValuesEnum.SYNCHRONOUS
      )
    if not args.IsKnownAndSpecified('replication'):
      instance_resource.settings.replicationType = replication

    if args.IsKnownAndSpecified('failover_replica_name'):
      _ShowFailoverReplicaDeprecationWarning()
      instance_resource.failoverReplica = (
          sql_messages.DatabaseInstance.FailoverReplicaValue(
              name=args.failover_replica_name
          )
      )

    if args.collation:
      instance_resource.settings.collation = args.collation

    # BETA: Config for creating a replica of an external primary instance.
    if IsBetaOrNewer(release_track) and args.IsSpecified('master_username'):
      # Ensure that the primary instance name is specified.
      if not args.IsSpecified('master_instance_name'):
        raise exceptions.RequiredArgumentException(
            '--master-instance-name',
            (
                'To create a read replica of an external '
                'master instance, [--master-instance-name] must be specified'
            ),
        )

      # TODO(b/78648703): Remove when mutex required status is fixed.
      # Ensure that the primary replication user password is specified.
      if not (
          args.IsSpecified('master_password')
          or args.IsSpecified('prompt_for_master_password')
      ):
        raise exceptions.RequiredArgumentException(
            '--master-password',
            (
                'To create a read replica of an external '
                'master instance, [--master-password] or '
                '[--prompt-for-master-password] must be specified'
            ),
        )

      # Get password if not specified on command line.
      if args.prompt_for_master_password:
        args.master_password = console_io.PromptPassword(
            'Master Instance Password: '
        )

      instance_resource.replicaConfiguration = reducers.ReplicaConfiguration(
          sql_messages,
          args.master_username,
          args.master_password,
          args.master_dump_file_path,
          args.master_ca_certificate_path,
          args.client_certificate_path,
          args.client_key_path,
      )

    is_primary = instance_resource.masterInstanceName is None
    key_name = _GetAndValidateCmekKeyName(args, is_primary)
    enable_confidential = getattr(args, 'enable_confidential_storage', None)
    enforce_cmek_log = getattr(
        args, 'enforce_cmek_log_encryption_at_source', None
    )
    if (
        key_name
        or enable_confidential is not None
        or enforce_cmek_log is not None
    ):
      config = sql_messages.DiskEncryptionConfiguration(
          kind='sql#diskEncryptionConfiguration'
      )
      if key_name:
        config.kmsKeyName = key_name
      if enable_confidential is not None:
        config.confidentialMode = enable_confidential
      if enforce_cmek_log is not None:
        config.cmekSourceLogEncryptionEnforced = enforce_cmek_log
      instance_resource.diskEncryptionConfiguration = config

    tags = getattr(args, 'tags')
    if tags is not None:
      instance_resource.tags = sql_messages.DatabaseInstance.TagsValue(
          additionalProperties=[
              sql_messages.DatabaseInstance.TagsValue.AdditionalProperty(
                  key=tag,
                  value=value,
              )
              for tag, value in six.iteritems(tags)
          ]
      )

    if args.IsKnownAndSpecified('instance_type'):
      instance_resource.instanceType = _ParseInstanceType(
          sql_messages, args.instance_type
      )
    if args.IsKnownAndSpecified('node_count'):
      instance_resource.nodeCount = args.node_count

    return instance_resource

  @classmethod
  def ConstructPatchInstanceFromArgs(
      cls,
      sql_messages,
      args,
      original,
      instance_ref=None,
      release_track=DEFAULT_RELEASE_TRACK,
  ):
    """Constructs Instance for patch request from base instance and args."""
    instance_resource = cls._ConstructBaseInstanceFromArgs(
        sql_messages, args, original, instance_ref
    )

    instance_resource.databaseVersion = ParseDatabaseVersion(
        sql_messages, args.database_version
    )

    instance_resource.maintenanceVersion = args.maintenance_version

    instance_resource.settings = cls._ConstructPatchSettingsFromArgs(
        sql_messages, args, original, release_track
    )

    if args.IsKnownAndSpecified('database_center_integration_enabled'):
      instance_resource.databaseCenterIntegrationEnabled = (
          args.database_center_integration_enabled
      )

    if args.upgrade_sql_network_architecture:
      instance_resource.sqlNetworkArchitecture = (
          sql_messages.DatabaseInstance.SqlNetworkArchitectureValueValuesEnum.NEW_NETWORK_ARCHITECTURE
      )
    if args.enforce_new_sql_network_architecture:
      instance_resource.sqlNetworkArchitecture = (
          sql_messages.DatabaseInstance.SqlNetworkArchitectureValueValuesEnum.NEW_NETWORK_ARCHITECTURE
      )

    if args.IsKnownAndSpecified('switch_transaction_logs_to_cloud_storage'):
      instance_resource.switchTransactionLogsToCloudStorageEnabled = (
          args.switch_transaction_logs_to_cloud_storage
      )

    if args.IsSpecified('simulate_maintenance_event'):
      instance_resource.maintenanceVersion = original.maintenanceVersion
      api_util.InstancesV1Beta4.PrintAndConfirmSimulatedMaintenanceEvent()
    # Have the simulate maintenance event flag take precedence.
    # Confirm simulation, maintenance-version flag cannot be supplied with
    # simulate-maintenance-event flag.
    if (
        args.IsSpecified('maintenance_version')
        and args.maintenance_version == original.maintenanceVersion
    ):
      api_util.InstancesV1Beta4.PrintAndConfirmSimulatedMaintenanceEvent()

    if args.IsSpecified('reconcile_psa_networking'):
      if instance_resource.settings.ipConfiguration is None:
        instance_resource.settings.ipConfiguration = (
            sql_messages.IpConfiguration()
        )
      instance_resource.settings.ipConfiguration.privateNetwork = (
          original.settings.ipConfiguration.privateNetwork
      )

    if args.IsKnownAndSpecified('failover_dr_replica_name'):
      replication_cluster = sql_messages.ReplicationCluster()
      replication_cluster.failoverDrReplicaName = args.failover_dr_replica_name
      instance_resource.replicationCluster = replication_cluster
    if args.IsKnownAndSpecified('clear_failover_dr_replica_name'):
      if instance_resource.replicationCluster is not None:
        instance_resource.replicationCluster.ClearFailoverDrReplicaName()

    if args.IsKnownAndSpecified('include_replicas_for_major_version_upgrade'):
      instance_resource.includeReplicasForMajorVersionUpgrade = (
          args.include_replicas_for_major_version_upgrade
      )

    if args.IsKnownAndSpecified('instance_type'):
      instance_resource.instanceType = _ParseInstanceType(
          sql_messages, args.instance_type
      )
    if args.IsKnownAndSpecified('node_count'):
      instance_resource.nodeCount = args.node_count

    enforce_cmek_log = getattr(
        args, 'enforce_cmek_log_encryption_at_source', None
    )
    if enforce_cmek_log is not None:
      if original.diskEncryptionConfiguration:
        instance_resource.diskEncryptionConfiguration = (
            original.diskEncryptionConfiguration
        )
        instance_resource.diskEncryptionConfiguration.cmekSourceLogEncryptionEnforced = (
            enforce_cmek_log
        )
      else:
        instance_resource.diskEncryptionConfiguration = (
            sql_messages.DiskEncryptionConfiguration(
                kind='sql#diskEncryptionConfiguration',
                cmekSourceLogEncryptionEnforced=enforce_cmek_log,
            )
        )

    semi_managed_params = {
        'gce_instances': 'gce_instances',
        'sql_account': 'sql_account',
        'sql_account_secret_name': 'sql_account_secret_name',
        'windows_service_account': 'windows_service_account',
        'windows_service_account_secret_name': (
            'windows_service_account_secret_name'
        ),
        'enable_semi_managed_replication': 'replication_enabled',
        'enable_semi_managed_backup': 'backup_enabled',
        'semi_managed_insights_gcs_uri': 'insights_gcs_uri',
        'semi_managed_patch_database_gcs_uri': 'patch_database_gcs_uri',
    }
    kwargs = {
        param: getattr(args, flag)
        for flag, param in semi_managed_params.items()
        if args.IsKnownAndSpecified(flag)
    }
    if kwargs:
      instance_resource.semiManagedConfig = reducers.SemiManagedConfig(
          sql_messages, **kwargs
      )

    return instance_resource


class InstancesV1Beta4(_BaseInstances):
  """Common utility functions for sql instances V1Beta4."""

  @staticmethod
  def SetProjectAndInstanceFromRef(instance_resource, instance_ref):
    instance_resource.project = instance_ref.project
    instance_resource.name = instance_ref.instance

  @staticmethod
  def AddBackupConfigToSettings(settings, backup_config):
    settings.backupConfiguration = backup_config

  @staticmethod
  def AddFinalBackupConfigToSettings(settings, final_backup_config):
    settings.finalBackupConfig = final_backup_config

  @staticmethod
  def SetIpConfigurationEnabled(settings, assign_ip):
    settings.ipConfiguration.ipv4Enabled = assign_ip

  @staticmethod
  def SetAuthorizedNetworks(settings, authorized_networks, acl_entry_value):
    settings.ipConfiguration.authorizedNetworks = [
        acl_entry_value(kind='sql#aclEntry', value=n)
        for n in authorized_networks
    ]
