################################################################################
# Module: agentidp
# Main — ECS Fargate (AWS) or Cloud Run (GCP)
#
# Deploys the sentryagent/agentidp container.
# All sensitive environment variables are injected from AWS Secrets Manager
# (AWS path) or GCP Secret Manager (GCP path) — no plaintext secrets here.
################################################################################

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.40.0"
    }
    google = {
      source  = "hashicorp/google"
      version = ">= 5.20.0"
    }
  }
}

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

locals {
  common_tags = {
    environment = var.environment
    project     = var.project
    managed_by  = "terraform"
  }

  # Build the list of Vault-related env vars conditionally.
  # If vault_addr is empty we omit all Vault env vars entirely.
  aws_vault_env_plain = var.aws_vault_addr != "" ? [
    {
      name  = "VAULT_ADDR"
      value = var.aws_vault_addr
    },
    {
      name  = "VAULT_MOUNT"
      value = var.aws_vault_mount
    }
  ] : []

  aws_vault_secret_env = var.aws_secret_vault_token_arn != "" ? [
    {
      name      = "VAULT_TOKEN"
      valueFrom = var.aws_secret_vault_token_arn
    }
  ] : []

  gcp_vault_env_plain = var.gcp_vault_addr != "" ? {
    VAULT_ADDR  = var.gcp_vault_addr
    VAULT_MOUNT = var.gcp_vault_mount
  } : {}
}

################################################################################
# ── AWS PATH ──────────────────────────────────────────────────────────────────
################################################################################

# Security group: allow inbound traffic only from the ALB on app_port,
# allow all outbound (needed for Secrets Manager and ECR API calls over HTTPS).
resource "aws_security_group" "app" {
  count = var.provider_type == "aws" ? 1 : 0

  name        = "${var.project}-${var.environment}-app-sg"
  description = "Security group for AgentIdP ECS tasks — inbound from ALB only"
  vpc_id      = var.aws_vpc_id

  ingress {
    description = "App port from ALB"
    from_port   = var.app_port
    to_port     = var.app_port
    protocol    = "tcp"
    # The ALB security group ID is not directly available here; in the root
    # environment module the ALB SG and this SG are cross-referenced.
    # The environment module passes the ALB SG id via aws_lb_security_group_id
    # below using a separate ingress rule resource to avoid circular dependency.
    cidr_blocks = []
    self        = false
  }

  egress {
    description = "All outbound"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(local.common_tags, {
    Name = "${var.project}-${var.environment}-app-sg"
  })
}

# ECS Cluster
resource "aws_ecs_cluster" "main" {
  count = var.provider_type == "aws" ? 1 : 0

  name = "${var.project}-${var.environment}"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }

  tags = local.common_tags
}

# ECS Cluster Capacity Providers — use FARGATE and FARGATE_SPOT
resource "aws_ecs_cluster_capacity_providers" "main" {
  count = var.provider_type == "aws" ? 1 : 0

  cluster_name       = aws_ecs_cluster.main[0].name
  capacity_providers = ["FARGATE", "FARGATE_SPOT"]

  default_capacity_provider_strategy {
    capacity_provider = "FARGATE"
    weight            = 1
    base              = 1
  }
}

# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "app" {
  count = var.provider_type == "aws" ? 1 : 0

  name              = var.aws_log_group_name
  retention_in_days = 30

  tags = local.common_tags
}

# ECS Task Definition
resource "aws_ecs_task_definition" "app" {
  count = var.provider_type == "aws" ? 1 : 0

  family                   = "${var.project}-${var.environment}"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = tostring(var.aws_cpu)
  memory                   = tostring(var.aws_memory)
  execution_role_arn       = var.aws_execution_role_arn
  task_role_arn            = var.aws_task_role_arn

  container_definitions = jsonencode([
    {
      name      = "agentidp"
      image     = var.app_image
      essential = true

      portMappings = [
        {
          containerPort = var.app_port
          protocol      = "tcp"
        }
      ]

      # Plain (non-sensitive) environment variables
      environment = concat(
        [
          { name = "PORT", value = tostring(var.app_port) },
          { name = "NODE_ENV", value = "production" },
          { name = "CORS_ORIGIN", value = var.aws_cors_origin },
          { name = "POLICY_DIR", value = var.aws_policy_dir }
        ],
        local.aws_vault_env_plain
      )

      # Sensitive values fetched from Secrets Manager at task launch.
      # Each entry is injected as the named environment variable.
      secrets = concat(
        [
          {
            name      = "DATABASE_URL"
            valueFrom = var.aws_secret_database_url_arn
          },
          {
            name      = "REDIS_URL"
            valueFrom = var.aws_secret_redis_url_arn
          },
          {
            name      = "JWT_PRIVATE_KEY"
            valueFrom = var.aws_secret_jwt_private_key_arn
          },
          {
            name      = "JWT_PUBLIC_KEY"
            valueFrom = var.aws_secret_jwt_public_key_arn
          }
        ],
        local.aws_vault_secret_env
      )

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = var.aws_log_group_name
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "agentidp"
        }
      }

      healthCheck = {
        command     = ["CMD-SHELL", "wget -qO- http://localhost:${var.app_port}/health || exit 1"]
        interval    = 30
        timeout     = 5
        retries     = 3
        startPeriod = 60
      }

      readonlyRootFilesystem = false
      user                   = "node"
    }
  ])

  tags = local.common_tags
}

# ECS Service
resource "aws_ecs_service" "app" {
  count = var.provider_type == "aws" ? 1 : 0

  name            = "${var.project}-${var.environment}"
  cluster         = aws_ecs_cluster.main[0].id
  task_definition = aws_ecs_task_definition.app[0].arn
  desired_count   = var.aws_desired_count
  launch_type     = "FARGATE"

  # Rolling update: keep at least 100% healthy tasks during deployment
  deployment_minimum_healthy_percent = 100
  deployment_maximum_percent         = 200

  network_configuration {
    subnets          = var.aws_subnet_ids
    security_groups  = [aws_security_group.app[0].id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = var.aws_target_group_arn
    container_name   = "agentidp"
    container_port   = var.app_port
  }

  # Ignore task_definition changes driven by image tag updates — deployments
  # are managed externally (CI/CD pipeline updates the image tag).
  lifecycle {
    ignore_changes = [task_definition, desired_count]
  }

  tags = local.common_tags

  depends_on = [aws_ecs_cluster_capacity_providers.main]
}

################################################################################
# ── GCP PATH ──────────────────────────────────────────────────────────────────
################################################################################

# Cloud Run Service
resource "google_cloud_run_v2_service" "app" {
  count = var.provider_type == "gcp" ? 1 : 0

  name     = "${var.project}-${var.environment}"
  location = var.gcp_region
  project  = var.gcp_project_id

  # Ingress: allow only requests from the load balancer / public internet.
  # Cloud Run provides Google-managed TLS on the default *.run.app domain
  # and on any custom domains mapped via Cloud Run domain mappings.
  ingress = "INGRESS_TRAFFIC_ALL"

  template {
    service_account = var.gcp_service_account_email

    scaling {
      min_instance_count = var.gcp_min_instances
      max_instance_count = var.gcp_max_instances
    }

    # VPC access — route outbound traffic through the VPC connector so the
    # container can reach Cloud SQL (private IP) and Memorystore.
    vpc_access {
      connector = var.gcp_vpc_connector_name
      egress    = "PRIVATE_RANGES_ONLY"
    }

    containers {
      image = var.app_image

      ports {
        container_port = var.app_port
      }

      resources {
        limits = {
          cpu    = var.gcp_cpu
          memory = var.gcp_memory
        }
        cpu_idle          = false
        startup_cpu_boost = true
      }

      # Plain environment variables
      dynamic "env" {
        for_each = merge(
          {
            PORT       = tostring(var.app_port)
            NODE_ENV   = "production"
            CORS_ORIGIN = var.gcp_cors_origin
            POLICY_DIR = var.gcp_policy_dir
          },
          local.gcp_vault_env_plain
        )
        content {
          name  = env.key
          value = env.value
        }
      }

      # DATABASE_URL from Secret Manager
      env {
        name = "DATABASE_URL"
        value_source {
          secret_key_ref {
            secret  = var.gcp_secret_database_url_id
            version = "latest"
          }
        }
      }

      # REDIS_URL from Secret Manager
      env {
        name = "REDIS_URL"
        value_source {
          secret_key_ref {
            secret  = var.gcp_secret_redis_url_id
            version = "latest"
          }
        }
      }

      # JWT_PRIVATE_KEY from Secret Manager
      env {
        name = "JWT_PRIVATE_KEY"
        value_source {
          secret_key_ref {
            secret  = var.gcp_secret_jwt_private_key_id
            version = "latest"
          }
        }
      }

      # JWT_PUBLIC_KEY from Secret Manager
      env {
        name = "JWT_PUBLIC_KEY"
        value_source {
          secret_key_ref {
            secret  = var.gcp_secret_jwt_public_key_id
            version = "latest"
          }
        }
      }

      # VAULT_TOKEN from Secret Manager (conditional)
      dynamic "env" {
        for_each = var.gcp_secret_vault_token_id != "" ? [1] : []
        content {
          name = "VAULT_TOKEN"
          value_source {
            secret_key_ref {
              secret  = var.gcp_secret_vault_token_id
              version = "latest"
            }
          }
        }
      }

      liveness_probe {
        http_get {
          path = "/health"
          port = var.app_port
        }
        initial_delay_seconds = 30
        period_seconds        = 15
        failure_threshold     = 3
        timeout_seconds       = 5
      }

      startup_probe {
        http_get {
          path = "/health"
          port = var.app_port
        }
        initial_delay_seconds = 10
        period_seconds        = 5
        failure_threshold     = 12
        timeout_seconds       = 3
      }
    }
  }

  labels = {
    environment = var.environment
    project     = replace(var.project, "-", "_")
    managed_by  = "terraform"
  }
}

# Allow unauthenticated (public internet) invocations of the Cloud Run service.
# Authentication for AgentIdP clients is handled by the application layer
# (JWT Bearer tokens), not by Cloud Run's built-in IAM auth.
resource "google_cloud_run_v2_service_iam_member" "public_invoker" {
  count = var.provider_type == "gcp" ? 1 : 0

  project  = var.gcp_project_id
  location = var.gcp_region
  name     = google_cloud_run_v2_service.app[0].name
  role     = "roles/run.invoker"
  member   = "allUsers"
}
