data "aws_caller_identity" "current" {
}

data "aws_caller_identity" "dns" {
  provider = aws.dns
}


data "aws_iam_policy_document" "assume_rds_monitoring_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["monitoring.rds.amazonaws.com"]
    }
  }
}

data "aws_vpc" "vpc" {
  id = var.vpc_id
}

data "aws_ec2_managed_prefix_lists" "private_subnets" {
  tags = {
    environment  = var.environment
    subnet_group = "all"
    tier         = "private"
    vpc_id       = var.vpc_id
  }
}

data "aws_ec2_managed_prefix_list" "ingress_prefix_lists" {
  for_each = setunion(
    var.rds_ingress_prefix_list_names,
    ["shared-orcd-atlantis-private-subnet-prefix-list"],
    ["shared-orcd-rds-backup-private-subnet-prefix-list"],
    ["vpn-ny-users"],
  )
  name = each.value
}

data "aws_iam_policy" "permissions_boundary_policy" {
  count = local.permissions_boundary_enabled ? 1 : 0
  name  = var.permissions_boundary_policy_name
}

locals {
  port              = var.rds_port == "" ? var.rds_engine == "aurora-postgresql" ? "5432" : "3306" : var.rds_port
  delete_protection = var.deletion_protection != null ? var.deletion_protection : (var.environment == "prod" ? true : false)

  primsa_sg_exception_tags = {
    "eiso-exception" = "aws.08.30"
  }

  # Concatenate required and user-supplied tags
  combined_resource_tags = merge(
    {
      environment        = var.environment
      service_name       = var.service_name
      application_family = var.application_family
      terraformed        = true
      rds_proxy          = var.rds_proxy_enabled
    },
    var.additional_tags
  )

  # For production environments with CMK encryption, enable resource sharing for cross-account RDS refreshes
  enable_resource_share = (
    (var.custom_kms_key_enabled || var.custom_cmk_key_arn != null) &&
    var.rds_primary_cluster_enabled &&
    var.environment == "prod"
  )

  route53_zone_id = var.override_route53_zone_id != null ? var.override_route53_zone_id : (var.environment == "dev" ? "Z21XEY26C989RH" : "Z0183645HDT0XCWHLW7S")

  cluster_parameter_group_name = (var.rds_db_cluster_parameter_group_name == null ?
  aws_rds_cluster_parameter_group.cluster_parameter_group[0].name : var.rds_db_cluster_parameter_group_name)
  instance_parameter_group_name = (var.rds_cluster_instance_parameter_group_name == null ?
  aws_db_parameter_group.instance_parameter_group[0].name : var.rds_cluster_instance_parameter_group_name)

  subnet_group_name = var.rds_db_subnet_group_name == null ? aws_db_subnet_group.subnet_group[0].name : var.rds_db_subnet_group_name

  permissions_boundary_enabled = coalesce(var.permissions_boundary_enabled, var.environment == "dev" ? true : false)
  permissions_boundary_arn     = local.permissions_boundary_enabled ? data.aws_iam_policy.permissions_boundary_policy[0].arn : null
}

resource "aws_iam_role" "rds_monitoring_role" {
  name                 = "${var.environment}-${var.service_name}-rds-monitoring-role"
  assume_role_policy   = data.aws_iam_policy_document.assume_rds_monitoring_role.json
  permissions_boundary = local.permissions_boundary_arn

  tags = local.combined_resource_tags
}

# Create our own policy, so we can attach it to a custom role to avoid Terraform/AWS APIs being brittle.
data "aws_iam_policy_document" "rds_enhanced_monitoring_policy" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:DescribeLogStreams",
      "logs:GetLogEvents",
      "logs:PutLogEvents",
      "logs:PutRetentionPolicy",
    ]

    resources = [
      "arn:aws:logs:*:*:log-group:RDS*",
    ]
  }
}

resource "aws_iam_policy" "rds_enhanced_monitoring_policy" {
  name   = "${var.environment}-${var.service_name}-rds-monitoring-policy"
  policy = data.aws_iam_policy_document.rds_enhanced_monitoring_policy.json
}

# Attach policy to role
resource "aws_iam_role_policy_attachment" "rds_enhanced_monitoring_policy_attachment" {
  role       = aws_iam_role.rds_monitoring_role.name
  policy_arn = aws_iam_policy.rds_enhanced_monitoring_policy.arn
}

resource "aws_sns_topic" "rds_event_notification_topic" {
  count             = var.rds_primary_cluster_enabled ? 1 : 0
  name              = "${var.environment}-${var.service_name}-rds-event-notification"
  kms_master_key_id = var.sns_kms_master_key_id
}

# Create an event subscription specifically for the RDS cluster
resource "aws_db_event_subscription" "cluster_event_subscription" {
  count            = var.rds_primary_cluster_enabled ? 1 : 0
  name             = "${var.environment}-${var.service_name}-rds-cluster-event-subscription"
  sns_topic        = aws_sns_topic.rds_event_notification_topic[count.index].arn
  source_type      = "db-cluster"
  source_ids       = [aws_rds_cluster.rds_cluster[count.index].id]
  enabled          = var.rds_event_notification_enabled
  event_categories = var.rds_cluster_event_notification_categories

  tags = local.combined_resource_tags
}

# Create an SNS topic for serverless clusters
resource "aws_sns_topic" "rds_serverless_event_notification_topic" {
  count             = var.rds_serverless_cluster_enabled ? 1 : 0
  name              = "${var.environment}-${var.service_name}-serverless-rds-event-notification"
  kms_master_key_id = var.sns_kms_master_key_id
}

# Create an event subscription specifically for serverless RDS clusters
resource "aws_db_event_subscription" "serverless_cluster_event_subscription" {
  count            = var.rds_serverless_cluster_enabled ? 1 : 0
  name             = "${var.environment}-${var.service_name}-serverless-rds-cluster-event-subscription"
  sns_topic        = aws_sns_topic.rds_serverless_event_notification_topic[count.index].arn
  source_type      = "db-cluster"
  source_ids       = [aws_rds_cluster.rds_serverless_cluster[count.index].id]
  enabled          = var.rds_event_notification_enabled
  event_categories = var.rds_cluster_event_notification_categories

  tags = local.combined_resource_tags
}

# Create an event subscription specifically for the RDS cluster instances
resource "aws_db_event_subscription" "instance_event_subscription" {
  count            = var.rds_primary_cluster_enabled ? 1 : 0
  name             = "${var.environment}-${var.service_name}-rds-instance-event-subscription"
  sns_topic        = aws_sns_topic.rds_event_notification_topic[count.index].arn
  source_type      = "db-instance"
  source_ids       = [aws_rds_cluster_instance.cluster_instances[count.index].id]
  enabled          = var.rds_event_notification_enabled
  event_categories = var.rds_instance_event_notification_categories

  tags = local.combined_resource_tags
}

data "aws_iam_policy_document" "kms_key_policy_document" {
  statement {
    effect = "Allow"
    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
    }
    actions = [
      "kms:*",
    ]
    resources = ["*"]
  }

  dynamic "statement" {
    # checkov:skip=CKV_AWS_109:It is a KMS key policy, so it applies only to a specific key.
    # checkov:skip=CKV_AWS_111:Same as above.
    for_each = toset(var.account_ids)
    content {
      sid    = "Allow share CMK with account ${statement.value}"
      effect = "Allow"
      principals {
        type = "AWS"
        identifiers = [
          "arn:aws:iam::${statement.value}:root",
        ]
      }
      actions = [
        "kms:Encrypt*",
        "kms:Decrypt*",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:Describe*",
        "kms:CreateGrant",
      ]
      resources = ["*"]
    }
  }
}

# Parameter groups
resource "aws_rds_cluster_parameter_group" "cluster_parameter_group" {
  count = var.rds_db_cluster_parameter_group_name == null ? 1 : 0

  name_prefix = "${var.environment}-${var.service_name}-cluster-parameter-group"
  family      = var.rds_db_cluster_parameter_group_family
  description = "Cluster parameter group for ${var.environment}-${var.service_name}"

  dynamic "parameter" {
    for_each = var.rds_db_cluster_parameters

    content {
      name         = parameter.value.name
      value        = parameter.value.value
      apply_method = parameter.value.apply_method
    }
  }

  tags = local.combined_resource_tags

  lifecycle {
    create_before_destroy = true # It is not possible to destroy the parameter group if it is in use by RDS instances
  }
}

resource "aws_db_parameter_group" "instance_parameter_group" {
  count = var.rds_cluster_instance_parameter_group_name == null ? 1 : 0

  name_prefix = "${var.environment}-${var.service_name}-instance-parameter-group"
  family      = var.rds_cluster_instance_parameter_group_family
  description = "Instance parameter group for ${var.environment}-${var.service_name}"

  dynamic "parameter" {
    for_each = var.rds_cluster_instance_parameters

    content {
      name         = parameter.value.name
      value        = parameter.value.value
      apply_method = parameter.value.apply_method
    }
  }

  tags = local.combined_resource_tags

  lifecycle {
    create_before_destroy = true # It is not possible to destroy the parameter group if it is in use by RDS instances
  }
}

# Subnet group
resource "aws_db_subnet_group" "subnet_group" {
  count = length(var.rds_db_subnet_ids) > 0 ? 1 : 0

  name        = "${var.environment}-${var.service_name}-subnet-group"
  description = "DB subnet group for ${var.environment}-${var.service_name}"
  subnet_ids  = var.rds_db_subnet_ids

  tags = local.combined_resource_tags
}

# KMS encryption key
resource "aws_kms_key" "kms_key" {
  count               = var.custom_kms_key_enabled && var.custom_cmk_key_arn == null ? 1 : 0
  description         = format("alias/%s-%s-%s", var.environment, var.service_name, "rds")
  enable_key_rotation = true
  policy              = data.aws_iam_policy_document.kms_key_policy_document.json

  tags = merge(local.combined_resource_tags, var.rds_refresh_tags)
}

resource "aws_ram_resource_share" "rds_share" {
  count = local.enable_resource_share ? 1 : 0

  name                      = "${var.environment}-${var.service_name}-RDS-share"
  allow_external_principals = false

  tags = local.combined_resource_tags
}

resource "aws_ram_resource_association" "rds_share" {
  count = local.enable_resource_share ? 1 : 0

  resource_arn       = aws_rds_cluster.rds_cluster[count.index].arn
  resource_share_arn = aws_ram_resource_share.rds_share[count.index].arn
}

resource "aws_ram_principal_association" "rds_share" {
  for_each = local.enable_resource_share ? { for id in var.account_ids : id => id } : {}

  principal          = each.value
  resource_share_arn = aws_ram_resource_share.rds_share[0].arn
}

resource "aws_kms_alias" "kms_alias" {
  count         = var.custom_kms_key_enabled && var.custom_cmk_key_arn == null ? 1 : 0
  name          = format("alias/%s-%s-%s", var.environment, var.service_name, "rds")
  target_key_id = aws_kms_key.kms_key.0.key_id
}

# Create a master RDS cluster
resource "aws_rds_cluster" "rds_cluster" {
  # checkov:skip=CKV2_AWS_27:Query logging support for PostgreSQL will be added later, as it requires refactoring of the module.

  # checkov:skip=CKV_AWS_327:Ensure RDS Clusters are encrypted using KMS CMKs
  # We do not enforce using KMS CMK for RDS, therefore the AWS-managed KMS key is allowed.

  count              = var.rds_primary_cluster_enabled ? 1 : 0
  cluster_identifier = "${var.environment}-${var.service_name}"
  engine             = var.rds_engine
  engine_version     = var.rds_engine_version
  engine_mode        = var.rds_engine_mode

  allow_major_version_upgrade         = var.rds_allow_major_version_upgrade
  apply_immediately                   = var.rds_apply_immediately
  availability_zones                  = var.rds_availability_zones
  backtrack_window                    = var.rds_backtrack_window
  backup_retention_period             = var.rds_backup_retention_period
  database_name                       = replace(var.service_name, "-", "_")
  db_cluster_parameter_group_name     = local.cluster_parameter_group_name
  db_subnet_group_name                = local.subnet_group_name
  final_snapshot_identifier           = "${var.environment}-${var.service_name}-final-snapshot"
  iam_roles                           = var.rds_db_cluster_iam_roles
  iam_database_authentication_enabled = var.iam_database_authentication_enabled
  master_username                     = var.rds_master_username
  master_password                     = var.rds_master_password
  preferred_backup_window             = var.rds_preferred_backup_window
  preferred_maintenance_window        = var.rds_preferred_maintenance_window
  skip_final_snapshot                 = var.rds_skip_final_snapshot
  snapshot_identifier                 = var.snapshot_identifier
  storage_encrypted                   = true
  port                                = local.port
  vpc_security_group_ids              = tolist([aws_security_group.rds-sg.id])
  copy_tags_to_snapshot               = var.rds_copy_tags_to_snapshot
  enabled_cloudwatch_logs_exports     = var.enabled_cloudwatch_logs_exports
  deletion_protection                 = local.delete_protection
  enable_local_write_forwarding       = var.enable_local_write_forwarding

  kms_key_id = var.custom_kms_key_enabled ? (
    var.custom_cmk_key_arn != null ? var.custom_cmk_key_arn : aws_kms_key.kms_key.0.arn
  ) : null

  tags = local.combined_resource_tags

  # Ignore master password and AZ changes
  lifecycle {
    ignore_changes = [
      master_username,
      master_password,
      availability_zones,
      database_name,
      snapshot_identifier,
    ]
  }
}

# Create a serverless RDS cluster
resource "aws_rds_cluster" "rds_serverless_cluster" {
  # checkov:skip=CKV2_AWS_27:Query logging support for PostgreSQL will be added later, as it requires refactoring of the module.

  # checkov:skip=CKV_AWS_327:Ensure RDS Clusters are encrypted using KMS CMKs
  # We do not enforce using KMS CMK for RDS, therefore the AWS-managed KMS key is allowed.

  count              = var.rds_serverless_cluster_enabled ? 1 : 0
  cluster_identifier = "${var.environment}-${var.service_name}"
  engine             = var.rds_engine
  engine_version     = var.rds_engine_version
  engine_mode        = "serverless"

  allow_major_version_upgrade         = var.rds_allow_major_version_upgrade
  apply_immediately                   = var.rds_apply_immediately
  availability_zones                  = var.rds_availability_zones
  backtrack_window                    = var.rds_backtrack_window
  backup_retention_period             = var.rds_backup_retention_period
  database_name                       = replace(var.service_name, "-", "_")
  db_cluster_parameter_group_name     = local.cluster_parameter_group_name
  db_subnet_group_name                = local.subnet_group_name
  iam_database_authentication_enabled = var.iam_database_authentication_enabled
  final_snapshot_identifier           = "${var.environment}-${var.service_name}-final-snapshot"
  master_username                     = var.rds_master_username
  master_password                     = var.rds_master_password
  preferred_backup_window             = var.rds_preferred_backup_window
  preferred_maintenance_window        = var.rds_preferred_maintenance_window
  skip_final_snapshot                 = var.rds_skip_final_snapshot
  snapshot_identifier                 = var.snapshot_identifier
  storage_encrypted                   = true
  port                                = local.port
  vpc_security_group_ids              = tolist([aws_security_group.rds-sg.id])
  copy_tags_to_snapshot               = var.rds_copy_tags_to_snapshot
  enabled_cloudwatch_logs_exports     = var.enabled_cloudwatch_logs_exports
  deletion_protection                 = local.delete_protection

  kms_key_id = var.custom_kms_key_enabled ? (
    var.custom_cmk_key_arn != null ? var.custom_cmk_key_arn : aws_kms_key.kms_key.0.arn
  ) : null

  scaling_configuration {
    auto_pause               = true
    max_capacity             = var.rds_serverless_cluster_max_capacity
    min_capacity             = var.rds_serverless_cluster_min_capacity
    seconds_until_auto_pause = var.rds_serverless_cluster_auto_pause_seconds
  }

  tags = local.combined_resource_tags

  # Ignore master password and AZ changes
  lifecycle {
    ignore_changes = [
      master_username,
      master_password,
      availability_zones,
      snapshot_identifier,
    ]
  }
}

resource "aws_rds_cluster_instance" "cluster_instances" {
  count                        = var.rds_cluster_instance_count
  identifier                   = "${var.environment}-${var.service_name}-${count.index}"
  cluster_identifier           = aws_rds_cluster.rds_cluster.0.id
  engine                       = var.rds_engine
  engine_version               = var.rds_engine_version
  apply_immediately            = var.rds_apply_immediately
  auto_minor_version_upgrade   = var.rds_cluster_instance_auto_minor_version_upgrade
  db_subnet_group_name         = local.subnet_group_name
  db_parameter_group_name      = local.instance_parameter_group_name
  monitoring_interval          = var.rds_cluster_instance_monitoring_interval
  monitoring_role_arn          = aws_iam_role.rds_monitoring_role.arn
  performance_insights_enabled = var.rds_performance_insights_enabled
  publicly_accessible          = var.rds_cluster_instance_publicly_accessible

  instance_class = count.index == 0 ? var.rds_cluster_instance_class : (
    var.rds_cluster_instance_class_secondary == "none" ?
    var.rds_cluster_instance_class : var.rds_cluster_instance_class_secondary
  )

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [engine_version]
  }

  tags = local.combined_resource_tags
}

# It should be accessible within corporate network
resource "aws_security_group" "rds-sg" {
  name_prefix = "${var.environment}-${var.service_name}-rds-sg"
  description = "RDS security group for ${var.environment}-${var.service_name}"
  vpc_id      = data.aws_vpc.vpc.id

  # RDS access
  ingress {
    from_port = local.port
    to_port   = local.port
    protocol  = "tcp"
    cidr_blocks = concat(
      var.rds_ingress_cidr_blocks,
    )
    prefix_list_ids = concat(
      data.aws_ec2_managed_prefix_lists.private_subnets.ids,
      [for prefix_list in data.aws_ec2_managed_prefix_list.ingress_prefix_lists : prefix_list.id]
    )
    security_groups = var.rds_ingress_security_groups
  }

  # RDS proxy access
  ingress {
    from_port = local.port
    to_port   = local.port
    protocol  = "tcp"
    self      = true
  }

  # Outbound internet access
  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"

    cidr_blocks = [
      "0.0.0.0/0",
    ]
  }

  tags = merge(local.combined_resource_tags, local.primsa_sg_exception_tags)

  lifecycle {
    create_before_destroy = true # It is not possible to destroy the security group if it is in use by RDS instances
  }
}

resource "aws_route53_record" "networking_rds_cluster_cname" {
  provider = aws.dns
  count    = var.route53_record_creation_enabled ? 1 : 0

  zone_id = local.route53_zone_id
  name    = "${var.environment}-${var.service_name}-db"
  type    = "CNAME"
  ttl     = var.route53_ttl
  records = aws_rds_cluster.rds_cluster.*.endpoint
}

resource "aws_route53_record" "networking_rds_readonly_cluster_cname" {
  provider = aws.dns
  count    = var.route53_record_creation_enabled ? 1 : 0

  zone_id = local.route53_zone_id
  name    = "${var.environment}-${var.service_name}-db-ro"
  type    = "CNAME"
  ttl     = var.route53_ttl
  records = aws_rds_cluster.rds_cluster.*.reader_endpoint
}
