/**
 * 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.
 */

# TODO: add project sa to context

locals {
  # project data from folders tree
  _folder_projects_raw = {
    for f in try(fileset(local.paths.folders, "**/*.yaml"), []) :
    trimsuffix(f, ".yaml") => merge(
      { parent = dirname(f) == "." ? null : "$folder_ids:${dirname(f)}" },
      yamldecode(file("${local.paths.folders}/${f}"))
    ) if !endswith(f, "/.config.yaml")
  }
  _projects_input = {
    for k, v in merge(local._folder_projects_raw, local._projects_raw) :
    basename(k) => merge(
      try(local._templates_raw[v.project_template], {}),
      v
    )
    # apply exclusions
    if alltrue([
      for x in var.factories_config.exclusions.projects : !startswith(k, x)
    ])
  }
  # project data from projects folder
  _projects_raw = {
    for f in try(fileset(local.paths.projects, "**/*.yaml"), []) :
    trimsuffix(f, ".yaml") => yamldecode(file("${local.paths.projects}/${f}"))
    if !endswith(f, ".config.yaml")
  }
  _templates_path = try(
    pathexpand(local.paths.project_templates), null
  )
  _templates_raw = {
    for f in try(fileset(local._templates_path, "**/*.yaml"), []) :
    trimsuffix(f, ".yaml") => yamldecode(file("${local._templates_path}/${f}"))
  }
  ctx_project_ids     = merge(local.ctx.project_ids, local.project_ids)
  ctx_project_numbers = merge(local.ctx.project_numbers, local.project_numbers)
  # cross-project tag contexts, keyed on project name
  ctx_tag_keys = merge(local.ctx.tag_keys, {
    for k, v in merge({}, [
      for pk, pv in local.projects_input : {
        for tk, tv in module.projects[pk].tag_keys :
        "${pk}/${tk}" => tv.id
      }
    ]...) : k => v
  })
  ctx_tag_values = merge(local.ctx.tag_values, {
    for k, v in merge({}, [
      for pk, pv in local.projects_input : {
        for tk, tv in module.projects[pk].tag_values :
        "${pk}/${tk}" => tv.id
      }
    ]...) : k => v
  })
  tag_vars_projects = {
    for k, v in local.projects_input : v.name => {
      for kk, vv in module.projects[k].tag_keys :
      kk => vv.namespaced_name
      if vv.allowed_values_regex != null
    }
  }
  per_project_service_agents = {
    for k, v in module.projects : k => {
      for kk, vv in v.service_agents :
      "service_agents/_self_/${kk}" => vv.iam_email
    }
  }
  project_ids = {
    for k, v in module.projects : k => v.project_id
  }
  project_numbers = {
    for k, v in module.projects : k => v.number
  }
  projects_input = merge(var.projects, local._projects_output)
  projects_service_agents = merge([
    for k, v in module.projects : {
      for kk, vv in v.service_agents : "service_agents/${k}/${kk}" => vv.iam_email
    }
  ]...)
}

moved {
  from = terraform_data.project-preconditions
  to   = terraform_data.project_preconditions
}

resource "terraform_data" "project_preconditions" {
  lifecycle {
    precondition {
      condition = alltrue([
        for k, v in local._projects_input :
        try(v.project_template, null) == null ||
        lookup(local._templates_raw, v.project_template, null) != null
      ])
      error_message = "Missing project templates referenced in projects."
    }
  }
}

module "projects" {
  source              = "../project"
  for_each            = local.projects_input
  billing_account     = each.value.billing_account
  deletion_policy     = each.value.deletion_policy
  name                = each.value.name
  descriptive_name    = each.value.descriptive_name
  parent              = each.value.parent
  prefix              = each.value.prefix
  project_reuse       = each.value.project_reuse
  alerts              = try(each.value.alerts, null)
  asset_feeds         = each.value.asset_feeds
  auto_create_network = try(each.value.auto_create_network, false)
  compute_metadata    = try(each.value.compute_metadata, {})
  # TODO: concat lists for each key
  contacts = merge(
    each.value.contacts, var.data_merges.contacts
  )
  context = merge(local.ctx, {
    condition_vars = merge(local.ctx.condition_vars, {
      folder_ids = {
        for k, v in local.ctx_folder_ids : replace(k, "$folder_ids:", "") => v
      }
    })
    folder_ids = local.ctx_folder_ids
  })
  default_service_account = try(each.value.default_service_account, "keep")
  # Exclude factories that are either:
  # a) Handled in parallel by calling specific modules (e.g., aspect_types, data_catalog_taxonomy)
  # b) Handled in the projects-iam call to leverage expanded context (e.g., org_policies)
  factories_config = {
    for k, v in each.value.factories_config : k => try(pathexpand(
      var.factories_config.basepath == null || startswith(v, "/") || startswith(v, ".")
      ? v :
      "${var.factories_config.basepath}/${v}"
    ), null)
    if !contains(["aspect_types", "data_catalog_taxonomy", "org_policies"], k)
  }
  kms_autokeys = try(each.value.kms.autokeys, {})
  labels = merge(
    each.value.labels, var.data_merges.labels
  )
  lien_reason           = try(each.value.lien_reason, null)
  log_scopes            = try(each.value.log_scopes, null)
  logging_exclusions    = try(each.value.logging_exclusions, {})
  logging_metrics       = try(each.value.logging_metrics, null)
  logging_sinks         = try(each.value.logging_sinks, {})
  notification_channels = try(each.value.notification_channels, null)
  quotas                = each.value.quotas
  services = distinct(concat(
    each.value.services,
    var.data_merges.services
  ))
  tags = each.value.tags
  tags_config = {
    ignore_iam = true
  }
  universe                = each.value.universe
  vpc_sc                  = each.value.vpc_sc
  workload_identity_pools = each.value.workload_identity_pools
}

module "projects-iam" {
  source       = "../project"
  for_each     = local.projects_input
  name         = each.value.name
  prefix       = each.value.prefix
  org_policies = each.value.org_policies
  project_reuse = {
    use_data_source = false
    attributes = {
      name             = module.projects[each.key].name
      number           = module.projects[each.key].number
      services_enabled = module.projects[each.key].services
    }
  }
  context = merge(local.ctx, {
    condition_vars = merge(
      local.ctx.condition_vars, {
        folder_ids = {
          for k, v in local.ctx_folder_ids : replace(k, "$folder_ids:", "") => v
        }
        projects = {
          for k, v in module.projects : k => v.project_id
        }
      }
    )
    tag_vars = {
      projects     = merge(try(local.ctx.tag_vars.projects, {}), local.tag_vars_projects)
      organization = try(local.ctx.tag_vars.organization, {})
    }
    folder_ids = local.ctx_folder_ids
    kms_keys   = merge(local.ctx.kms_keys, local.kms_keys)
    iam_principals = merge(
      local.ctx_iam_principals,
      lookup(local.per_project_service_agents, each.key, {}),
      lookup(local.self_sas_iam_emails, each.key, {}),
      local.projects_service_agents
    )
    custom_roles = merge(
      try(local.ctx.custom_roles, {}),
      module.projects[each.key].custom_role_id
    )
    project_ids = merge(
      local.ctx.project_ids,
      { for k, v in module.projects : k => v.project_id }
    )
    tag_keys   = local.ctx_tag_keys
    tag_values = local.ctx_tag_values
  })
  factories_config = {
    # we do anything that can refer to IAM and custom roles in this call
    pam_entitlements = try(each.value.factories_config.pam_entitlements, null)
    org_policies = lookup(each.value.factories_config, "org_policies", null) == null ? null : try(pathexpand(
      var.factories_config.basepath == null || startswith(each.value.factories_config.org_policies, "/") || startswith(each.value.factories_config.org_policies, ".")
      ? each.value.factories_config.org_policies :
      "${var.factories_config.basepath}/${each.value.factories_config.org_policies}"
    ), null)
  }
  iam                           = lookup(each.value, "iam", {})
  iam_bindings                  = lookup(each.value, "iam_bindings", {})
  iam_bindings_additive         = lookup(each.value, "iam_bindings_additive", {})
  iam_by_principals             = lookup(each.value, "iam_by_principals", {})
  iam_by_principals_conditional = lookup(each.value, "iam_by_principals_conditional", {})
  iam_by_principals_additive    = lookup(each.value, "iam_by_principals_additive", {})
  logging_data_access           = lookup(each.value, "logging_data_access", {})
  metric_scopes = distinct(concat(
    each.value.metric_scopes, var.data_merges.metric_scopes
  ))
  pam_entitlements = try(each.value.pam_entitlements, {})
  service_agents_config = {
    create_primary_agents = false
    grant_default_roles   = false
  }
  service_encryption_key_ids = merge(
    each.value.service_encryption_key_ids,
    var.data_merges.service_encryption_key_ids
  )
  shared_vpc_host_config    = each.value.shared_vpc_host_config
  shared_vpc_service_config = each.value.shared_vpc_service_config
  tag_bindings = merge(
    each.value.tag_bindings, var.data_merges.tag_bindings
  )
  tags = each.value.tags
  tags_config = {
    force_context_ids = true
  }
  iam_deny_policies = lookup(each.value, "iam_deny_policies", {})
  universe          = each.value.universe
  # we use explicit depends_on as this allows us passing name and prefix
  depends_on = [
    module.projects
  ]
}
