################
# Providers
################

module "default_tags" {
  source             = "git@github.com:theorchard/terraform-default-tags.git//?ref=2.0.0"
  environment        = var.environment
  application_family = var.application_family
  service_name       = var.service_name
  team_name          = var.team_name
}

provider "aws" {
  region = var.region

  default_tags {
    tags = merge(
      module.default_tags.tags,
      var.instance != "" ? { ephemeral = "true", instance = var.instance } : {}
    )
  }
}

provider "aws" {
  region  = var.region
  alias   = "networking"
  profile = "networking"

  default_tags {
    tags = merge(
      module.default_tags.tags,
      var.instance != "" ? { ephemeral = "true", instance = var.instance } : {}
    )
  }
}

terraform {
  backend "s3" {
    bucket  = "orcd-terraform-state"
    key     = "qa/ows-coda/terraform.tfstate"
    region  = "us-east-1"
    encrypt = "true"
  }
}

################
# Data sources
################

data "aws_caller_identity" "current" {}

module "vpc_info" {
  source      = "git@github.com:theorchard/terraform-vpc-info.git//?ref=3.2.2"
  environment = var.environment
}

data "aws_acm_certificate" "theorchard_io" {
  domain   = "*.theorchard.io"
  statuses = ["ISSUED"]
}

data "aws_acm_certificate" "qaorch_com" {
  domain      = "*.qaorch.com"
  types       = ["AMAZON_ISSUED"]
  most_recent = true
}

# Auto-discover the main environment's shared resources (ephemeral only).
# Prerequisite: the default workspace must be applied first so these resources exist.
# Data sources resolve by naming convention ({env}-{service_name}). If a name
# doesn't match, terraform plan will fail with "resource not found" — update the
# identifier in the relevant data source block to match.

data "aws_rds_cluster" "main" {
  count              = local.is_ephemeral ? 1 : 0
  cluster_identifier = "${var.environment}-${var.service_name}"
}

data "aws_elasticache_replication_group" "main" {
  count                = local.is_ephemeral ? 1 : 0
  replication_group_id = "${var.environment}-${var.service_name}-chatbot"
}

data "aws_s3_bucket" "main_uploads" {
  count  = local.is_ephemeral ? 1 : 0
  bucket = "${var.environment}-${var.service_name}-uploads"
}

# Redis SG discovery — matches the terraform-elasticache module's naming convention.
# If the module changes its SG naming, update the pattern here.
data "aws_security_groups" "main_redis" {
  count = local.is_ephemeral ? 1 : 0

  filter {
    name   = "group-name"
    values = ["${var.environment}-${var.service_name}-chatbot-elasticache-security-group"]
  }

  filter {
    name   = "vpc-id"
    values = [module.vpc_info.vpc_id]
  }
}

check "ephemeral_workspace_match" {
  assert {
    condition     = !local.is_ephemeral || terraform.workspace == var.instance
    error_message = "Workspace name must match var.instance for ephemeral environments. Current workspace: '${terraform.workspace}', var.instance: '${var.instance}'."
  }
}

check "rds_sg_discovered" {
  assert {
    condition     = !local.is_ephemeral || length(local.shared_rds_sg_ids) > 0
    error_message = "RDS cluster has no security groups attached. Ephemeral Fargate task will not reach RDS."
  }
}

check "redis_sg_discovered" {
  assert {
    condition     = !local.is_ephemeral || length(local.shared_redis_sg_ids) > 0
    error_message = "Could not discover Redis security group. Ephemeral Fargate task may not reach Redis. Verify the SG name pattern in data.aws_security_groups.main_redis matches the terraform-elasticache module's convention."
  }
}

################
# Locals
################

locals {
  is_ephemeral  = var.instance != ""
  service_label = local.is_ephemeral ? "${var.service_name}-${var.instance}" : var.service_name
  ecr_image     = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.service_name}:latest"

  # Search service name — used across search.tf and ecs_ec2.tf
  search_service_name = "ows-coda-search"

  # Runner service name and port — used across runner.tf
  runner_service_name = "ows-coda-runner"
  runner_port         = 8082

  # Platform service name and port — used across platform.tf
  platform_service_name = "ows-coda-platform"
  platform_port         = 8082

  # Langfuse service names — must match ECR repo names
  langfuse_service_name        = "ows-coda-langfuse"
  langfuse_web_service_name    = "ows-coda-langfuse-web"
  langfuse_worker_service_name = "ows-coda-langfuse-worker"
  langfuse_web_port            = 3000
  langfuse_worker_port         = 3030
  create_langfuse              = var.langfuse_enabled && !local.is_ephemeral

  # Shared constants — extracted to avoid duplication across files
  graphql_gateway_url                       = "https://${var.environment}-graphql-router.theorchard.io/graphql"
  elb_logs_bucket                           = "orch-elb-logs"
  datadog_notification_endpoints            = "@slack-monitoring @slack-coda-monitoring"
  datadog_escalation_notification_endpoints = "@slack-coda-monitoring"

  rds_host   = local.is_ephemeral ? data.aws_rds_cluster.main[0].endpoint : module.ows_coda_rds[0].rds_cluster_endpoint
  redis_host = local.is_ephemeral ? data.aws_elasticache_replication_group.main[0].primary_endpoint_address : module.ows_coda_chatbot_cache[0].redis_primary_endpoint_address
  s3_bucket  = local.is_ephemeral ? data.aws_s3_bucket.main_uploads[0].id : module.s3_ows_coda_uploads[0].s3_bucket_name_output
  s3_arn     = local.is_ephemeral ? data.aws_s3_bucket.main_uploads[0].arn : module.s3_ows_coda_uploads[0].s3_bucket_arn_output
  db_name    = local.is_ephemeral ? "coda_${replace(var.instance, "-", "_")}" : "coda"

  # Security group IDs for shared datastore access
  shared_rds_sg_ids   = local.is_ephemeral ? data.aws_rds_cluster.main[0].vpc_security_group_ids : []
  shared_redis_sg_ids = local.is_ephemeral ? data.aws_security_groups.main_redis[0].ids : []
}

################
# Fargate
################

module "ows_coda_fargate_environment" {
  source = "git@github.com:theorchard/terraform-fargate.git//?ref=6.5.0"

  providers = {
    aws.dns = aws.networking
  }

  environment  = var.environment
  service_name = local.service_label
  aws_region   = var.region

  application_family = var.application_family
  commit_sha         = "latest"
  container_port     = "8080"
  task_type          = "web_service"
  # Ephemeral: 1 task, no HA — acceptable for PR previews where brief downtime
  # during deploys is tolerable. Main environment uses 2 tasks for HA.
  desired_task_count                       = local.is_ephemeral ? 1 : 2
  task_cpu                                 = local.is_ephemeral ? 512 : 4096
  task_memory                              = local.is_ephemeral ? 1024 : 8192
  maximum_capacity                         = local.is_ephemeral ? 2 : 4
  minimum_capacity                         = local.is_ephemeral ? 1 : 2
  load_balancer_idle_timeout               = 300
  load_balancer_access_logs_s3_bucket_name = local.elb_logs_bucket
  vpc_id                                   = module.vpc_info.vpc_id
  https_listener_certificate_id            = split("/", data.aws_acm_certificate.qaorch_com.arn)[1]
  http_listener_enabled                    = true # HTTP→HTTPS redirect only; the module does not serve plaintext traffic
  blocking_waf_enabled                     = true
  health_check_path                        = "/health"

  # Ephemeral instances reuse the main ows-coda ECR image; empty string = use default ECR.
  non_ecr_image = local.is_ephemeral ? local.ecr_image : ""

  # Ephemeral instances share Secrets Manager entries with the main environment.
  # For the main env this is a no-op (same value the module would derive).
  secrets_manager_service_name = var.service_name

  fargate_service_subnets = module.vpc_info.default_private_subnet_ids
  load_balancer_subnets   = module.vpc_info.default_private_subnet_ids

  # Langfuse secrets live under ows-coda-langfuse/*, not ows-coda/* — grant
  # the execution role access so ECS can inject them as env vars at startup.
  execution_role_iam_managed_policy_attachments = local.create_langfuse ? [
    aws_iam_policy.langfuse_secrets_access[0].arn,
  ] : []

  secrets = concat(
    [
      {
        CODA_DB_PASS = "${var.environment}/${var.service_name}/CODA_DB_PASS"
      },
      {
        CODA_DB_IDENTITY_HMAC_SECRET = "${var.environment}/${var.service_name}/CODA_DB_IDENTITY_HMAC_SECRET"
      },
      {
        CODA_DB_IDENTITY_AES_KEY = "${var.environment}/${var.service_name}/CODA_DB_IDENTITY_AES_KEY"
      },
      {
        SNOWFLAKE_READER_PRIVATE_KEY = "${var.environment}/${var.service_name}/SNOWFLAKE_PRIVATE_KEY"
      },
      {
        SNOWFLAKE_READER_KEY_PASS = "${var.environment}/${var.service_name}/SNOWFLAKE_PRIVATE_KEY_PASS"
      },
    ],
    local.create_langfuse ? [
      {
        LANGFUSE_PUBLIC_KEY = "${var.environment}/${local.langfuse_service_name}/LANGFUSE_PUBLIC_KEY"
      },
      {
        LANGFUSE_SECRET_KEY = "${var.environment}/${local.langfuse_service_name}/LANGFUSE_SECRET_KEY"
      },
    ] : [],
  )

  environment_variables = [
    {
      AUTH0_AUDIENCE = var.auth0_audience
    },
    {
      AUTH0_CLIENT_ID = var.auth0_client_id
    },
    {
      AUTH0_DOMAIN = var.auth0_domain
    },
    {
      BEDROCK_REGION = var.region
    },
    {
      CODA_DB_DATABASE = local.db_name
    },
    {
      CODA_DB_HOST = local.rds_host
    },
    {
      CODA_DB_PORT = "3306"
    },
    {
      CODA_DB_USER = "coda_svc"
    },
    {
      # Agentic workflows (LangGraph) — contract-creation pilot (ows-coda#255).
      # Mounts /api/v1/workflows; off by default in the app, enabled for QA.
      CODA_WORKFLOWS_ENABLED = "true"
    },
    {
      # false = confirm performs real QA writes (the app's requireQA gate still
      # applies). Set true to preview exact mutation payloads without writing.
      CODA_WORKFLOWS_DRY_RUN = "false"
    },
    {
      DD_LOGS_INJECTION = true
    },
    {
      ENVIRONMENT = var.environment
    },
    {
      GRAPHQL_GATEWAY_URL = local.graphql_gateway_url
    },
    {
      NODE_ENV = var.environment
    },
    {
      OWS_ABACUS_ACCOUNT_URL = "https://qa-ows-abacus-account.theorchard.io/"
    },
    {
      OWS_MONEYHUB_URL = "https://qa-ows-moneyhub.theorchard.io/"
    },
    {
      OWS_PRODUCT_URL = "https://qa-ows-product.theorchard.io/"
    },
    {
      OWS_ROYALTIES_URL = "https://qa-ows-royalties.theorchard.io/"
    },
    {
      OWS_LEDGER_URL = "https://qa-ows-ledger.theorchard.io/"
    },
    {
      # rediss:// (double s) enables TLS. Requires cache_transit_encryption_enabled on the cluster.
      # Specify :6379 to ensure correct port. Some clients default to it, but explicit is safer.
      REDIS_URL = "rediss://${local.redis_host}:6379"
    },
    {
      S3_UPLOADS_BUCKET = local.s3_bucket
    },
    {
      S3_UPLOADS_REGION = var.region
    },
    {
      SENTRY_DSN = try(module.sentry_ows_coda[0].sentry_key_dsn_public_output, "")
    },
    {
      FRONTEND_MONEYHUB_URL = "https://moneyhub.qaorch.com"
    },
    {
      FRONTEND_CONTENT_URL = "https://content.qaorch.com"
    },
    {
      FRONTEND_ROYALTIES_URL = "https://abacus.qaorch.com"
    },
    {
      S3_ADJUSTMENTS_BUCKET = "qa-abacus-adjustments"
    },
    {
      LAMBDA_ADJUSTMENTS_JSON_VALIDATION = "qa-lambda-abacus-adjustments-json-validation"
    },
    {
      LAMBDA_ADJUSTMENTS_JSON_IMPORT = "qa-lambda-abacus-adjustments-json-import"
    },
    {
      # Empty string disables the search client; server falls back to keyword search.
      # Ephemeral instances skip the search service (cold-start cost + Snowflake credentials).
      SEARCH_URL = local.is_ephemeral ? "" : "https://${var.environment}-${local.search_service_name}.theorchard.io"
    },
    {
      RUNNER_URL = local.is_ephemeral ? "" : "https://${var.environment}-${local.runner_service_name}.theorchard.io"
    },
    {
      SNOWFLAKE_READER_ACCOUNT = var.snowflake_account
    },
    {
      SNOWFLAKE_READER_DATABASE = var.snowflake_database
    },
    {
      SNOWFLAKE_READER_ROLE = var.snowflake_role
    },
    {
      SNOWFLAKE_READER_USER = var.snowflake_username
    },
    {
      SNOWFLAKE_READER_WAREHOUSE = var.snowflake_warehouse
    },
    {
      LANGFUSE_HOST = local.create_langfuse ? "https://${var.environment}-${local.langfuse_web_service_name}.theorchard.io" : ""
    },
  ]
}

resource "aws_lb_listener_certificate" "theorchard_io_lb_listener_certificate" {
  listener_arn    = module.ows_coda_fargate_environment.fargate_load_balancer_https_listener_arn
  certificate_arn = data.aws_acm_certificate.theorchard_io.arn
}

################
# Secrets
################

# Secrets Manager entries. Some are injected into the Fargate container via the `secrets` block
# above; others are for Jenkins build steps or ephemeral env setup only.
# CODA_DB_PASS, CODA_DB_IDENTITY_*: Fargate secrets injection (runtime).
# CODA_DB_EPHEMERAL_PASS: Jenkins DB setup for ephemeral environments.
# SENTRY_AUTH_TOKEN: Jenkins Vite sourcemap upload only (not a Fargate secret).
module "ows_coda_secrets" {
  source = "git@github.com:theorchard/terraform-secrets-manager.git//?ref=1.6.1"
  for_each = local.is_ephemeral ? toset([]) : toset([
    "CODA_DB_EPHEMERAL_PASS",
    "CODA_DB_PASS",
    "CODA_DB_IDENTITY_AES_KEY",
    "CODA_DB_IDENTITY_HMAC_SECRET",
    "SENTRY_AUTH_TOKEN",
  ])

  application_family = var.application_family
  environment        = var.environment
  service_name       = var.service_name
  secret_name        = each.value
}

# SENTRY_DSN — managed directly so the value auto-syncs from the Sentry project on every apply.
# Used at build time (baked into the Vite client bundle by Jenkins) and at runtime (Fargate plain env var).
resource "aws_secretsmanager_secret" "sentry_dsn" {
  count       = local.is_ephemeral ? 0 : 1
  name        = "${var.environment}/${var.service_name}/SENTRY_DSN"
  description = "Sentry DSN for ows-coda (auto-synced from Sentry module)"
}

resource "aws_secretsmanager_secret_version" "sentry_dsn" {
  count         = local.is_ephemeral ? 0 : 1
  secret_id     = aws_secretsmanager_secret.sentry_dsn[0].id
  secret_string = module.sentry_ows_coda[0].sentry_key_dsn_public_output
}

################
# Sentry
################

module "sentry_ows_coda" {
  count  = local.is_ephemeral ? 0 : 1
  source = "git@github.com:theorchard/terraform-sentry.git//?ref=5.1.0"

  environment        = var.environment
  platform           = "node"
  service_name       = var.service_name
  teams              = ["coda"]
  application_family = var.application_family
}

################
# Datadog
################

module "ows_coda_service_dashboard" {
  count                             = local.is_ephemeral ? 0 : 1
  source                            = "git@github.com:theorchard/terraform-datadog.git//modules/service?ref=6.19.0"
  environment                       = var.environment
  environment_type                  = "fargate"
  service_name                      = var.service_name
  application_family                = var.application_family
  teams                             = ["coda"]
  notification_endpoints            = local.datadog_notification_endpoints
  escalation_notification_endpoints = local.datadog_escalation_notification_endpoints
}

################
# Security group rules — ephemeral Fargate → shared datastores
################

resource "aws_security_group_rule" "ephemeral_to_rds" {
  for_each = toset(local.shared_rds_sg_ids)

  type                     = "ingress"
  from_port                = 3306
  to_port                  = 3306
  protocol                 = "tcp"
  source_security_group_id = module.ows_coda_fargate_environment.fargate_security_group_id
  security_group_id        = each.value
  description              = "Ephemeral ${local.service_label} -> shared RDS"
}

resource "aws_security_group_rule" "ephemeral_to_redis" {
  for_each = local.is_ephemeral ? toset(local.shared_redis_sg_ids) : toset([])

  type                     = "ingress"
  from_port                = 6379
  to_port                  = 6379
  protocol                 = "tcp"
  source_security_group_id = module.ows_coda_fargate_environment.fargate_security_group_id
  security_group_id        = each.value
  description              = "Ephemeral ${local.service_label} -> shared Redis"
}
