# ── S3 bucket ─────────────────────────────────────────────────────────────────
# TiKV and TiFlash use this bucket as their object storage backend.
# Only created when var.s3_create_bucket = true; otherwise the bucket is assumed
# to already exist (e.g. pre-created by another process).

resource "aws_s3_bucket" "tidb_x" {
  count  = var.s3_create_bucket ? 1 : 0
  bucket = var.s3_bucket_name
  tags   = merge(var.tags, { Name = var.s3_bucket_name })
}

resource "aws_s3_bucket_public_access_block" "tidb_x" {
  count  = var.s3_create_bucket ? 1 : 0
  bucket = aws_s3_bucket.tidb_x[0].id

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

# ── IAM: S3 access policy for TiKV / TiFlash ─────────────────────────────────
# Grants the minimum S3 actions required by TiKV DFS and TiFlash disaggregated
# storage. Attached to an IRSA role so pods never need long-lived credentials.

resource "aws_iam_policy" "tidb_x_s3" {
  name        = "${var.eks_cluster_name}-tidb-x-s3"
  description = "Allow TiKV/TiFlash pods to access their S3 bucket via IRSA."

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "ListTidbXS3Bucket"
        Effect = "Allow"
        Action = [
          "s3:GetBucketLocation",
          "s3:ListBucket",
          "s3:ListBucketMultipartUploads",
        ]
        Resource = "arn:aws:s3:::${var.s3_bucket_name}"
      },
      {
        Sid    = "ReadWriteTidbXS3Objects"
        Effect = "Allow"
        Action = [
          "s3:AbortMultipartUpload",
          "s3:DeleteObject",
          "s3:GetObject",
          "s3:ListMultipartUploadParts",
          "s3:PutObject",
        ]
        Resource = "arn:aws:s3:::${var.s3_bucket_name}/*"
      },
    ]
  })

  tags = var.tags
}

# ── IAM: IRSA role ────────────────────────────────────────────────────────────
# The ServiceAccount (below) references this role via the
# eks.amazonaws.com/role-arn annotation so the EKS Pod Identity Webhook can
# inject short-lived AWS credentials into matching pods.

module "tidb_x_s3_irsa_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.0"

  role_name = "${var.eks_cluster_name}-tidb-x-s3"

  role_policy_arns = {
    s3 = aws_iam_policy.tidb_x_s3.arn
  }

  oidc_providers = {
    main = {
      provider_arn               = var.oidc_provider_arn
      namespace_service_accounts = ["${var.cluster_namespace}:${var.s3_service_account}"]
    }
  }

  tags = var.tags
}

# ── Kubernetes namespaces ─────────────────────────────────────────────────────

resource "kubernetes_namespace" "tidb_operator" {
  metadata {
    name = var.operator_namespace
    labels = {
      "app.kubernetes.io/managed-by" = "terraform"
    }
  }
}

resource "kubernetes_namespace" "tidb_cluster" {
  metadata {
    name = var.cluster_namespace
    labels = {
      "app.kubernetes.io/managed-by" = "terraform"
    }
  }
}

# ── Helm: tidb-operator ───────────────────────────────────────────────────────
# Installs the TiDB Operator v2 from the official OCI Helm registry.
# Helm manages CRD installation via the chart's built-in CRD hooks, so no
# separate kubectl apply step is needed.

locals {
  # Resolve the operator image tag: prefer the explicit override, fall back to
  # the chart version so operator image and chart are always in sync.
  operator_image_tag = var.operator_image_tag != "" ? var.operator_image_tag : var.operator_chart_version
}

resource "helm_release" "tidb_operator" {
  name       = "tidb-operator"
  repository = "oci://ghcr.io/pingcap/charts"
  chart      = "tidb-operator"
  version    = var.operator_chart_version
  namespace  = var.operator_namespace

  # Namespace was created above; do not let Helm attempt to recreate it.
  create_namespace = false

  # Allow re-running plan/apply without manual --force-conflicts.
  force_update = false

  values = [
    yamlencode({
      operator = {
        replicas = 1
        image = {
          repository = var.operator_image
          tag        = local.operator_image_tag
        }
      }
      webhook = {
        # The admission webhook validates TiDB CRD objects at admission time.
        create = true
      }
      # Helm manages CRDs; do NOT skip CRD management.
      # skipCRDsManagement = false  (default)
    })
  ]

  depends_on = [kubernetes_namespace.tidb_operator]
}

# ── Kubernetes: IRSA ServiceAccount ──────────────────────────────────────────
# TiKV and TiFlash pods must run under this ServiceAccount to receive the
# IRSA-injected S3 credentials. Created after the cluster namespace exists.

resource "kubernetes_service_account" "tidb_x_s3" {
  metadata {
    name      = var.s3_service_account
    namespace = var.cluster_namespace
    annotations = {
      # The EKS Pod Identity Webhook reads this annotation and mounts a
      # projected service account token bound to the specified IAM role.
      "eks.amazonaws.com/role-arn" = module.tidb_x_s3_irsa_role.iam_role_arn
    }
    labels = {
      "app.kubernetes.io/managed-by" = "terraform"
    }
  }

  depends_on = [kubernetes_namespace.tidb_cluster]
}

# ── Helm: tidb cluster ────────────────────────────────────────────────────────
# Deploys the TiDB cluster CRD instances (Cluster, PDGroup, TiKVGroup, etc.)
# via a local Helm chart that wraps all the core.pingcap.com/v1alpha1 resources.
# Using Helm here gives us proper lifecycle management (upgrade, rollback) and
# avoids plan-time CRD schema validation issues that kubernetes_manifest has.

locals {
  # Whether the cluster runs PD in microservices mode.
  # Both TSO and Scheduling services must be enabled together for mode=ms.
  pd_ms_mode = var.pd_tso_enabled && var.pd_scheduling_enabled

  # Resolve per-component versions, falling back to the cluster-wide default.
  pd_version             = var.pd_version != "" ? var.pd_version : var.cluster_version
  tikv_version           = var.tikv_version != "" ? var.tikv_version : var.cluster_version
  tidb_version           = var.tidb_version != "" ? var.tidb_version : var.cluster_version
  pd_tso_version         = var.pd_tso_version != "" ? var.pd_tso_version : var.cluster_version
  pd_scheduling_version  = var.pd_scheduling_version != "" ? var.pd_scheduling_version : var.cluster_version
  tiproxy_version        = var.tiproxy_version != "" ? var.tiproxy_version : var.cluster_version
  tiflash_write_version  = var.tiflash_write_version != "" ? var.tiflash_write_version : var.cluster_version
  tiflash_compute_version = var.tiflash_compute_version != "" ? var.tiflash_compute_version : var.cluster_version

  # Resolve TSO/Scheduling image: fall back to the PD image (they ship together).
  pd_tso_image        = var.pd_tso_image != "" ? var.pd_tso_image : var.pd_image
  pd_scheduling_image = var.pd_scheduling_image != "" ? var.pd_scheduling_image : var.pd_image
}

resource "helm_release" "tidb_cluster" {
  name      = var.cluster_name
  chart     = "${path.module}/chart"
  namespace = var.cluster_namespace

  # Re-apply on every terraform apply so component spec changes are reflected.
  force_update = true

  # EKS openapi/v2 endpoint is large (~12MB) and frequently exceeds the helm
  # provider's 32s download timeout. Skip schema validation — CRDs are already
  # installed by the operator chart and are validated by the API server itself.
  disable_openapi_validation = true

  values = [
    yamlencode({
      cluster = {
        name      = var.cluster_name
        namespace = var.cluster_namespace
        version   = var.cluster_version
      }

      pd = {
        image      = var.pd_image
        version    = local.pd_version
        replicas   = var.pd_replicas
        storage    = var.pd_storage
        config     = var.pd_config
        extra_spec = var.pd_extra_spec
        # "ms" enables PD microservices mode (TSO + Scheduling run separately).
        mode = local.pd_ms_mode ? "ms" : ""
      }

      tikv = {
        image      = var.tikv_image
        version    = local.tikv_version
        replicas   = var.tikv_replicas
        storage    = var.tikv_storage
        config     = var.tikv_config
        extra_spec = var.tikv_extra_spec
      }

      tidb = {
        image      = var.tidb_image
        version    = local.tidb_version
        replicas   = var.tidb_replicas
        config     = var.tidb_config
      }

      pd_tso = {
        enabled  = var.pd_tso_enabled
        image    = local.pd_tso_image
        version  = local.pd_tso_version
        replicas = var.pd_tso_replicas
      }

      pd_scheduling = {
        enabled  = var.pd_scheduling_enabled
        image    = local.pd_scheduling_image
        version  = local.pd_scheduling_version
        replicas = var.pd_scheduling_replicas
      }

      tiproxy = {
        enabled  = var.tiproxy_enabled
        image    = var.tiproxy_image
        version  = local.tiproxy_version
        replicas = var.tiproxy_replicas
      }

      tiflash_write = {
        enabled    = var.tiflash_write_enabled
        image      = var.tiflash_write_image
        version    = local.tiflash_write_version
        replicas   = var.tiflash_write_replicas
        storage    = var.tiflash_write_storage
        config     = var.tiflash_write_config
        extra_spec = var.tiflash_write_extra_spec
      }

      tiflash_compute = {
        enabled    = var.tiflash_compute_enabled
        image      = var.tiflash_compute_image
        version    = local.tiflash_compute_version
        replicas   = var.tiflash_compute_replicas
        storage    = var.tiflash_compute_storage
        config     = var.tiflash_compute_config
        extra_spec = var.tiflash_compute_extra_spec
      }
    })
  ]

  # The cluster CRD instances depend on:
  # 1. tidb-operator being installed (it registers the CRDs)
  # 2. The IRSA ServiceAccount existing (TiKV/TiFlash pods use it for S3 access)
  depends_on = [
    helm_release.tidb_operator,
    kubernetes_service_account.tidb_x_s3,
  ]
}
