/**
 * Copyright 2026 Google LLC
 *
 * 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.
 */

locals {
  ctx = {
    for k, v in var.context : k => {
      for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv
    }
  }
  ctx_p = "$"
  encryption_key_name = (
    var.encryption_key_name == null
    ? null
    : lookup(local.ctx.kms_keys, var.encryption_key_name, var.encryption_key_name)
  )
  has_replicas = length(var.replicas) > 0
  is_mysql     = can(regex("^MYSQL", var.database_version))
  is_postgres  = can(regex("^POSTGRES", var.database_version))
  is_regional  = var.availability_type == "REGIONAL" ? true : false
  psa_private_network = (
    try(var.network_config.connectivity.psa_config.private_network, null) == null
    ? null
    : lookup(
      local.ctx.networks,
      var.network_config.connectivity.psa_config.private_network,
      var.network_config.connectivity.psa_config.private_network
    )
  )
  prefix     = var.prefix == null ? "" : "${var.prefix}-"
  project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id)
  region     = lookup(local.ctx.locations, var.region, var.region)

  users = {
    for k, v in var.users : k =>
    local.is_mysql
    ? {
      name           = v.type == "BUILT_IN" ? split("@", k)[0] : k
      host           = v.type == "BUILT_IN" ? try(split("@", k)[1], null) : null
      password       = v.type == "BUILT_IN" ? try(random_password.passwords[k].result, v.password) : null
      type           = v.type
      database_roles = v.database_roles
    }
    : {
      name           = local.is_postgres ? try(trimsuffix(k, ".gserviceaccount.com"), k) : k
      host           = null
      password       = v.type == "BUILT_IN" ? try(random_password.passwords[k].result, v.password) : null
      type           = v.type
      database_roles = v.database_roles
    }
  }
}

resource "google_sql_database_instance" "primary" {
  provider            = google-beta
  project             = local.project_id
  name                = "${local.prefix}${var.name}"
  region              = local.region
  database_version    = var.database_version
  encryption_key_name = local.encryption_key_name
  root_password       = var.root_password.random_password ? random_password.root_password[0].result : var.root_password.password

  settings {
    tier                        = var.tier
    edition                     = var.edition
    deletion_protection_enabled = var.gcp_deletion_protection
    disk_autoresize             = var.disk_size == null
    disk_autoresize_limit       = var.disk_autoresize_limit
    disk_size                   = var.disk_size
    disk_type                   = var.disk_type
    availability_type           = var.availability_type
    user_labels                 = var.labels
    activation_policy           = var.activation_policy
    collation                   = var.collation
    connector_enforcement       = var.connector_enforcement
    data_api_access             = var.data_api_access
    time_zone                   = var.time_zone
    retain_backups_on_delete    = try(var.backup_configuration.retain_backups_on_delete, null)

    ip_configuration {
      ipv4_enabled    = var.network_config.connectivity.public_ipv4
      private_network = local.psa_private_network
      allocated_ip_range = try(
        var.network_config.connectivity.psa_config.allocated_ip_ranges.primary, null
      )
      ssl_mode = var.ssl.mode
      enable_private_path_for_google_cloud_services = (
        var.network_config.connectivity.enable_private_path_for_services
      )
      dynamic "authorized_networks" {
        for_each = (
          var.network_config.authorized_networks != null
          ? var.network_config.authorized_networks
          : {}
        )
        iterator = network
        content {
          name  = network.key
          value = network.value
        }
      }
      dynamic "psc_config" {
        for_each = (
          var.network_config.connectivity.psc_allowed_consumer_projects != null
          ? [""]
          : []
        )
        content {
          psc_enabled = true
          allowed_consumer_projects = [
            for p in var.network_config.connectivity.psc_allowed_consumer_projects :
            lookup(local.ctx.project_ids, p, p)
          ]
        }
      }
    }

    # backup_configuration is Optional+Computed in the provider schema, meaning an absent block
    # is treated as "don't manage" rather than "set to disabled". Setting var.backup_configuration
    # to null preserves existing GCP settings; passing an object (even with enabled=false) causes
    # Terraform to actively manage and apply the desired state.
    # See: https://github.com/hashicorp/terraform-provider-google/blob/main/google/services/sql/resource_sql_database_instance.go#L424
    # See: https://developer.hashicorp.com/terraform/language/attr-as-blocks
    dynamic "backup_configuration" {
      for_each = var.backup_configuration != null ? [1] : []
      content {
        enabled = var.backup_configuration.enabled
        // auto-enable binary log for MySQL when replicas or regional HA are in use;
        // must be null (not true) when backups are disabled
        binary_log_enabled = (
          local.is_mysql && var.backup_configuration.enabled
          ? var.backup_configuration.binary_log_enabled || local.has_replicas || local.is_regional
          : null
        )
        start_time = var.backup_configuration.start_time
        location   = var.backup_configuration.location
        // must be explicitly false (not null) when backups are disabled
        point_in_time_recovery_enabled = (
          var.backup_configuration.enabled
          ? var.backup_configuration.point_in_time_recovery_enabled
          : false
        )
        transaction_log_retention_days = var.backup_configuration.log_retention_days
        dynamic "backup_retention_settings" {
          for_each = var.backup_configuration.enabled ? { 1 = 1 } : {}
          content {
            retained_backups = var.backup_configuration.retention_count
            retention_unit   = "COUNT"
          }
        }
      }
    }

    dynamic "final_backup_config" {
      for_each = try(var.backup_configuration.final_backup != null ? [var.backup_configuration.final_backup] : [], [])
      content {
        enabled        = final_backup_config.value.enabled
        retention_days = final_backup_config.value.retention_days
      }
    }

    dynamic "connection_pool_config" {
      # Only available for PostgreSQL on Enterprise Plus edition.
      for_each = (var.managed_connection_pooling_config.enabled && local.is_postgres && var.edition == "ENTERPRISE_PLUS") ? [1] : []
      content {
        connection_pooling_enabled = true
        dynamic "flags" {
          for_each = var.managed_connection_pooling_config.flags
          iterator = flag
          content {
            name  = flag.key
            value = flag.value
          }
        }
      }
    }

    dynamic "data_cache_config" {
      for_each = var.edition == "ENTERPRISE_PLUS" ? [1] : []
      content {
        data_cache_enabled = var.data_cache
      }
    }

    dynamic "database_flags" {
      for_each = var.flags != null ? var.flags : {}
      iterator = flag
      content {
        name  = flag.key
        value = flag.value
      }
    }

    dynamic "deny_maintenance_period" {
      for_each = var.maintenance_config.deny_maintenance_period != null ? [1] : []
      content {
        start_date = var.maintenance_config.deny_maintenance_period.start_date
        end_date   = var.maintenance_config.deny_maintenance_period.end_date
        time       = var.maintenance_config.deny_maintenance_period.start_time
      }
    }

    dynamic "insights_config" {
      for_each = var.insights_config != null ? [1] : []
      content {
        query_insights_enabled          = true
        query_string_length             = var.insights_config.query_string_length
        record_application_tags         = var.insights_config.record_application_tags
        record_client_address           = var.insights_config.record_client_address
        query_plans_per_minute          = var.insights_config.query_plans_per_minute
        enhanced_query_insights_enabled = var.insights_config.enhanced_query_insights_enabled
      }
    }

    dynamic "maintenance_window" {
      for_each = var.maintenance_config.maintenance_window != null ? [""] : []
      content {
        day          = var.maintenance_config.maintenance_window.day
        hour         = var.maintenance_config.maintenance_window.hour
        update_track = var.maintenance_config.maintenance_window.update_track
      }
    }

    dynamic "password_validation_policy" {
      for_each = var.password_validation_policy != null ? [""] : []
      content {
        complexity = (
          var.password_validation_policy.default_complexity == true
          ? "COMPLEXITY_DEFAULT"
          : null # "COMPLEXITY_UNSPECIFIED" generates a permadiff
        )
        disallow_username_substring = (
          var.password_validation_policy.disallow_username_substring
        )
        enable_password_policy = var.password_validation_policy.enabled
        min_length             = var.password_validation_policy.min_length
        password_change_interval = (
          var.password_validation_policy.change_interval == null
          ? null
          : "${var.password_validation_policy.change_interval}s"
        )
        reuse_interval = var.password_validation_policy.reuse_interval
      }
    }
  }
  deletion_protection = var.terraform_deletion_protection
}

resource "google_sql_database_instance" "replicas" {
  provider         = google-beta
  for_each         = local.has_replicas ? var.replicas : {}
  project          = local.project_id
  name             = "${local.prefix}${each.key}"
  region           = lookup(local.ctx.locations, each.value.region, each.value.region)
  database_version = var.database_version
  encryption_key_name = (
    each.value.encryption_key_name == null
    ? null
    : lookup(local.ctx.kms_keys, each.value.encryption_key_name, each.value.encryption_key_name)
  )
  master_instance_name = google_sql_database_instance.primary.name

  settings {
    tier                        = var.tier
    edition                     = var.edition
    deletion_protection_enabled = var.gcp_deletion_protection
    disk_autoresize             = var.disk_size == null
    disk_autoresize_limit       = var.disk_autoresize_limit
    disk_size                   = var.disk_size
    disk_type                   = var.disk_type
    availability_type           = each.value.availability_type
    user_labels                 = var.labels
    activation_policy           = var.activation_policy
    collation                   = var.collation
    connector_enforcement       = var.connector_enforcement
    time_zone                   = var.time_zone


    ip_configuration {
      ipv4_enabled = (
        var.network_config.connectivity.public_ipv4
      )
      private_network = local.psa_private_network
      allocated_ip_range = try(
        var.network_config.connectivity.psa_config.allocated_ip_ranges.replica, null
      )
      ssl_mode = var.ssl.mode
      enable_private_path_for_google_cloud_services = (
        var.network_config.connectivity.enable_private_path_for_services
      )
      dynamic "authorized_networks" {
        for_each = (
          var.network_config.authorized_networks != null
          ? var.network_config.authorized_networks
          : {}
        )
        iterator = network
        content {
          name  = network.key
          value = network.value
        }
      }
      dynamic "psc_config" {
        for_each = (
          var.network_config.connectivity.psc_allowed_consumer_projects != null
          ? [""]
          : []
        )
        content {
          psc_enabled = true
          allowed_consumer_projects = [
            for p in var.network_config.connectivity.psc_allowed_consumer_projects :
            lookup(local.ctx.project_ids, p, p)
          ]
        }
      }
    }

    dynamic "database_flags" {
      for_each = var.flags != null ? var.flags : {}
      iterator = flag
      content {
        name  = flag.key
        value = flag.value
      }
    }
  }
  deletion_protection = var.terraform_deletion_protection
}

resource "google_sql_database" "databases" {
  for_each = var.databases != null ? toset(var.databases) : toset([])
  project  = local.project_id
  instance = google_sql_database_instance.primary.name
  name     = each.key
}

resource "random_password" "passwords" {
  for_each = toset([
    for k, v in var.users :
    k
    if v.password == null && v.type == "BUILT_IN"
  ])
  length      = try(var.password_validation_policy.min_length, 16)
  special     = true
  min_lower   = 1
  min_numeric = 1
  min_special = 1
  min_upper   = 1
}

resource "random_password" "root_password" {
  count       = var.root_password.random_password ? 1 : 0
  length      = try(var.password_validation_policy.min_length, 16)
  special     = true
  min_lower   = 1
  min_numeric = 1
  min_special = 1
  min_upper   = 1
}

resource "google_sql_user" "users" {
  for_each       = local.users
  project        = local.project_id
  instance       = google_sql_database_instance.primary.name
  name           = each.value.name
  host           = each.value.host
  password       = each.value.password
  type           = each.value.type
  database_roles = each.value.database_roles
}

moved {
  from = google_sql_ssl_cert.postgres_client_certificates
  to   = google_sql_ssl_cert.client_certificates
}

resource "google_sql_ssl_cert" "client_certificates" {
  for_each = (
    var.ssl.client_certificates != null
    ? toset(var.ssl.client_certificates)
    : toset([])
  )
  provider    = google-beta
  project     = local.project_id
  instance    = google_sql_database_instance.primary.name
  common_name = each.key
}
