################
# IAM — Bedrock
################

# Inference-only access to Claude models (same policy as graphql-abacus).
# Cross-Region inference profile routes across US regions for availability.
data "aws_iam_policy_document" "ows_coda_bedrock_policy_document" {
  statement {
    sid    = "AllowBedrockModelInvoke"
    effect = "Allow"

    actions = [
      "bedrock:InvokeModel",
      "bedrock:InvokeModelWithResponseStream",
    ]

    resources = [
      "arn:aws:bedrock:us-*::foundation-model/anthropic.claude-opus-4-6-*",
      "arn:aws:bedrock:us-*:${data.aws_caller_identity.current.account_id}:inference-profile/us.anthropic.claude-opus-4-6-*",
      "arn:aws:bedrock:us-*::foundation-model/anthropic.claude-sonnet-4-6*",
      "arn:aws:bedrock:us-*:${data.aws_caller_identity.current.account_id}:inference-profile/us.anthropic.claude-sonnet-4-6*",
      "arn:aws:bedrock:us-*::foundation-model/anthropic.claude-haiku-4-5-*",
      "arn:aws:bedrock:us-*:${data.aws_caller_identity.current.account_id}:inference-profile/us.anthropic.claude-haiku-4-5-*"
    ]
  }
}

# Each ephemeral instance needs its own IAM policy (names must be unique per account)
# and its own Fargate task role attachment. These resources are always created, not
# gated by count, because every Fargate task needs Bedrock and S3 access.
resource "aws_iam_policy" "ows_coda_bedrock_policy" {
  name        = "${var.environment}-${local.service_label}-bedrock-policy"
  description = local.is_ephemeral ? "Ephemeral (${var.instance}): Bedrock access for ows-coda" : "Allow ows-coda to invoke Bedrock models for the Coda chatbot"
  policy      = data.aws_iam_policy_document.ows_coda_bedrock_policy_document.json
}

resource "aws_iam_role_policy_attachment" "ows_coda_bedrock_policy_attachment" {
  role       = module.ows_coda_fargate_environment.fargate_task_iam_role_name
  policy_arn = aws_iam_policy.ows_coda_bedrock_policy.arn
}

############################
# IAM — Jenkins CI
############################

# Jenkins runs in the prod account (437795906767) and assumes into QA
# to read build secrets (e.g. Sentry auth token, DSN) during suiteAppBuild.
data "aws_iam_policy_document" "jenkins_assume_role_policy" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      type = "AWS"
      identifiers = [
        "arn:aws:iam::437795906767:role/prod-jenkins-aws-pipeline-agent",
      ]
    }
  }
}

resource "aws_iam_role" "jenkins_secrets_role" {
  count = local.is_ephemeral ? 0 : 1

  name               = "${var.environment}-${var.service_name}-jenkins-secrets-role"
  description        = "Allows Jenkins CI from prod account to read ${var.service_name} secrets for builds"
  assume_role_policy = data.aws_iam_policy_document.jenkins_assume_role_policy.json

  tags = {
    environment        = var.environment
    application_family = var.application_family
    service_name       = var.service_name
  }
}

data "aws_iam_policy_document" "jenkins_secrets_read" {
  statement {
    effect = "Allow"
    actions = [
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret"
    ]
    resources = [
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${var.service_name}/*",
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.search_service_name}/*",
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.runner_service_name}/*",
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.platform_service_name}/*",
    ]
  }
}

resource "aws_iam_role_policy" "jenkins_secrets_read" {
  count = local.is_ephemeral ? 0 : 1

  name   = "${var.environment}-${var.service_name}-jenkins-secrets-read"
  role   = aws_iam_role.jenkins_secrets_role[0].name
  policy = data.aws_iam_policy_document.jenkins_secrets_read.json
}

############################
# IAM — RDS master password rotation (self-service)
############################

# Scoped to the Coda RDS cluster. Allows changing the master password
# without a Terraform PR via:
#   aws rds modify-db-cluster \
#     --db-cluster-identifier qa-ows-coda \
#     --master-user-password '<new-password>' \
#     --apply-immediately
data "aws_iam_policy_document" "rds_master_password_rotation" {
  count = local.is_ephemeral ? 0 : 1

  statement {
    sid    = "AllowDescribeCluster"
    effect = "Allow"

    actions = [
      "rds:DescribeDBClusters",
    ]

    resources = [
      "arn:aws:rds:${var.region}:${data.aws_caller_identity.current.account_id}:cluster:${var.environment}-${var.service_name}",
      "arn:aws:rds:${var.region}:${data.aws_caller_identity.current.account_id}:cluster:${var.environment}-${local.langfuse_service_name}",
    ]
  }

  statement {
    sid    = "AllowModifyMasterPassword"
    effect = "Allow"

    actions = [
      "rds:ModifyDBCluster",
    ]

    resources = [
      "arn:aws:rds:${var.region}:${data.aws_caller_identity.current.account_id}:cluster:${var.environment}-${var.service_name}",
      "arn:aws:rds:${var.region}:${data.aws_caller_identity.current.account_id}:cluster:${var.environment}-${local.langfuse_service_name}",
    ]
  }
}

resource "aws_iam_policy" "rds_master_password_rotation" {
  count = local.is_ephemeral ? 0 : 1

  name        = "${var.environment}-${var.service_name}-rds-master-password-rotation"
  description = "Allow modifying the RDS master password for ${var.environment}-${var.service_name}"
  policy      = data.aws_iam_policy_document.rds_master_password_rotation[0].json
}

data "aws_iam_group" "engineering" {
  count      = local.is_ephemeral ? 0 : 1
  group_name = "generic-orchard-prod-engineering-group"
}

resource "aws_iam_group_policy_attachment" "engineering_rds_password_rotation" {
  count = local.is_ephemeral ? 0 : 1

  group      = data.aws_iam_group.engineering[0].group_name
  policy_arn = aws_iam_policy.rds_master_password_rotation[0].arn
}

############################
# IAM — ECS + ELB deploy (engineering self-service)
############################

# Allows engineers to force new deployments after updating secrets
# or config, and to manage ALB target groups / listeners for protocol
# version changes that force TG replacement.

data "aws_iam_policy_document" "engineering_ecs_deploy" {
  count = local.is_ephemeral ? 0 : 1

  statement {
    sid    = "AllowUpdateService"
    effect = "Allow"

    actions = [
      "ecs:UpdateService",
      "ecs:DescribeServices",
    ]

    resources = [
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${var.environment}-${var.service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${var.environment}-${local.search_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${var.environment}-${local.runner_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${var.environment}-${local.platform_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${var.environment}-${local.langfuse_web_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:service/${var.environment}-${local.langfuse_worker_service_name}/*",
    ]
  }

  statement {
    sid    = "AllowManageTasks"
    effect = "Allow"

    actions = [
      "ecs:StopTask",
      "ecs:DescribeTasks",
      "ecs:ListTasks",
    ]

    resources = [
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task/${var.environment}-${var.service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task/${var.environment}-${local.search_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task/${var.environment}-${local.runner_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task/${var.environment}-${local.platform_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task/${var.environment}-${local.langfuse_web_service_name}/*",
      "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task/${var.environment}-${local.langfuse_worker_service_name}/*",
    ]
  }

  statement {
    sid    = "AllowPassRole"
    effect = "Allow"

    actions = [
      "iam:PassRole",
    ]

    resources = [
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.environment}-${var.service_name}-*",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.environment}-${local.search_service_name}-*",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.environment}-${local.runner_service_name}-*",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.environment}-${local.platform_service_name}-*",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.environment}-${local.langfuse_web_service_name}-*",
      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.environment}-${local.langfuse_worker_service_name}-*",
    ]

    condition {
      test     = "StringEquals"
      variable = "iam:PassedToService"
      values   = ["ecs-tasks.amazonaws.com"]
    }
  }

  # ELB Describe actions require wildcard resource (AWS limitation)
  statement {
    sid    = "AllowDescribeELB"
    effect = "Allow"

    actions = [
      "elasticloadbalancing:DescribeLoadBalancers",
      "elasticloadbalancing:DescribeTargetGroups",
      "elasticloadbalancing:DescribeListeners",
    ]

    resources = ["*"]
  }

  # ELB modify/delete scoped to coda ALB resources
  statement {
    sid    = "AllowManageELB"
    effect = "Allow"

    actions = [
      "elasticloadbalancing:ModifyListener",
      "elasticloadbalancing:DeleteTargetGroup",
    ]

    resources = [
      "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:targetgroup/${var.environment}-${var.service_name}*/*",
      "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:listener/app/${var.environment}-${var.service_name}*/*/*",
      "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:targetgroup/${var.environment}-${local.platform_service_name}*/*",
      "arn:aws:elasticloadbalancing:${var.region}:${data.aws_caller_identity.current.account_id}:listener/app/${var.environment}-${local.platform_service_name}*/*/*",
    ]
  }
}

resource "aws_iam_policy" "engineering_ecs_deploy" {
  count = local.is_ephemeral ? 0 : 1

  name        = "${var.environment}-${var.service_name}-engineering-ecs-deploy"
  description = "Allow engineers to manage ECS deployments and ELB resources for ows-coda services in ${var.environment}"
  policy      = data.aws_iam_policy_document.engineering_ecs_deploy[0].json
}

resource "aws_iam_group_policy_attachment" "engineering_ecs_deploy" {
  count = local.is_ephemeral ? 0 : 1

  group      = data.aws_iam_group.engineering[0].group_name
  policy_arn = aws_iam_policy.engineering_ecs_deploy[0].arn
}

############################
# IAM — Secrets Manager (engineering self-service)
############################

# Edit access (PutSecretValue, UpdateSecret) is required so engineers can
# rotate and update application secrets without a Terraform PR.

data "aws_iam_policy_document" "engineering_secrets_access" {
  count = local.is_ephemeral ? 0 : 1

  statement {
    effect = "Allow"
    actions = [
      "secretsmanager:GetSecretValue",
      "secretsmanager:PutSecretValue",
      "secretsmanager:UpdateSecret",
      "secretsmanager:DescribeSecret",
    ]
    resources = [
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${var.service_name}/*",
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.search_service_name}/*",
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.runner_service_name}/*",
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.platform_service_name}/*",
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.langfuse_service_name}/*",
    ]
  }
}

resource "aws_iam_policy" "engineering_secrets_access" {
  count = local.is_ephemeral ? 0 : 1

  name        = "${var.environment}-${var.service_name}-engineering-secrets-access"
  description = "Allow engineers to manage all ows-coda secrets in ${var.environment}"
  policy      = data.aws_iam_policy_document.engineering_secrets_access[0].json
}

resource "aws_iam_group_policy_attachment" "engineering_secrets_access" {
  count = local.is_ephemeral ? 0 : 1

  group      = data.aws_iam_group.engineering[0].group_name
  policy_arn = aws_iam_policy.engineering_secrets_access[0].arn
}

# ---------------------------------------------------------------------------
# Langfuse S3 access (Web + Worker need to read/write event data)
# ---------------------------------------------------------------------------

data "aws_iam_policy_document" "langfuse_s3_access" {
  count = local.create_langfuse ? 1 : 0

  statement {
    effect    = "Allow"
    actions   = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"]
    resources = ["${module.s3_langfuse[0].s3_bucket_arn_output}/*"]
  }

  statement {
    effect    = "Allow"
    actions   = ["s3:ListBucket"]
    resources = [module.s3_langfuse[0].s3_bucket_arn_output]
  }

  statement {
    effect    = "Allow"
    actions   = ["kms:GenerateDataKey", "kms:Decrypt"]
    resources = [data.aws_kms_alias.s3.target_key_arn]
  }
}

resource "aws_iam_policy" "langfuse_s3_access" {
  count = local.create_langfuse ? 1 : 0

  name   = "${var.environment}-${local.langfuse_service_name}-s3-access"
  policy = data.aws_iam_policy_document.langfuse_s3_access[0].json
}

resource "aws_iam_role_policy_attachment" "langfuse_web_s3" {
  count = local.create_langfuse ? 1 : 0

  role       = module.langfuse_web_fargate[0].fargate_task_iam_role_name
  policy_arn = aws_iam_policy.langfuse_s3_access[0].arn
}

resource "aws_iam_role_policy_attachment" "langfuse_worker_s3" {
  count = local.create_langfuse ? 1 : 0

  role       = module.langfuse_worker_fargate[0].fargate_task_iam_role_name
  policy_arn = aws_iam_policy.langfuse_s3_access[0].arn
}

# ---------------------------------------------------------------------------
# Langfuse secrets access (task execution roles)
#
# The terraform-fargate module scopes its built-in secrets policy to
# ${environment}/${service_name}/*. Langfuse secrets live under
# ${environment}/${langfuse_service_name}/* (ows-coda-langfuse) which
# doesn't match any execution role's service name (ows-coda,
# ows-coda-langfuse-web, ows-coda-langfuse-worker), so each module
# needs this policy via execution_role_iam_managed_policy_attachments.
# ---------------------------------------------------------------------------

data "aws_iam_policy_document" "langfuse_secrets_access" {
  count = local.create_langfuse ? 1 : 0

  statement {
    effect  = "Allow"
    actions = ["secretsmanager:GetSecretValue"]
    resources = [
      "arn:aws:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${var.environment}/${local.langfuse_service_name}/*",
    ]
  }

  statement {
    effect    = "Allow"
    actions   = ["kms:Decrypt"]
    resources = [aws_kms_key.langfuse_secrets[0].arn]
  }
}

resource "aws_iam_policy" "langfuse_secrets_access" {
  count = local.create_langfuse ? 1 : 0

  name        = "${var.environment}-${local.langfuse_service_name}-execution-secrets-access"
  description = "Allow Fargate execution roles to read Langfuse secrets at task startup"
  policy      = data.aws_iam_policy_document.langfuse_secrets_access[0].json
}
