# ──────────────────────────────────────────────────────────────────────
# Sentry — Ops Monitoring stack
# ──────────────────────────────────────────────────────────────────────
#
# Deploys two workloads (sharing one ECR image):
#
#   * sentry-service (long-running, 10-min cycle) — NOT YET ENABLED
#     Awaiting the CRM /internal/sentry-metrics endpoint to ship to
#     staging. When ready, add an aws_ecs_service resource and a task
#     definition for the `sentry` binary.
#
#   * sentry-daily-report (scheduled one-shot) — ACTIVE
#     EventBridge cron(0 1 * * ? *) → ECS RunTask → sentry-probe binary
#     → multi-target snapshot → Lark digest. Fires daily at
#     01:00 UTC = 09:00 UTC+8.
#
# Sentry is intentionally restricted to the `prod` workspace because:
#   - `prod` is the stable long-running environment in the nexuscrm AWS
#     account (604175033442). It hosts nexuscrm-prod-cluster, which is
#     the de-facto production deployment.
#   - The `staging` and `integration` workspaces are ephemeral —
#     frequently destroyed and re-applied for testing — so anything
#     pinned to them would be lost.
#   - Sentry monitors all TiDB targets (prod + staging) from a single
#     prod-located deployment, so it doesn't need its own environment.
#
# To deploy sentry to a different workspace later, change the local below
# or convert it to a `var.deploy_sentry` boolean.
#
# Secret VALUES (DB creds, Qwen key, Lark webhook) are managed manually
# via `aws secretsmanager update-secret` — Terraform owns only the
# secret resource shells (metadata) so rotating values does not require
# a Terraform apply. See lifecycle.ignore_changes blocks below.
# ──────────────────────────────────────────────────────────────────────

locals {
  deploy_sentry = terraform.workspace == "prod"
}

# ── ECR repository ────────────────────────────────────────────────

resource "aws_ecr_repository" "sentry" {
  count                = local.deploy_sentry ? 1 : 0
  name                 = "nexus-sentry"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

# ── CloudWatch Log Group ──────────────────────────────────────────

resource "aws_cloudwatch_log_group" "sentry" {
  count             = local.deploy_sentry ? 1 : 0
  name              = "/ecs/nexus-sentry"
  retention_in_days = 30
}

# ── Secrets Manager ───────────────────────────────────────────────
#
# Resource shells only — values are bootstrapped/rotated outside
# Terraform via `aws secretsmanager put-secret-value`. Drift on
# secret_string is intentionally ignored.

resource "aws_secretsmanager_secret" "sentry_probe_targets" {
  count                   = local.deploy_sentry ? 1 : 0
  name                    = "sentry/probe-targets-json"
  description             = "JSON array of TiDB targets (prod + staging) for sentry-probe daily report"
  recovery_window_in_days = 0
}

resource "aws_secretsmanager_secret" "sentry_dashscope" {
  count                   = local.deploy_sentry ? 1 : 0
  name                    = "sentry/dashscope"
  description             = "Qwen / DashScope API key for alert translation"
  recovery_window_in_days = 0
}

resource "aws_secretsmanager_secret" "sentry_lark_webhook" {
  count                   = local.deploy_sentry ? 1 : 0
  name                    = "sentry/lark-webhook"
  description             = "Lark custom-robot webhook URL + HMAC signing secret (JSON: url, signing_secret)"
  recovery_window_in_days = 0
}

# ── IAM execution role (ECS pulls image, writes logs, fetches secrets) ─

data "aws_iam_policy_document" "sentry_assume_ecs_tasks" {
  count = local.deploy_sentry ? 1 : 0
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "sentry_execution" {
  count              = local.deploy_sentry ? 1 : 0
  name               = "SentryExecutionRole"
  assume_role_policy = data.aws_iam_policy_document.sentry_assume_ecs_tasks[0].json
}

resource "aws_iam_role_policy_attachment" "sentry_execution_managed" {
  count      = local.deploy_sentry ? 1 : 0
  role       = aws_iam_role.sentry_execution[0].name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

data "aws_iam_policy_document" "sentry_secrets_read" {
  count = local.deploy_sentry ? 1 : 0
  statement {
    actions   = ["secretsmanager:GetSecretValue"]
    resources = ["arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:sentry/*"]
  }
}

resource "aws_iam_role_policy" "sentry_execution_secrets" {
  count  = local.deploy_sentry ? 1 : 0
  name   = "SentrySecretsRead"
  role   = aws_iam_role.sentry_execution[0].id
  policy = data.aws_iam_policy_document.sentry_secrets_read[0].json
}

# ── IAM task role (what the running container can call) ───────────
#
# sentry-service fetches secrets directly via the AWS SDK at boot and
# writes snapshot JSON to S3 on every cycle, so the task role (not just
# the execution role) needs secretsmanager:GetSecretValue + S3 put.

resource "aws_iam_role" "sentry_task" {
  count              = local.deploy_sentry ? 1 : 0
  name               = "SentryTaskRole"
  assume_role_policy = data.aws_iam_policy_document.sentry_assume_ecs_tasks[0].json
}

data "aws_iam_policy_document" "sentry_task_permissions" {
  count = local.deploy_sentry ? 1 : 0

  statement {
    sid       = "ReadSentrySecrets"
    actions   = ["secretsmanager:GetSecretValue"]
    resources = ["arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:sentry/*"]
  }

  statement {
    sid       = "ListSnapshotBucket"
    actions   = ["s3:ListBucket"]
    resources = [aws_s3_bucket.sentry_snapshots[0].arn]
  }

  statement {
    sid     = "PutSnapshotObjects"
    actions = ["s3:PutObject", "s3:GetObject"]
    resources = [
      "${aws_s3_bucket.sentry_snapshots[0].arn}/snapshots/*",
      "${aws_s3_bucket.sentry_snapshots[0].arn}/alerts/*",
      "${aws_s3_bucket.sentry_snapshots[0].arn}/slow_tables.json",
    ]
  }
}

resource "aws_iam_role_policy" "sentry_task_permissions" {
  count  = local.deploy_sentry ? 1 : 0
  name   = "SentryTaskPermissions"
  role   = aws_iam_role.sentry_task[0].id
  policy = data.aws_iam_policy_document.sentry_task_permissions[0].json
}

# ── IAM EventBridge invoke role ───────────────────────────────────
#
# EventBridge needs ecs:RunTask on the daily-report task definition,
# plus iam:PassRole for the task's execution and task roles.

data "aws_iam_policy_document" "sentry_eventbridge_assume" {
  count = local.deploy_sentry ? 1 : 0
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["events.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "sentry_eventbridge_invoke" {
  count              = local.deploy_sentry ? 1 : 0
  name               = "SentryEventBridgeInvokeRole"
  assume_role_policy = data.aws_iam_policy_document.sentry_eventbridge_assume[0].json
}

data "aws_iam_policy_document" "sentry_eventbridge_runtask" {
  count = local.deploy_sentry ? 1 : 0
  statement {
    actions   = ["ecs:RunTask"]
    resources = ["arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task-definition/nexus-sentry-daily-report:*"]
  }
  statement {
    actions = ["iam:PassRole"]
    resources = [
      aws_iam_role.sentry_execution[0].arn,
      aws_iam_role.sentry_task[0].arn,
    ]
  }
}

resource "aws_iam_role_policy" "sentry_eventbridge_runtask" {
  count  = local.deploy_sentry ? 1 : 0
  name   = "SentryRunTask"
  role   = aws_iam_role.sentry_eventbridge_invoke[0].id
  policy = data.aws_iam_policy_document.sentry_eventbridge_runtask[0].json
}

# ── ECS Task Definition: sentry-daily-report ──────────────────────
#
# entryPoint is set explicitly (not relying on Docker CMD/ENTRYPOINT)
# so the task definition is portable across image builds that may
# change the default entrypoint.

resource "aws_ecs_task_definition" "sentry_daily_report" {
  count                    = local.deploy_sentry ? 1 : 0
  family                   = "nexus-sentry-daily-report"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.sentry_execution[0].arn
  task_role_arn            = aws_iam_role.sentry_task[0].arn

  container_definitions = jsonencode([{
    name       = "sentry-probe"
    image      = "${aws_ecr_repository.sentry[0].repository_url}:latest"
    essential  = true
    entryPoint = ["/usr/local/bin/sentry-probe"]

    environment = [
      { name = "PROBE_TOP_N",   value = "10" },
      { name = "PROBE_TIMEOUT", value = "10s" },
      { name = "QWEN_API_URL",  value = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1" },
      { name = "QWEN_MODEL",    value = "qwen3.6-plus-2026-04-02" },
      { name = "QWEN_TIMEOUT",  value = "120s" },
    ]

    secrets = [
      { name = "PROBE_TARGETS_JSON",  valueFrom = aws_secretsmanager_secret.sentry_probe_targets[0].arn },
      { name = "QWEN_API_KEY",        valueFrom = aws_secretsmanager_secret.sentry_dashscope[0].arn },
      { name = "LARK_WEBHOOK_URL",    valueFrom = "${aws_secretsmanager_secret.sentry_lark_webhook[0].arn}:url::" },
      { name = "LARK_SIGNING_SECRET", valueFrom = "${aws_secretsmanager_secret.sentry_lark_webhook[0].arn}:signing_secret::" },
    ]

    logConfiguration = {
      logDriver = "awslogs"
      options = {
        awslogs-group         = aws_cloudwatch_log_group.sentry[0].name
        awslogs-region        = var.aws_region
        awslogs-stream-prefix = "sentry-daily-report"
      }
    }
  }])
}

# ── EventBridge schedule ──────────────────────────────────────────

resource "aws_cloudwatch_event_rule" "sentry_daily_report" {
  count               = local.deploy_sentry ? 1 : 0
  name                = "sentry-daily-report"
  description         = "Sentry daily ops report — fires 01:00 UTC = 09:00 UTC+8"
  schedule_expression = "cron(0 1 * * ? *)"
}

resource "aws_cloudwatch_event_target" "sentry_daily_report" {
  count     = local.deploy_sentry ? 1 : 0
  rule      = aws_cloudwatch_event_rule.sentry_daily_report[0].name
  target_id = "1"
  arn       = aws_ecs_cluster.main.arn
  role_arn  = aws_iam_role.sentry_eventbridge_invoke[0].arn

  ecs_target {
    task_definition_arn = "arn:aws:ecs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:task-definition/nexus-sentry-daily-report"
    task_count          = 1
    launch_type         = "FARGATE"
    platform_version    = "LATEST"

    network_configuration {
      subnets          = aws_subnet.private[*].id
      security_groups  = [aws_security_group.ecs.id]
      assign_public_ip = false
    }
  }
}

# ──────────────────────────────────────────────────────────────────────
# Sentry Service — long-running 10-minute cycle
# ──────────────────────────────────────────────────────────────────────
#
# Additional resources needed by the `sentry` binary (not `sentry-probe`):
#
#   * S3 bucket for snapshot persistence + cold-start LRU hydration
#   * 2 new Secrets Manager entries: sentry/tidb and sentry/crm-token
#   * Expanded task role permissions (S3 + Secrets Manager)
#   * sentry-service task definition
#   * ECS Service (desired=1, long-running)
#
# Only deployed when local.deploy_sentry is true (prod workspace).

# ── S3 bucket: snapshot + alert archive ──────────────────────────

resource "aws_s3_bucket" "sentry_snapshots" {
  count         = local.deploy_sentry ? 1 : 0
  bucket        = "nexus-sentry-snapshots-${data.aws_caller_identity.current.account_id}"
  force_destroy = false
}

resource "aws_s3_bucket_versioning" "sentry_snapshots" {
  count  = local.deploy_sentry ? 1 : 0
  bucket = aws_s3_bucket.sentry_snapshots[0].id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "sentry_snapshots" {
  count  = local.deploy_sentry ? 1 : 0
  bucket = aws_s3_bucket.sentry_snapshots[0].id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "sentry_snapshots" {
  count                   = local.deploy_sentry ? 1 : 0
  bucket                  = aws_s3_bucket.sentry_snapshots[0].id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_lifecycle_configuration" "sentry_snapshots" {
  count  = local.deploy_sentry ? 1 : 0
  bucket = aws_s3_bucket.sentry_snapshots[0].id

  rule {
    id     = "snapshots-aging"
    status = "Enabled"

    filter {
      prefix = "snapshots/"
    }

    transition {
      days          = 90
      storage_class = "DEEP_ARCHIVE"
    }

    expiration {
      days = 365
    }
  }

  rule {
    id     = "alerts-aging"
    status = "Enabled"

    filter {
      prefix = "alerts/"
    }

    expiration {
      days = 365
    }
  }
}

# ── Additional secrets for sentry-service ────────────────────────
#
# sentry/service-targets : JSON array of {name, tidb, crm?} entries.
#                          sentry-service runs one fully-isolated cycle
#                          per target each tick (10 min). Each target has
#                          its own DB connection, its own LRU, its own
#                          suppressor, and its own S3 prefix. The `crm`
#                          field is optional — targets without one skip
#                          app-health scraping via the cycle's soft-fail
#                          path.

resource "aws_secretsmanager_secret" "sentry_service_targets" {
  count                   = local.deploy_sentry ? 1 : 0
  name                    = "sentry/service-targets"
  description             = "JSON array of TiDB + optional CRM targets for sentry-service multi-target mode"
  recovery_window_in_days = 0
}

# ── ECS Task Definition: sentry-service (10-min cycle) ───────────
#
# The service fetches secrets itself via the AWS SDK at boot, so the
# container `secrets:` field is NOT used. Only the plain-env SENTRY_*
# settings are injected. The task role supplies the permissions.

resource "aws_ecs_task_definition" "sentry_service" {
  count                    = local.deploy_sentry ? 1 : 0
  family                   = "nexus-sentry-service"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.sentry_execution[0].arn
  task_role_arn            = aws_iam_role.sentry_task[0].arn

  container_definitions = jsonencode([{
    name       = "sentry-service"
    image      = "${aws_ecr_repository.sentry[0].repository_url}:latest"
    essential  = true
    entryPoint = ["/usr/local/bin/sentry"]

    environment = [
      { name = "SENTRY_ENV",                   value = "prod" },
      { name = "SENTRY_REGION",                value = var.aws_region },
      { name = "SENTRY_S3_BUCKET",             value = aws_s3_bucket.sentry_snapshots[0].id },
      { name = "SENTRY_CYCLE_INTERVAL",        value = "10m" },
      { name = "SENTRY_QUERY_TIMEOUT",         value = "10s" },
      { name = "SENTRY_DB_WORKER_POOL",        value = "8" },
      { name = "SENTRY_LRU_CAP",               value = "300" },
      { name = "SENTRY_RESET_SCHEDULE",        value = "01:00,10:00" },
      { name = "SENTRY_RESET_WINDOW_MIN",      value = "20" },
      { name = "SENTRY_RESET_EPOCH_REFERENCE", value = "2026-01-01T00:00:00Z" },
      # Multi-target mode: sentry-service reads a JSON array of targets
      # and runs one cycle per target each tick. Per-target CRM URLs
      # live inside this secret too; targets without a `crm` block skip
      # app-health scraping via the cycle's soft-fail path.
      { name = "SENTRY_SECRET_TARGETS",        value = aws_secretsmanager_secret.sentry_service_targets[0].arn },
      { name = "SENTRY_SECRET_DASHSCOPE",      value = aws_secretsmanager_secret.sentry_dashscope[0].arn },
      { name = "SENTRY_SECRET_LARK",           value = aws_secretsmanager_secret.sentry_lark_webhook[0].arn },
    ]

    logConfiguration = {
      logDriver = "awslogs"
      options = {
        awslogs-group         = aws_cloudwatch_log_group.sentry[0].name
        awslogs-region        = var.aws_region
        awslogs-stream-prefix = "sentry-service"
      }
    }
  }])
}

# ── ECS Service: long-running singleton ──────────────────────────

resource "aws_ecs_service" "sentry_service" {
  count           = local.deploy_sentry ? 1 : 0
  name            = "sentry-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.sentry_service[0].arn
  desired_count   = 1
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = aws_subnet.private[*].id
    security_groups  = [aws_security_group.ecs.id]
    assign_public_ip = false
  }

  # Force redeploy when image/task def changes.
  force_new_deployment = true

  deployment_minimum_healthy_percent = 0
  deployment_maximum_percent         = 100

  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }
}

# Note: data.aws_caller_identity.current is already declared in iam.tf
# and reused here for ARN construction.
