data "aws_caller_identity" "current" {}

data "aws_vpc" "current_vpc" {
  count = var.vpc_enabled ? 1 : 0
  id    = var.vpc_id
}

data "aws_security_group" "default" {
  count = var.vpc_enabled ? 1 : 0

  name   = var.vpc_security_group_name == "" ? local.default_security_group : var.vpc_security_group_name
  vpc_id = data.aws_vpc.current_vpc[0].id
}

# A workaround to not delete security group until after network interface is deleted
# A separate data source is needed to avoid circular dependency
data "aws_security_groups" "check_sg_existance" {
  filter {
    name   = "group-name"
    values = ["${var.environment}-${var.lambda_name}-lambda-security-group"]
  }
}

data "aws_network_interfaces" "check_eni_sg_attachment" {
  filter {
    name   = "group-id"
    values = length(data.aws_security_groups.check_sg_existance.ids) > 0 ? data.aws_security_groups.check_sg_existance.ids : ["sg-fakeid"]
  }
}

locals {
  eni_attached              = length(data.aws_network_interfaces.check_eni_sg_attachment.ids) > 0 ? true : false
  vpc_create_security_group = var.vpc_enable_eni_lookup ? (local.eni_attached || var.vpc_create_security_group) : var.vpc_create_security_group
}

resource "aws_security_group" "lambda_security_group" {
  count       = local.vpc_create_security_group ? 1 : 0
  name        = "${var.environment}-${var.lambda_name}-lambda-security-group"
  description = "Lambda security group for ${var.environment}-${var.lambda_name}"
  vpc_id      = data.aws_vpc.current_vpc[0].id

  tags = merge({ "type" = "lambda" }, local.combined_resource_tags, local.primsa_sg_exception_tags)
}

resource "aws_security_group_rule" "allow_lambda_egress" {
  count             = local.vpc_create_security_group ? 1 : 0
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.lambda_security_group[0].id
}

data "aws_iam_policy" "splitio_api_key_policy" {
  count = var.splitio_enabled ? 1 : 0

  name = "SecretsManager-${var.environment}-split-policy"
}

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

    principals {
      type        = "Service"
      identifiers = var.lambda_assume_role_service_identifiers
    }
  }
}

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

resource "aws_iam_role" "lambda_execution_role" {
  name                 = "lambda-${var.environment}-${var.lambda_name}"
  assume_role_policy   = data.aws_iam_policy_document.assume_lambda_role.json
  permissions_boundary = local.permissions_boundary_arn

  tags = local.combined_resource_tags

  lifecycle {
    precondition {
      condition     = length("lambda-${var.environment}-${var.lambda_name}") <= 64
      error_message = "IAM role name must be 64 characters or less"
    }
  }
}

# Basic execution policy for lambda
data "aws_iam_policy_document" "lambda_basic_policy" {

  statement {
    effect = "Allow"

    actions = [
      "logs:*",
    ]

    resources = [
      "arn:aws:logs:*:${data.aws_caller_identity.current.account_id}:log-group:*",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "ec2:DescribeNetworkInterfaces",
    ]

    resources = [
      "*",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "ec2:CreateNetworkInterface",
      "ec2:DeleteNetworkInterface",
    ]

    resources = [
      "arn:aws:ec2:*:${data.aws_caller_identity.current.account_id}:*",
    ]
  }

  dynamic "statement" {
    for_each = (var.dlq_type == "sns" && var.dlq_enabled) ? ["SNS enabled"] : []
    content {
      effect = "Allow"

      actions = [
        "sns:List*",
        "sns:Publish",
      ]

      resources = [
        aws_sns_topic.dead_letter_topic.0.arn,
      ]
    }
  }

  dynamic "statement" {
    for_each = (var.dlq_type == "sqs" && var.dlq_enabled) ? ["sqs enabled"] : []
    content {
      effect = "Allow"

      actions = [
        "sqs:List*",
        "sqs:SendMessage",
      ]

      resources = [
        aws_sqs_queue.dead_letter_queue.0.arn,
      ]
    }
  }

  dynamic "statement" {
    for_each = var.kafka_event_enabled ? ["kafka event enabled"] : []
    content {
      effect = "Allow"

      actions = [
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcs"
      ]

      resources = [
        "*",
      ]
    }
  }
}



data "aws_iam_policy_document" "secrets_manager_policy" {
  statement {
    actions = [
      "secretsmanager:GetResourcePolicy",
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret",
      "secretsmanager:ListSecretVersionIds",
    ]

    resources = [
      "arn:aws:secretsmanager:*:*:secret:${var.environment}/${var.lambda_name}/",
      "arn:aws:secretsmanager:*:*:secret:${var.environment}/${var.lambda_name}/*",
    ]
  }

  statement {
    actions = [
      "secretsmanager:GetRandomPassword",
    ]

    resources = [
      "*",
    ]
  }
}

data "aws_iam_policy_document" "kms_decryption_policy" {
  statement {
    actions = [
      "kms:Decrypt",
      "kms:DescribeKey",
      "kms:GenerateDataKey",
    ]

    resources = [
      aws_kms_key.lambda_kms_key.arn,
    ]
  }
}

resource "aws_iam_role_policy" "lambda_basic_policy" {
  name   = "lambda-${var.environment}-${var.lambda_name}"
  role   = aws_iam_role.lambda_execution_role.name
  policy = data.aws_iam_policy_document.lambda_basic_policy.json
}

resource "aws_iam_role_policy" "lambda_secrets_manager_policy" {
  name   = "SecretsManager-${var.environment}-${var.lambda_name}-policy"
  role   = aws_iam_role.lambda_execution_role.name
  policy = data.aws_iam_policy_document.secrets_manager_policy.json
}

# Attach ows machine-to-machine secrets manager policy
resource "aws_iam_role_policy_attachment" "ows_machine_to_machine_secrets_manager_iam_policy_attachment" {
  count      = var.ows_machine_to_machine_enabled ? 1 : 0
  role       = aws_iam_role.lambda_execution_role.name
  policy_arn = local.ows_machine_to_machine_manager_policy_arn
}

resource "aws_iam_role_policy" "lambda_kms_policy" {
  name   = "KMS-lambda-${var.environment}-${var.lambda_name}-policy"
  role   = aws_iam_role.lambda_execution_role.name
  policy = data.aws_iam_policy_document.kms_decryption_policy.json
}

# Attach Managed Xray policy to role
resource "aws_iam_role_policy_attachment" "lambda_managed_xray_policy_attachment" {
  role       = aws_iam_role.lambda_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess"
}

# Attach Zappa S3 access policy to role
resource "aws_iam_role_policy_attachment" "lambda_s3_zappa_policy_attachment" {
  count      = var.zappa_s3_policy_enabled ? 1 : 0
  role       = aws_iam_role.lambda_execution_role.name
  policy_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/S3-${var.environment}-lambda-zappa-RO"
}

# Attach split secrets manager policies
resource "aws_iam_role_policy_attachment" "splitio_secrets_manager_iam_policy_attachment" {
  count      = var.splitio_enabled ? 1 : 0
  role       = aws_iam_role.lambda_execution_role.name
  policy_arn = data.aws_iam_policy.splitio_api_key_policy[0].arn
}

# Allow IAM policy from file to configure case-specific access requirements, if enabled.
resource "aws_iam_role_policy" "lambda_execution_iam_policy" {
  count = var.iam_policy_file_enabled ? 1 : 0
  name  = "lambda-${var.environment}-${var.lambda_name}-execution-policy"
  role  = aws_iam_role.lambda_execution_role.name
  policy = templatefile(
    "${path.root}/${var.iam_policy_file_location}/${var.lambda_name}.json",
    {
      environment = var.environment
      account_id  = data.aws_caller_identity.current.account_id
    }
  )
}

# Attach additional existing managed IAM policies, if enabled
resource "aws_iam_role_policy_attachment" "existing_iam_policy_attachment" {
  count      = length(var.iam_managed_policy_attachments)
  role       = aws_iam_role.lambda_execution_role.name
  policy_arn = var.iam_managed_policy_attachments[count.index]
}

# Attach datadog secrets manager policy to fetch API keys
resource "aws_iam_role_policy_attachment" "datadog_secrets_manager_iam_policy_attachment" {
  count      = var.datadog_enabled ? 1 : 0
  role       = aws_iam_role.lambda_execution_role.name
  policy_arn = local.datadog_secrets_manager_policy_arn
}

# Create dead letter queue resources
resource "aws_sqs_queue" "dead_letter_queue" {
  count                             = (var.dlq_type == "sqs" && var.dlq_enabled) ? 1 : 0
  name                              = "${var.environment}-${var.lambda_name}-dlq"
  max_message_size                  = var.dlq_sqs_max_message_size
  message_retention_seconds         = var.dlq_sqs_message_retention_seconds
  fifo_queue                        = var.dlq_sqs_fifo_queue
  content_based_deduplication       = var.dlq_sqs_fifo_queue
  visibility_timeout_seconds        = var.dlq_sqs_visibility_timeout_seconds
  kms_master_key_id                 = aws_kms_key.lambda_kms_key.key_id
  kms_data_key_reuse_period_seconds = var.dlq_sqs_kms_data_key_reuse_period_seconds

  tags = local.combined_resource_tags
}

resource "aws_sns_topic" "dead_letter_topic" {
  count             = (var.dlq_type == "sns" && var.dlq_enabled) ? 1 : 0
  name              = "${var.environment}-${var.lambda_name}-dlq"
  kms_master_key_id = aws_kms_key.lambda_kms_key.key_id

  tags = local.combined_resource_tags
}

resource "aws_kms_key" "lambda_kms_key" {
  description             = "lambda-${var.environment}-${var.lambda_name}"
  enable_key_rotation     = true
  deletion_window_in_days = 30

  tags = local.combined_resource_tags
}

resource "aws_kms_alias" "lambda_kms_alias" {
  name          = "alias/lambda-${var.environment}-${var.lambda_name}"
  target_key_id = aws_kms_key.lambda_kms_key.key_id
}

# Explicitly manage the Cloudwatch log group location that Lambda will choose
resource "aws_cloudwatch_log_group" "lambda_log_group" {
  name              = "/aws/lambda/${var.use_custom_function_name ? var.custom_function_name : local.generated_function_name}"
  retention_in_days = var.log_retention_in_days
  tags              = local.combined_resource_tags
}

# Create the function
resource "aws_lambda_function" "function" {
  # checkov:skip=CKV_AWS_116:Silencing Checkov false warning as the DLQ is enabled by default
  # checkov:skip=CKV_AWS_50:Silencing Checkov false warning as the XRay tracing is enabled by default
  filename                       = local.use_s3_source || var.use_container_image ? null : var.lambda_function_package_path
  s3_bucket                      = local.use_s3_source ? var.s3_source_bucket : null
  s3_key                         = local.use_s3_source ? local.s3_source_key : null
  image_uri                      = var.use_container_image ? local.container_image_uri : null
  package_type                   = var.use_container_image ? "Image" : "Zip"
  function_name                  = local.function_name
  description                    = var.lambda_description
  runtime                        = var.use_container_image ? null : var.lambda_runtime
  memory_size                    = local.lambda_function_memory_size
  role                           = aws_iam_role.lambda_execution_role.arn
  publish                        = local.publish_version
  handler                        = var.use_container_image ? null : local.lambda_function_handler
  timeout                        = var.lambda_function_timeout
  reserved_concurrent_executions = var.lambda_function_reserved_concurrent_executions
  kms_key_arn                    = aws_kms_key.lambda_kms_key.arn
  architectures                  = var.architectures

  replace_security_groups_on_destroy = local.replace_security_groups_on_destroy
  replacement_security_group_ids     = coalesce(local.replace_security_groups_on_destroy, false) ? data.aws_security_group.default[*].id : null

  ephemeral_storage {
    size = var.lambda_function_ephemeral_storage_size
  }

  dynamic "vpc_config" {
    for_each = var.vpc_enabled ? ["true"] : []
    content {
      subnet_ids         = var.vpc_subnet_ids
      security_group_ids = var.vpc_create_security_group ? aws_security_group.lambda_security_group[*].id : data.aws_security_group.default[*].id
    }
  }

  dynamic "file_system_config" {
    for_each = var.efs_enabled ? [""] : []
    content {
      arn              = var.efs_access_point_arn
      local_mount_path = var.efs_local_mount_path
    }
  }

  dynamic "dead_letter_config" {
    for_each = var.dlq_enabled ? [""] : []
    content {
      target_arn = var.dlq_type == "sqs" ? aws_sqs_queue.dead_letter_queue.0.arn : aws_sns_topic.dead_letter_topic.0.arn
    }
  }

  tracing_config {
    mode = local.tracing_mode
  }

  environment {
    variables = local.lambda_function_environment_variables
  }

  layers = local.lambda_layers

  tags = merge(
    local.combined_resource_tags,
    var.use_container_image ? {} : { runtime = var.lambda_runtime }
  )

  lifecycle {
    ignore_changes = [image_uri]
    precondition {
      condition     = length(local.function_name) <= 64
      error_message = "Function name must be 64 characters or less"
    }
  }

  depends_on = [
    aws_cloudwatch_log_group.lambda_log_group,
    time_sleep.lambda_role_existing_iam_policy_attachments_propagation,
    time_sleep.lambda_efs_access_point_propagation,
  ]
}

resource "aws_cloudwatch_log_subscription_filter" "datadog_lambda_function_log_filter" {
  count           = local.cloudwatch_log_subscription_enabled ? 1 : 0
  name            = "${var.environment}-${var.lambda_name}-lambda-logs-subscription-filter"
  log_group_name  = aws_cloudwatch_log_group.lambda_log_group.name
  filter_pattern  = ""
  destination_arn = local.datadog_function_destination_arn
  distribution    = "ByLogStream"
}

resource "aws_lambda_alias" "provisioned" {
  count            = local.provisioned_concurrency_enabled ? 1 : 0
  name             = var.lambda_function_provisioned_concurrency_alias
  description      = "Alias to a provisioned version."
  function_name    = aws_lambda_function.function.function_name
  function_version = aws_lambda_function.function.version
}

resource "aws_lambda_provisioned_concurrency_config" "lambda_versioned" {
  count                             = local.provisioned_concurrency_enabled ? 1 : 0
  function_name                     = aws_lambda_function.function.function_name
  qualifier                         = aws_lambda_alias.provisioned[count.index].name
  provisioned_concurrent_executions = var.lambda_function_provisioned_concurrent_executions
}
