# NOTE: This bucket must be emptied manually before `terraform destroy`.
# AWS refuses to delete non-empty buckets, causing the destroy to fail.
resource "aws_s3_bucket" "ds-logs" {
  bucket = "${local.eks.name}-ds-logs"

  tags = merge({
    tidbcloud-auto-deploy-owner = local.eks.name
  }, var.tags)
}

resource "aws_s3_bucket_public_access_block" "ds-logs" {
  bucket = aws_s3_bucket.ds-logs.id

  block_public_acls       = true
  ignore_public_acls      = true
  block_public_policy     = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_policy" "ds-logs" {
  bucket = aws_s3_bucket.ds-logs.id
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Sid" : "AWSConsoleStmt-regions-after-202208",
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "logdelivery.elasticloadbalancing.amazonaws.com"
        },
        "Action" : "s3:PutObject",
        "Resource" : "arn:aws:s3:::${aws_s3_bucket.ds-logs.id}/*"
      },
      {
        "Sid" : "AWSLogDeliveryWrite",
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "delivery.logs.amazonaws.com"
        },
        "Action" : "s3:PutObject",
        "Resource" : "arn:aws:s3:::${aws_s3_bucket.ds-logs.id}/*",
        "Condition" : {
          "StringEquals" : {
            "s3:x-amz-acl" : "bucket-owner-full-control"
          }
        }
      },
      {
        "Sid" : "AWSLogDeliveryAclCheck",
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "delivery.logs.amazonaws.com"
        },
        "Action" : "s3:GetBucketAcl",
        "Resource" : "arn:aws:s3:::${aws_s3_bucket.ds-logs.id}"
      }
    ]
  })
}

module "cloud-addon" {
  source     = "../../../modules/app"
  depends_on = [module.o11y-infra-cd, module.register-region-meta]

  workflow = "new-region-cloud-addon.yaml"
  gh_repo  = "tidbcloud/observability"
  parameters = {
    vendor            = "aws"
    region            = var.aws_region
    logicalRegionName = var.logical_region_name
    env               = var.env
    cluster           = local.eks.name
    args = jsonencode({
      hostedZoneName           = var.o11y_hosted_zone_name
      hostedZoneId             = var.o11y_hosted_zone_id
      imageRegistry            = var.byoc_ecr
      clusterAutoscalerRoleArn = local.o11y_control_plane_role_arn
      albControllerRoleArn     = local.o11y_control_plane_role_arn
      externalDNSRoleArn       = local.o11y_control_plane_role_arn
      dsLogsBucketName         = aws_s3_bucket.ds-logs.bucket
      subnetName               = module.vpc.private_subnet_objects[0].tags.Name
      byoc                     = true
    })
  }
  wait_workflow_success = true
  finalizer = {
    workflow = "destroy-region-cloud-addon.yaml"
    parameters = {
      vendor            = "aws"
      region            = var.aws_region
      logicalRegionName = var.logical_region_name
      env               = var.env
    }
  }
}

# Destroy-time finalizer: deletes the nginx Services and Ingress objects so that
# AWS deregisters and removes the backing NLBs (and their ENIs) BEFORE EKS and
# the VPC are torn down. If the NLBs are not removed first, VPC deletion will
# fail because the ENIs are still in use.
#
# Real-world caveat: the security team applies deletion protection to the AWS LBs
# and these Kubernetes resources. Terraform cannot remove deletion-protected
# resources, so this provisioner will stall or fail on protected environments.
# Before running `terraform destroy`, manually remove the deletion protection from
# the AWS Load Balancer(s) and the corresponding Kubernetes Service/Ingress objects
# (e.g. remove finalizers or deletion-protection annotations), otherwise the
# destroy will not complete.
resource "null_resource" "cloud-addon-svc-finalizer" {
  depends_on = [
    module.cloud-addon,
    null_resource.control-plane-role-policy,
    module.eks,
    module.vpc,
  ]

  lifecycle {
    ignore_changes = all
  }

  triggers = {
    kubeconfig_path = module.kubeconfig.path
  }

  provisioner "local-exec" {
    when        = destroy
    interpreter = ["bash", "-c"]
    environment = {
      KUBECONFIG = self.triggers.kubeconfig_path
    }
    command = <<-EOT
      set -xeou pipefail

      kubectl delete svc nginx-write -n kube-system --ignore-not-found
      kubectl delete svc ingress-nginx-write-controller -n kube-system --ignore-not-found
      kubectl delete ingress nginx-read -n kube-system --ignore-not-found
      kubectl delete ingress nginx-ingress -n kube-system --ignore-not-found

      kubectl wait --for=delete svc/nginx-write -n kube-system --timeout=600s
      kubectl wait --for=delete svc/ingress-nginx-write-controller -n kube-system --timeout=600s
      kubectl wait --for=delete ingress/nginx-read -n kube-system --timeout=600s
      kubectl wait --for=delete ingress/nginx-ingress -n kube-system --timeout=600s
    EOT
  }
}

module "cloud-addon-cd" {
  depends_on = [module.cloud-addon]
  source     = "../../../modules/app"

  kubeconfig_path = module.kubeconfig.path
  workflow        = "cloud-addon-cd.yaml"
  gh_repo         = "tidbcloud/observability"
  parameters = {
    vendor       = "aws"
    region       = var.logical_region_name
    env          = var.env
    git-ref      = "main"
    aws-role-arn = "arn:aws:iam::${local.aws_account_id}:role/auto-deploy-cli"
    bootstrap    = "true"
  }
  services = [
    {
      name      = "deploy/ingress-nginx-read-controller"
      namespace = "kube-system"
      wait_for  = "jsonpath='{.status.availableReplicas}'=REPLICAS"
    },
    {
      name      = "deploy/ingress-nginx-write-controller"
      namespace = "kube-system"
      wait_for  = "jsonpath='{.status.availableReplicas}'=REPLICAS"
    },
    {
      name      = "deploy/external-dns"
      namespace = "kube-system"
      wait_for  = "jsonpath='{.status.availableReplicas}'=REPLICAS"
    },
    {
      name      = "deploy/cluster-autoscaler"
      namespace = "kube-system"
      wait_for  = "jsonpath='{.status.availableReplicas}'=REPLICAS"
    },
    {
      name      = "deploy/aws-load-balancer-controller"
      namespace = "kube-system"
      wait_for  = "jsonpath='{.status.availableReplicas}'=REPLICAS"
    }
  ]
  wait_workflow_success = true
  check_before_destroy  = false
}

resource "null_resource" "wait-ngx-w-active" {
  depends_on = [module.cloud-addon-cd]

  triggers = {
    lb_name = "ngx-w-${var.logical_region_name}"
  }

  provisioner "local-exec" {
    command = <<-EOT
      aws elbv2 wait load-balancer-available \
        --profile ${local.aws_account_id} \
        --region ${var.aws_region} \
        --names ngx-w-${var.logical_region_name}
    EOT
  }
}

data "aws_lb" "ngx-w" {
  count      = var.destroy_mode ? 0 : 1
  depends_on = [null_resource.wait-ngx-w-active, module.kubeconfig]
  name       = "ngx-w-${var.logical_region_name}"
}

resource "aws_cloudformation_stack" "vpc-endpoint-service" {
  name = "auto-deploy-${local.eks.name}-vpc-endpoint-service"

  template_body = file("${path.module}/cloudformation/vpc-endpoint-service.yaml")
  parameters = {
    NLBArn               = try(data.aws_lb.ngx-w[0].arn, "")
    NLBAllowedPrincipals = "arn:aws:iam::${local.aws_account_id}:root"
    ResourceOwnerTag     = local.eks.name
  }
  tags = merge({
    tidbcloud-auto-deploy-owner = local.eks.name
  }, var.tags)
}

locals {
  vpc_endpoint_zone_suffix = trimprefix(local.zone_name, var.aws_region)
  vpc_endpoint_dns_name    = "prometheus-endpoint-${local.vpc_endpoint_zone_suffix}.${var.o11y_hosted_zone_name}"
}

resource "null_resource" "vpc-endpoint-service-configuration" {
  depends_on = [aws_cloudformation_stack.vpc-endpoint-service]

  triggers = {
    service_id = aws_cloudformation_stack.vpc-endpoint-service.outputs.VpcEndpointServiceId
    dns_name   = local.vpc_endpoint_dns_name
  }

  provisioner "local-exec" {
    command = <<-EOT
      aws ec2 modify-vpc-endpoint-service-configuration \
        --profile ${local.aws_account_id} --region ${var.aws_region} \
        --service-id ${self.triggers.service_id} \
        --private-dns-name ${self.triggers.dns_name}
    EOT
  }
}

data "external" "vpc-endpoint-service-configurations" {
  depends_on = [null_resource.vpc-endpoint-service-configuration]

  program = [
    "aws", "ec2", "describe-vpc-endpoint-service-configurations",
    "--profile", local.aws_account_id,
    "--region", var.aws_region,
    "--service-id", aws_cloudformation_stack.vpc-endpoint-service.outputs.VpcEndpointServiceId,
    "--query", "ServiceConfigurations[0].PrivateDnsNameConfiguration",
    "--output", "json"
  ]
}

resource "aws_route53_record" "vpc-endpoint-service" {
  zone_id = var.o11y_hosted_zone_id
  name    = "${data.external.vpc-endpoint-service-configurations.result.Name}.${local.vpc_endpoint_dns_name}"
  type    = data.external.vpc-endpoint-service-configurations.result.Type
  records = [data.external.vpc-endpoint-service-configurations.result.Value]
  ttl     = 300
}

# Fail the region bring-up early if AWS does not finish verifying the endpoint
# service's private DNS ownership after the correct TXT record is published.
resource "null_resource" "wait-vpc-endpoint-service-private-dns-verified" {
  depends_on = [aws_route53_record.vpc-endpoint-service]

  triggers = {
    service_id         = aws_cloudformation_stack.vpc-endpoint-service.outputs.VpcEndpointServiceId
    private_dns_name   = local.vpc_endpoint_dns_name
    verification_fqdn  = aws_route53_record.vpc-endpoint-service.fqdn
    verification_value = one(aws_route53_record.vpc-endpoint-service.records)
  }

  provisioner "local-exec" {
    interpreter = ["bash", "-c"]
    command     = <<-EOT
      set -euo pipefail

      for i in $(seq 1 90); do
        state=$(aws ec2 describe-vpc-endpoint-service-configurations \
          --profile ${local.aws_account_id} \
          --region ${var.aws_region} \
          --service-ids "${self.triggers.service_id}" \
          --query 'ServiceConfigurations[0].PrivateDnsNameConfiguration.State' \
          --output text 2>/dev/null || echo "error")

        if [ "$state" = "error" ]; then
          echo "Error querying Private DNS verification state for ${self.triggers.private_dns_name} ($i/90); retrying..." >&2
          sleep 10
          continue
        fi

        echo "Private DNS verification for ${self.triggers.private_dns_name}: $state ($i/90)"

        if [ "$state" = "verified" ]; then
          exit 0
        fi

        if [ "$state" = "failed" ]; then
          echo "Private DNS verification failed for ${self.triggers.private_dns_name}. TXT ${self.triggers.verification_fqdn} must resolve to ${self.triggers.verification_value}." >&2
          exit 1
        fi

        sleep 10
      done

      echo "Timed out waiting for Private DNS verification of ${self.triggers.private_dns_name}. Last checked TXT ${self.triggers.verification_fqdn} -> ${self.triggers.verification_value}." >&2
      exit 1
    EOT
  }
}

resource "aws_vpc_endpoint" "s3" {
  vpc_id            = module.vpc.vpc_id
  service_name      = "com.amazonaws.${var.aws_region}.s3"
  vpc_endpoint_type = "Gateway"
  route_table_ids   = local.vpc_route_table_ids
  tags = merge({
    tidbcloud-auto-deploy-owner = local.eks.name
  }, var.tags)
}
