SystemContext.java

package com.cloudforgeci.api.core;

import com.cloudforgeci.api.core.rules.AwsConfigRule;
import com.cloudforgeci.api.core.rules.ComplianceMatrix;
import com.cloudforgeci.api.core.rules.Rules;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.TopologyType;
import com.cloudforge.core.enums.SecurityProfile;
import com.cloudforge.core.enums.IAMProfile;
import com.cloudforgeci.api.interfaces.SecurityProfileConfiguration;
import com.cloudforgeci.api.network.VpcFactory;
import com.cloudforgeci.api.ingress.AlbFactory;
import com.cloudforgeci.api.storage.EfsFactory;
import com.cloudforgeci.api.observability.LoggingCwFactory;
import com.cloudforgeci.api.observability.GuardDutyFactory;
import com.cloudforgeci.api.compute.FargateFactory;
import com.cloudforgeci.api.storage.ContainerFactory;
import com.cloudforgeci.api.observability.AlarmFactory;
import com.cloudforgeci.api.compute.Ec2Factory;
// Note: S3BucketFactory, CloudFrontFactory, and Ec2InstanceFactory will be created later
import com.cloudforgeci.api.network.DomainFactory;
import com.cloudforgeci.api.security.ApplicationOidcFactory;
import com.cloudforgeci.api.security.ApplicationSamlFactory;
import com.cloudforgeci.api.security.CertificateFactory;
import com.cloudforgeci.api.security.CognitoAuthenticationFactory;
import com.cloudforgeci.api.security.IdentityCenterFactory;
import com.cloudforgeci.api.security.IdentityCenterSamlFactory;
import com.cloudforgeci.api.security.OidcAuthenticationFactory;

import software.amazon.awscdk.Stack;
import software.amazon.awscdk.services.ec2.FlowLogOptions;
import software.amazon.awscdk.services.ec2.Peer;
import software.amazon.awscdk.services.ec2.Port;
import software.amazon.awscdk.services.ec2.SecurityGroup;
import software.amazon.awscdk.services.ecs.ContainerDefinition;
import software.amazon.awscdk.services.ecs.FargateService;
import software.amazon.awscdk.services.ecs.TaskDefinition;
import software.amazon.awscdk.services.efs.AccessPoint;
import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationListener;
import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationTargetGroup;
import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationLoadBalancer;
import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationProtocol;
import software.amazon.awscdk.services.elasticloadbalancingv2.TargetType;
import software.amazon.awscdk.services.elasticloadbalancingv2.HealthCheck;
import software.amazon.awscdk.services.elasticloadbalancingv2.AddApplicationTargetGroupsProps;
import io.github.cdklabs.cdknag.NagPackSuppression;
import io.github.cdklabs.cdknag.NagSuppressions;
import software.constructs.Construct;
import software.constructs.IConstruct;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;


public final class SystemContext extends Construct {

  private static final String NODE_ID = "SystemContext";
    private static final Logger LOG = Logger.getLogger(SystemContext.class.getName());

  public final TopologyType topology;
  public final RuntimeType runtime;
  public final SecurityProfile security;
  public final IAMProfile iamProfile;
  public final DeploymentContext cfc;
  public final String stackName;

  // Security Profile Configuration
  public final Slot<SecurityProfileConfiguration> securityProfileConfig = new Slot<>();

  // Application Specification
  public final Slot<com.cloudforge.core.interfaces.ApplicationSpec> applicationSpec = new Slot<>();

  // Common slots
  public final Slot<software.amazon.awscdk.services.ec2.Vpc> vpc = new Slot<>();
  public final Slot<software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationLoadBalancer> alb = new Slot<>();
  public final Slot<software.amazon.awscdk.services.autoscaling.AutoScalingGroup> asg = new Slot<>();
  public final Slot<software.amazon.awscdk.services.ec2.Instance> ec2Instance = new Slot<>();
  public final Slot<software.amazon.awscdk.services.efs.FileSystem> efs = new Slot<>();
  public final Slot<software.amazon.awscdk.services.logs.LogGroup> logs = new Slot<>();
  public final Slot<software.amazon.awscdk.services.route53.IHostedZone> zone = new Slot<>();
  public final Slot<software.amazon.awscdk.services.ec2.SecurityGroup> instanceSg = new Slot<>();

  // ALB Properties
  public final Slot<ApplicationTargetGroup> albTargetGroup = new Slot<>();
  public final Slot<Boolean> httpsTargetsAdded = new Slot<>();
  public final Slot<Boolean> wired = new Slot<>();
  public final Slot<Boolean> dnsRecordsCreated = new Slot<>();
  public final Slot<Boolean> dnsRecordsCallbackRegistered = new Slot<>();
  public final Slot<Boolean> asgAddedToTargetGroup = new Slot<>();
  public final Slot<Boolean> scalingPoliciesApplied = new Slot<>();
  public final Slot<Boolean> fargateAutoscalingConfigured = new Slot<>();
  public final Slot<Boolean> fargateAutoscalingCallbackRegistered = new Slot<>();
  public final Slot<Boolean> ec2AutoscalingCallbackRegistered = new Slot<>();
  public final Slot<SecurityGroup> albSg = new Slot<>();
  public final Slot<ApplicationListener> http = new Slot<>();

  // EFS Properties
  public final Slot<SecurityGroup> efsSg = new Slot<>();
  public final Slot<AccessPoint> ap = new Slot<>();

  // Fargate Properties
  public final Slot<FargateService> fargateService = new Slot<>();
  public final Slot<SecurityGroup> fargateServiceSg = new Slot<>();
  public final Slot<TaskDefinition> fargateTaskDef = new Slot<>();

  // ECS Container Properties
  public final Slot<ContainerDefinition> container = new Slot<>();

  // Certificate Properties
  public final Slot<ApplicationListener> https = new Slot<>();
  public final Slot<software.amazon.awscdk.services.certificatemanager.ICertificate> cert = new Slot<>();
  public final Slot<software.amazon.awscdk.services.acmpca.CfnCertificateAuthority> privateCa = new Slot<>();

  // Identity Center Properties (for auto-provisioned OIDC)
  public final Slot<software.amazon.awscdk.CustomResource> identityCenter = new Slot<>();

  // Cognito Properties (for Cognito User Pool OIDC)
  public final Slot<String> cognitoIssuer = new Slot<>();
  public final Slot<String> cognitoAuthorizationEndpoint = new Slot<>();
  public final Slot<String> cognitoTokenEndpoint = new Slot<>();
  public final Slot<String> cognitoUserInfoEndpoint = new Slot<>();
  public final Slot<String> cognitoLogoutEndpoint = new Slot<>();
  public final Slot<String> cognitoClientId = new Slot<>();
  public final Slot<String> cognitoClientSecretName = new Slot<>();
  public final Slot<String> cognitoUserPoolId = new Slot<>();
  public final Slot<String> cognitoDomainPrefix = new Slot<>();

  // Cognito CDK Objects (for native ALB Cognito authentication)
  public final Slot<software.amazon.awscdk.services.cognito.IUserPool> cognitoUserPool = new Slot<>();
  public final Slot<software.amazon.awscdk.services.cognito.IUserPoolClient> cognitoUserPoolClient = new Slot<>();
  public final Slot<software.amazon.awscdk.services.cognito.IUserPoolDomain> cognitoUserPoolDomain = new Slot<>();

  // Database Properties (RDS)
  public final Slot<software.amazon.awscdk.services.rds.DatabaseInstance> rdsDatabase = new Slot<>();
  public final Slot<software.amazon.awscdk.services.secretsmanager.Secret> dbCredentials = new Slot<>();
  public final Slot<com.cloudforge.core.interfaces.DatabaseSpec.DatabaseConnection> dbConnection = new Slot<>();
  public final Slot<SecurityGroup> dbSecurityGroup = new Slot<>();
  // Connection string components for distroless containers (Mattermost) that can't do shell substitution
  public final Slot<java.util.Map<String, String>> dbConnectionStringComponents = new Slot<>();
  // SSM Parameter containing the complete datasource URL (for distroless containers)
  // Store the actual parameter object to avoid lookup issues
  public final Slot<software.amazon.awscdk.services.ssm.StringParameter> dbDatasourceParameter = new Slot<>();

  // INTERNAL USE ONLY: Cognito client secret Custom Resource (for CDK dependency tracking)
  // This is used internally to ensure ECS tasks don't start before the secret is created in Secrets Manager.
  // Do not use this field directly - it is managed automatically by CognitoAuthenticationFactory.
  public final Slot<software.constructs.IConstruct> cognitoClientSecretResourceInternal = new Slot<>();

  // Application-level OIDC configuration (for application-oidc mode)
  // Built by ApplicationOidcFactory and used by ApplicationSpec implementations
  public final Slot<com.cloudforge.core.interfaces.OidcConfiguration> applicationOidcConfig = new Slot<>();

  // INTERNAL USE ONLY: Application OIDC client secret Custom Resource (for CDK dependency tracking)
  // This is used internally to ensure ECS tasks don't start before the secret is created in Secrets Manager.
  // Do not use this field directly - it is managed automatically by ApplicationOidcFactory.
  public final Slot<software.constructs.IConstruct> applicationOidcClientSecretResource = new Slot<>();

  // Keycloak SAML Bridge Deployment Tracking (for cognito-saml provider)
  // Tracks whether Keycloak has been deployed in this stack (shared by all SAML apps)
  public final Slot<Boolean> keycloakDeployed = new Slot<>();
  public final Slot<String> keycloakServiceUrl = new Slot<>(); // e.g., https://auth.example.com

  // SSL Configuration Properties
  public final Slot<Boolean> sslEnabled = new Slot<>();
  public final Slot<Boolean> httpRedirectEnabled = new Slot<>();

  // Networking Configuration Properties (used in SecurityProfile)
  public final Slot<String> networkMode = new Slot<>(); // "public-no-nat" | "private-with-nat"
  public final Slot<Boolean> wafEnabled = new Slot<>();
  public final Slot<Boolean> cloudfront = new Slot<>();
  public final Slot<String> lbType = new Slot<>(); // "alb" | "nlb"

  // Auto Scaling Configuration Properties (used in RuntimeConfiguration)
  public final Slot<Integer> minInstanceCapacity = new Slot<>();
  public final Slot<Integer> maxInstanceCapacity = new Slot<>();
  public final Slot<Integer> cpuTargetUtilization = new Slot<>();

  // Container Configuration Properties (used in FargateRuntimeConfiguration)
  public final Slot<Integer> cpu = new Slot<>();
  public final Slot<Integer> memory = new Slot<>();

  // Authentication Configuration Properties (used in SecurityProfile)
  public final Slot<String> authMode = new Slot<>(); // "none" | "alb-oidc" | "jenkins-oidc" | "application-oidc"
  public final Slot<String> ssoInstanceArn = new Slot<>();
  public final Slot<String> ssoGroupId = new Slot<>();
  public final Slot<String> ssoTargetAccountId = new Slot<>();

  // SAML Configuration (used by CognitoSamlFactory for Cognito SAML IdP)
  // These slots are provider-agnostic and work with Cognito SAML
  public final Slot<String> samlSiteUrl = new Slot<>();           // SP site URL (Entity ID)
  public final Slot<String> samlAcsUrl = new Slot<>();            // Assertion Consumer Service URL
  public final Slot<String> samlIdpMetadataUrl = new Slot<>();    // IdP SAML metadata URL
  public final Slot<String> samlIdpSsoUrl = new Slot<>();         // IdP SSO endpoint URL
  public final Slot<String> samlIdpEntityId = new Slot<>();       // IdP Entity ID (issuer)
  public final Slot<String> samlIdpLogoutUrl = new Slot<>();      // IdP SLO endpoint URL
  public final Slot<String> samlProviderType = new Slot<>();      // Provider type: "cognito"
  public final Slot<String> samlConfigSecretArn = new Slot<>();   // Secrets Manager ARN for IdP config

  // Storage Configuration Properties (used in IAMConfiguration)
  public final Slot<String> artifactsBucket = new Slot<>();
  public final Slot<String> artifactsPrefix = new Slot<>();
  public final Slot<Boolean> enableFlowlogs = new Slot<>();

  // DNS Configuration Properties (used in TopologyConfiguration)
  public final Slot<String> domain = new Slot<>();
  public final Slot<String> subdomain = new Slot<>();
  public final Slot<String> fqdn = new Slot<>();

  // S3 website bits
  public final Slot<software.amazon.awscdk.services.s3.Bucket> websiteBucket = new Slot<>();
  public final Slot<software.amazon.awscdk.services.cloudfront.Distribution> distribution = new Slot<>();

  // Logging
  public final Slot<FlowLogOptions> flowlogs = new Slot<>();

  // Security
  public final Slot<software.amazon.awscdk.services.wafv2.CfnWebACL> wafWebAcl = new Slot<>();

  // IAM Roles
  public final Slot<software.amazon.awscdk.services.iam.Role> ec2InstanceRole = new Slot<>();
  public final Slot<software.amazon.awscdk.services.iam.Role> fargateExecutionRole = new Slot<>();
  public final Slot<software.amazon.awscdk.services.iam.Role> fargateTaskRole = new Slot<>();

  private final Set<String> onceKeys = new HashSet<>();
  private final List<Runnable> deferredActions = new ArrayList<>();
  private final Set<AwsConfigRule> requiredConfigRules = new HashSet<>();
  private boolean installed = false;

  private SystemContext(Stack stack, TopologyType topology, RuntimeType runtime, SecurityProfile security, IAMProfile iamProfile, DeploymentContext cfc) {
    super(stack, NODE_ID);
    this.topology = topology;
    this.runtime = runtime;
    this.security = security;
    this.iamProfile = iamProfile;
    this.cfc = cfc;
    this.stackName = stack.getStackName();
  }

  /** Start once at the entry point; installs runtime + topology + security + iam rules and wiring. */
  public static SystemContext start(Construct scope, TopologyType topology, RuntimeType runtime, SecurityProfile security, IAMProfile iamProfile, DeploymentContext cfc) {

    try {
      Stack stack = Stack.of(scope);

      SystemContext existing = (SystemContext) stack.getNode().tryFindChild(NODE_ID);
      if (existing != null) {
        // Allow multiple runtime types in the same stack for testing purposes
        // Runtime type check removed to allow EC2 and Fargate in same stack
        if (existing.topology != topology) {
          throw new IllegalStateException("SystemContext already started with topology = " + existing.topology);
        }
        if (existing.security != security) {
          throw new IllegalStateException("SystemContext already started with security = " + existing.security);
        }
        if (existing.iamProfile != iamProfile) {
          throw new IllegalStateException("SystemContext already started with iamProfile = " + existing.iamProfile);
        }

        return existing;
      }

      var ctx = new SystemContext(stack, topology, runtime, security, iamProfile, cfc);

      if (!ctx.installed) {
        try {
          Rules.installAll(ctx);   // installs RuntimeRules, TopologyRules, SecurityRules, and IAMRules
        } catch (Exception e) {
          LOG.log(Level.SEVERE, "*** ERROR in Rules.installAll() ***", e);
          throw e;
        }
        ctx.installed = true;

        // Stack-level CDK-nag suppressions for PRODUCTION and STAGING
        // STAGING has same compliance requirements as PRODUCTION
        if (security == SecurityProfile.PRODUCTION || security == SecurityProfile.STAGING) {
          applyStackLevelSuppressions(stack, security);
        }
      } else {
      }

      return ctx;

    } catch (Exception e) {
      LOG.log(Level.SEVERE, "*** ERROR in SystemContext.start() ***", e);
      throw e;
    }
  }

  /** Fetch the already-started context anywhere down the tree. */
  public static SystemContext of(Construct scope) {
    for (Construct cur = scope; cur != null; ) {
      Construct child = (Construct) cur.getNode().tryFindChild(NODE_ID);
      if (child instanceof SystemContext sc) return sc;
      IConstruct parent = cur.getNode().getScope();
      cur = parent instanceof Construct c ? c : null;
    }
    throw new IllegalStateException("SystemContext not started yet. Call SystemContext.start(...) first.");
  }

  /** Guard to register a wiring block only once per Stack. */
  public boolean once(String key, Runnable r) {
    if (!onceKeys.add(key)) return false;
    deferredActions.add(r);
    return true;
  }

  /** Execute all deferred actions. Call this after all factories are created. */
  public void executeDeferredActions() {
    // Create a copy to avoid ConcurrentModificationException
    List<Runnable> actionsToExecute = new ArrayList<>(deferredActions);
    // Clear the original list to prevent interference from new actions
    deferredActions.clear();
    for (Runnable action : actionsToExecute) {
      try {
        action.run();
      } catch (Exception e) {
        LOG.log(Level.SEVERE, "*** Error executing deferred action ***", e);
        throw e;
      }
    }
  }

  // ============================================================================
  // AWS CONFIG RULES COLLECTOR
  // ============================================================================

  /**
   * Register an AWS Config rule as required for this deployment.
   * Factories call this method where they create the infrastructure being monitored.
   * Duplicate rules are automatically deduplicated via Set.
   *
   * @param rule The AWS Config rule to require
   */
  public void requireConfigRule(AwsConfigRule rule) {
    requiredConfigRules.add(rule);
  }

  /**
   * Register all AWS Config rules for a specific security control.
   * Use this when enabling a security control (e.g., ENCRYPTION_AT_REST)
   * to automatically include all related Config rules.
   *
   * @param control The security control to get rules for
   */
  public void requireConfigRulesForControl(ComplianceMatrix.SecurityControl control) {
    requiredConfigRules.addAll(AwsConfigRule.getRulesForControl(control));
  }

  /**
   * Get all required AWS Config rules collected from factories.
   * Called by ComplianceFactory to deploy the rules.
   *
   * @return Unmodifiable set of required Config rules
   */
  public Set<AwsConfigRule> getRequiredConfigRules() {
    return Set.copyOf(requiredConfigRules);
  }

  public String debugPath(Construct scope) {
    String here = scope.getNode().getPath();
    String sc   = this.getNode().getPath();
    return "scopePath = " + here + " | ctxPath = " + sc + " | runtime = " + runtime + " | topology = " + topology + " | security = " + security + " | iamProfile = " + iamProfile + " | slots = " + presentSlots();
  }

  public String presentSlots() {
    return "vpc = "+vpc.get().isPresent()
            +", alb = "+alb.get().isPresent()
            +", efs = "+efs.get().isPresent()
            +", http = "+http.get().isPresent()
            +", tg = "+albTargetGroup.get().isPresent()
            +", ap = "+ap.get().isPresent()
            +", asg = "+asg.get().isPresent()
            +", fargate = "+fargateService.get().isPresent()
            +", instSg = "+instanceSg.get().isPresent();
  }

  // ============================================================================
  // ORCHESTRATION LAYER - Centralized Factory Creation
  // ============================================================================

  /**
   * Creates infrastructure factories in the correct order with proper context injection.
   * This orchestration layer ensures that infrastructure factories are created consistently
   * and can be reused across different application factories.
   *
   * @param scope The CDK construct scope
   * @param idPrefix Prefix for factory IDs (e.g., "Jenkins", "MyApp")
   * @return InfrastructureFactories containing references to created factories
   */
  public InfrastructureFactories createInfrastructureFactories(Construct scope, String idPrefix) {

    // Create infrastructure factories in dependency order
    VpcFactory vpcFactory = createVpcFactory(scope, idPrefix);
    AlbFactory albFactory = createAlbFactory(scope, idPrefix);
    EfsFactory efsFactory = createEfsFactory(scope, idPrefix);
    LoggingCwFactory loggingFactory = createLoggingFactory(scope, idPrefix);

    // Create GuardDuty threat detection (account-level service)
    createGuardDutyFactory(scope, idPrefix);

    // Note: Security factories (Certificate, OIDC, Identity Center) are created
    // in JenkinsFactory after DomainFactory, as they require the hosted zone

    // Create instance security group only for EC2 deployments
    if (this.runtime == RuntimeType.EC2) {
      createInstanceSecurityGroup(scope, idPrefix);
    }

    // Create target groups (orchestrated by SystemContext)
    createTargetGroups(scope, idPrefix);


    return new InfrastructureFactories(vpcFactory, albFactory, efsFactory, loggingFactory);
  }

  /**
   * Creates a VPC factory with proper context injection.
   */
  public VpcFactory createVpcFactory(Construct scope, String idPrefix) {
    VpcFactory vpcFactory = new VpcFactory(scope, idPrefix + "Vpc");
    vpcFactory.create();
    return vpcFactory;
  }

  /**
   * Creates an ALB factory with proper context injection.
   */
  public AlbFactory createAlbFactory(Construct scope, String idPrefix) {
    AlbFactory albFactory = new AlbFactory(scope, idPrefix + "Alb");
    albFactory.create();
    return albFactory;
  }

  /**
   * Creates an EFS factory with proper context injection.
   */
  public EfsFactory createEfsFactory(Construct scope, String idPrefix) {
    EfsFactory efsFactory = new EfsFactory(scope, idPrefix + "Efs");
    efsFactory.create();
    return efsFactory;
  }

  /**
   * Creates a logging factory with proper context injection.
   */
  public LoggingCwFactory createLoggingFactory(Construct scope, String idPrefix) {
    LoggingCwFactory loggingFactory = new LoggingCwFactory(scope, idPrefix + "Logging");
    loggingFactory.create();
    return loggingFactory;
  }

  /**
   * Creates GuardDuty threat detection factory.
   * Conditionally enabled based on security profile or explicit configuration.
   */
  public void createGuardDutyFactory(Construct scope, String idPrefix) {
    GuardDutyFactory guardDutyFactory = new GuardDutyFactory(scope, idPrefix + "GuardDuty");
    guardDutyFactory.create();
  }

  /**
   * Creates security-related factories (Certificate, OIDC, Identity Center).
   * These factories are conditionally created based on context configuration.
   *
   * IMPORTANT: Certificate is created LAST to ensure proper CloudFormation deletion order.
   * When deleting a stack, CloudFormation deletes resources in reverse creation order.
   * By creating the certificate last, it will be deleted first, before the ALB HTTPS listener,
   * preventing "Certificate in use" deletion errors.
   */
  public void createSecurityFactories(Construct scope, String idPrefix) {
    // Create Cognito authentication factory (auto-provisions Cognito User Pool if configured)
    // This must run BEFORE OidcAuthenticationFactory so endpoints are available
    CognitoAuthenticationFactory cognitoFactory = new CognitoAuthenticationFactory(scope, idPrefix + "Cognito");
    cognitoFactory.create();

    // Create Identity Center factory (creates placeholder secret for manual OIDC setup)
    IdentityCenterFactory identityCenterFactory = new IdentityCenterFactory(scope, idPrefix + "IdentityCenter");
    identityCenterFactory.create();

    // Create Identity Center SAML factory (auto-provisions SAML app if autoProvisionIdentityCenter = true)
    // This creates a SAML 2.0 application in IAM Identity Center for Mattermost, etc.
    IdentityCenterSamlFactory identityCenterSamlFactory = new IdentityCenterSamlFactory(scope, idPrefix + "IdentityCenterSaml");
    identityCenterSamlFactory.create();

    // Create OIDC authentication factory (configures ALB OIDC if authMode = alb-oidc)
    // This checks for Cognito endpoints first, then IAM Identity Center, then manual config
    OidcAuthenticationFactory oidcFactory = new OidcAuthenticationFactory(scope, idPrefix + "OidcAuth");
    oidcFactory.create();

    // Create Application OIDC factory (configures application-level OIDC if authMode = application-oidc)
    // This handles OIDC integration within the application itself (Jenkins, GitLab, etc.)
    ApplicationOidcFactory applicationOidcFactory = new ApplicationOidcFactory(scope, idPrefix + "ApplicationOidc");
    applicationOidcFactory.create();

    // Create Application SAML factory (configures application-level SAML if authMode = application-oidc)
    // This handles SAML integration for applications that require SAML authentication
    // - cognito-saml: Deploys Keycloak as SAML bridge to Cognito (works with ANY SAML-enabled app)
    // - identity-center: Uses IAM Identity Center SAML (configured by IdentityCenterSamlFactory)
    ApplicationSamlFactory applicationSamlFactory = new ApplicationSamlFactory(scope, idPrefix + "ApplicationSaml");
    applicationSamlFactory.create();

    // NOTE: ComplianceFactory is now created by security profile configurations
    // (ProductionSecurityConfiguration, StagingSecurityConfiguration) to avoid duplicates
    // and ensure proper integration with security profile settings.

    // Create Certificate LAST so it's deleted FIRST during stack teardown
    // This prevents "Certificate in use" errors when deleting the HTTPS listener
    CertificateFactory certificateFactory = new CertificateFactory(scope, idPrefix + "Certificate");
    certificateFactory.create();
  }

  /**
   * Creates target groups orchestrated by SystemContext.
   * This centralizes target group management and prevents duplicates.
   *
   * For HTTPS_STRICT mode (PCI-DSS compliance), target group creation is deferred
   * to Ec2RuntimeConfiguration which creates them after the HTTPS listener exists.
   */
  public void createTargetGroups(Construct scope, String idPrefix) {

    if (this.runtime == RuntimeType.EC2) {
      // Check if HTTPS strict mode is enabled - defer to RuntimeConfiguration if so
      boolean httpsStrict = securityProfileConfig.get()
          .map(config -> config.isHttpsStrictEnabled())
          .orElse(false);
      boolean sslEnabled = Boolean.TRUE.equals(cfc.enableSsl());

      if (httpsStrict && sslEnabled) {
        // HTTPS_STRICT mode: No HTTP listener exists, HTTPS listener not yet created
        // Defer target group creation to Ec2RuntimeConfiguration.wire()
        LOG.info("HTTPS strict mode: Deferring target group creation to Ec2RuntimeConfiguration");
        return;
      }

      // Create target group for EC2 runtime (standard HTTP mode)
      ApplicationLoadBalancer alb = this.alb.get().orElseThrow(() ->
        new IllegalStateException("ALB not found when creating target groups"));

      ApplicationTargetGroup targetGroup = ApplicationTargetGroup.Builder.create(scope, idPrefix + "Tg")
          .vpc(this.vpc.get().orElseThrow())
          .port(8080)
          .protocol(ApplicationProtocol.HTTP)
          .targetType(TargetType.INSTANCE)
          .healthCheck(HealthCheck.builder()
              .path("/login")
              .healthyHttpCodes("200-299")
              .interval(software.amazon.awscdk.Duration.seconds(
                  cfc.healthCheckInterval() != null ? cfc.healthCheckInterval() : 30))
              .timeout(software.amazon.awscdk.Duration.seconds(
                  cfc.healthCheckTimeout() != null ? cfc.healthCheckTimeout() : 5))
              .healthyThresholdCount(
                  cfc.healthyThreshold() != null ? cfc.healthyThreshold() : 2)
              .unhealthyThresholdCount(
                  cfc.unhealthyThreshold() != null ? cfc.unhealthyThreshold() : 3)
              .build())
          .build();

      this.albTargetGroup.set(targetGroup);

      // Add target group to the HTTP listener
      Optional<ApplicationListener> httpListener = this.http.get();
      if (httpListener.isPresent()) {
        httpListener.get().addTargetGroups(idPrefix + "HttpTg", AddApplicationTargetGroupsProps.builder()
            .targetGroups(List.of(targetGroup))
            .build());
      } else {
        throw new IllegalStateException("HTTP listener not found when creating target groups (non-HTTPS_STRICT mode)");
      }

    } else if (this.runtime == RuntimeType.FARGATE) {
      // For Fargate, target groups are created by FargateRuntimeConfiguration
      // We should not create target groups here to avoid conflicts
    } else {
      LOG.warning("*** SystemContext: Unknown runtime type: " + this.runtime + " ***");
    }
  }

  /**
   * Creates instance security group for EC2 deployments.
   * This is infrastructure-specific but not a full factory.
   */
  public SecurityGroup createInstanceSecurityGroup(Construct scope, String idPrefix) {

    // Check if instance security group already exists
    if (this.instanceSg.get().isPresent()) {
      return this.instanceSg.get().orElseThrow();
    }

    // Check if egress should be restricted to VPC CIDR only
    boolean restrictEgress = securityProfileConfig.get()
        .map(SecurityProfileConfiguration::isRestrictSecurityGroupEgressEnabled)
        .orElse(false);

    var vpcInstance = vpc.get().orElseThrow();
    SecurityGroup instanceSg = SecurityGroup.Builder.create(scope, idPrefix + "InstanceSg")
            .vpc(vpcInstance)
            .description("EC2 Instance Security Group")
            .allowAllOutbound(!restrictEgress)
            .build();

    // If egress is restricted, add explicit egress rule for VPC CIDR only
    if (restrictEgress) {
      instanceSg.addEgressRule(
          Peer.ipv4(vpcInstance.getVpcCidrBlock()),
          Port.allTraffic(),
          "Allow egress to VPC CIDR only"
      );
    }

    this.instanceSg.set(instanceSg);
    return instanceSg;
  }

  // ============================================================================
  // DEPLOYMENT TYPE ORCHESTRATION - High-level deployment methods
  // ============================================================================

  /**
   * Creates a complete Jenkins deployment with infrastructure and Jenkins-specific resources.
   * Supports both Fargate and EC2 runtimes with optional domain and SSL.
   *
   * @param scope The CDK construct scope
   * @param id Unique identifier for the Jenkins deployment
   * @return JenkinsDeployment containing all created resources
   */
  public JenkinsDeployment createJenkinsDeployment(Construct scope, String id) {

    // Create infrastructure factories
    InfrastructureFactories infra = createInfrastructureFactories(scope, id);

    // Create Jenkins-specific factories based on runtime
    JenkinsSpecificFactories jenkins = createJenkinsSpecificFactories(scope, id);

    // Create domain and SSL if configured
    DomainAndSslFactories domainSsl = createDomainAndSslFactories(scope, id);


    return new JenkinsDeployment(infra, jenkins, domainSsl);
  }

  /**
   * Creates a complete S3 + CloudFront deployment for static web applications.
   * Supports Angular, React, or any static site with optional domain.
   *
   * @param scope The CDK construct scope
   * @param id Unique identifier for the S3 deployment
   * @return S3CloudFrontDeployment containing all created resources
   */
  public S3CloudFrontDeployment createS3CloudFrontDeployment(Construct scope, String id) {

    // Create S3 and CloudFront factories
    S3CloudFrontFactories s3cf = createS3CloudFrontFactories(scope, id);

    // Create domain if configured
    DomainAndSslFactories domainSsl = createDomainAndSslFactories(scope, id);


    return new S3CloudFrontDeployment(s3cf, domainSsl);
  }

  /**
   * Creates Jenkins-specific factories based on the current runtime configuration.
   */
  private JenkinsSpecificFactories createJenkinsSpecificFactories(Construct scope, String id) {

    if (runtime == RuntimeType.FARGATE) {
      return createFargateJenkinsFactories(scope, id);
    } else if (runtime == RuntimeType.EC2) {
      return createEc2JenkinsFactories(scope, id);
    } else {
      throw new IllegalArgumentException("Unsupported runtime for Jenkins deployment: " + runtime);
    }
  }

  /**
   * Creates Fargate-specific Jenkins factories.
   */
  private JenkinsSpecificFactories createFargateJenkinsFactories(Construct scope, String id) {

    // Create Fargate factory
    FargateFactory fargate = new FargateFactory(scope, id + "Fargate");
    fargate.create();

    // Create container factory
    ContainerFactory container = new ContainerFactory(scope, id + "Container",
        software.amazon.awscdk.services.ecs.ContainerImage.fromRegistry("jenkins/jenkins:lts"));
    container.create();

    // JenkinsBootstrap removed - logic migrated to FargateFactory (security groups, CFN output)

    // Create alarms
    AlarmFactory alarms = new AlarmFactory(scope, id + "Alarms", null);
    alarms.create();

    return new JenkinsSpecificFactories(fargate, container, alarms, null, null);
  }

  /**
   * Creates EC2-specific Jenkins factories.
   */
  private JenkinsSpecificFactories createEc2JenkinsFactories(Construct scope, String id) {

    // Instance security group is already created by createInfrastructureFactories()
    // No need to create it again here

    // Create EC2 factory (Auto Scaling Group) if maxInstanceCapacity is provided
    Ec2Factory ec2 = null;
    if (cfc.maxInstanceCapacity() != null) {
      ec2 = new Ec2Factory(scope, id + "Ec2");
      ec2.create();
    }

    // Create single EC2 instance if no Auto Scaling Group
    // Note: Ec2InstanceFactory will be created later
    Object singleInstance = null;
    if (cfc.maxInstanceCapacity() == null) {
      // TODO: Create Ec2InstanceFactory for single instances
    }

    // Create alarms
    AlarmFactory alarms = new AlarmFactory(scope, id + "Alarms", null);
    alarms.create();

    return new JenkinsSpecificFactories(null, null, alarms, ec2, singleInstance);
  }

  /**
   * Creates S3 and CloudFront factories for static web applications.
   */
  private S3CloudFrontFactories createS3CloudFrontFactories(Construct scope, String id) {

    // Create S3 bucket factory
    // TODO: Create S3BucketFactory
    Object s3 = null;

    // Create CloudFront distribution factory
    // TODO: Create CloudFrontFactory
    Object cloudfront = null;

    return new S3CloudFrontFactories(s3, cloudfront);
  }

  /**
   * Creates domain and SSL factories if configured in the deployment context.
   */
  private DomainAndSslFactories createDomainAndSslFactories(Construct scope, String id) {

    DomainFactory domain = null;

    // Create domain factory if domain is provided
    if (cfc.domain() != null && !cfc.domain().isBlank()) {
      domain = new DomainFactory(scope, id + "Domain");
      domain.create();
    }

    // SSL certificate creation is handled by runtime configurations (FargateRuntimeConfiguration)

    return new DomainAndSslFactories(domain, null);
  }

  // ============================================================================
  // DEPLOYMENT CONTAINERS - Records for different deployment types
  // ============================================================================

  /**
   * Container for infrastructure factories created by the orchestration layer.
   * This provides a clean interface for accessing infrastructure components.
   *
   * @param vpc the VPC factory
   * @param alb the Application Load Balancer factory
   * @param efs the Elastic File System factory
   * @param logging the CloudWatch logging factory
   */
  public record InfrastructureFactories(
      VpcFactory vpc,
      AlbFactory alb,
      EfsFactory efs,
      LoggingCwFactory logging
  ) {}

  /**
   * Container for Jenkins-specific factories.
   * Note: JenkinsBootstrap removed - logic migrated to FargateFactory
   *
   * @param fargate the Fargate factory for container orchestration
   * @param container the container factory for Docker configuration
   * @param alarms the alarm factory for monitoring
   * @param ec2 the EC2 factory for VM-based deployments
   * @param singleInstance the single instance factory (if applicable)
   */
  public record JenkinsSpecificFactories(
      FargateFactory fargate,
      ContainerFactory container,
      AlarmFactory alarms,
      Ec2Factory ec2,
      Object singleInstance
  ) {}

  /**
   * Container for S3 and CloudFront factories.
   *
   * @param s3 the S3 bucket factory
   * @param cloudfront the CloudFront distribution factory
   */
  public record S3CloudFrontFactories(
      Object s3,
      Object cloudfront
  ) {}

  /**
   * Container for domain and SSL factories.
   *
   * @param domain the domain name factory
   * @param ssl the SSL certificate factory (handled by runtime configurations)
   */
  public record DomainAndSslFactories(
      DomainFactory domain,
      Object ssl  // SSL is handled by runtime configurations
  ) {}

  /**
   * Container for complete Jenkins deployment.
   *
   * @param infrastructure the infrastructure factories (VPC, ALB, EFS, logging)
   * @param jenkins the Jenkins-specific factories (Fargate, container, alarms, EC2)
   * @param domainSsl the domain and SSL factories
   */
  public record JenkinsDeployment(
      InfrastructureFactories infrastructure,
      JenkinsSpecificFactories jenkins,
      DomainAndSslFactories domainSsl
  ) {}

  /**
   * Container for complete S3 + CloudFront deployment.
   *
   * @param s3CloudFront the S3 and CloudFront factories
   * @param domainSsl the domain and SSL factories
   */
  public record S3CloudFrontDeployment(
      S3CloudFrontFactories s3CloudFront,
      DomainAndSslFactories domainSsl
  ) {}

  /**
   * Apply stack-level CDK-nag suppressions for common architectural patterns.
   * These suppressions cover CDK framework limitations and intentional architectural decisions.
   */
  private static void applyStackLevelSuppressions(Stack stack, SecurityProfile security) {
    List<NagPackSuppression> suppressions = new ArrayList<>();

    // =====================================================================
    // AwsSolutions Rules (common CDK-nag pack)
    // =====================================================================

    // IAM5 - Wildcard permissions are required for many AWS services
    suppressions.add(NagPackSuppression.builder()
        .id("AwsSolutions-IAM5")
        .reason("Wildcard permissions are required for: (1) CloudWatch Logs - log group/stream patterns, " +
                "(2) S3 bucket operations with object prefixes, (3) SSM parameter paths, " +
                "(4) AWS Backup cross-resource operations. Permissions are scoped to specific " +
                "resource patterns where possible.")
        .build());

    // IAM4 - AWS managed policies for service roles
    suppressions.add(NagPackSuppression.builder()
        .id("AwsSolutions-IAM4")
        .reason("AWS managed policies are used for service roles where AWS recommends them: " +
                "(1) AWSBackupServiceRolePolicyForBackup - AWS Backup service role, " +
                "(2) AWSLambdaBasicExecutionRole - CDK custom resource Lambda functions, " +
                "(3) AmazonECSTaskExecutionRolePolicy - ECS task execution. " +
                "These are AWS-maintained and follow least privilege for their service.")
        .build());

    // L1 - Lambda runtime managed by CDK
    suppressions.add(NagPackSuppression.builder()
        .id("AwsSolutions-L1")
        .reason("Lambda runtime version is managed by CDK AwsCustomResource construct. " +
                "Runtime updates are applied through CDK version upgrades.")
        .build());

    // S1 - S3 server access logs for log buckets
    suppressions.add(NagPackSuppression.builder()
        .id("AwsSolutions-S1")
        .reason("S3 server access logging is disabled for log destination buckets (ALB logs, " +
                "CloudTrail logs) to prevent circular dependencies. CloudTrail S3 data events " +
                "provide audit logging for these buckets.")
        .build());

    // EC23 - Security group open access for public-facing services
    suppressions.add(NagPackSuppression.builder()
        .id("AwsSolutions-EC23")
        .reason("Security groups allow 0.0.0.0/0 ingress for: (1) ALB on ports 80/443 for " +
                "public web traffic, (2) EC2 optional ports (SSH, JNLP) when explicitly enabled. " +
                "Backend instances are protected in private subnets behind ALB.")
        .build());

    // ECS4 - Container Insights (cost optimization for non-production)
    if (security != SecurityProfile.PRODUCTION) {
      suppressions.add(NagPackSuppression.builder()
          .id("AwsSolutions-ECS4")
          .reason("CloudWatch Container Insights is disabled for DEV/STAGING to reduce costs. " +
                  "Production deployments enable Container Insights for operational visibility.")
          .build());
    }

    // =====================================================================
    // HIPAA.Security Rules
    // =====================================================================

    String inlinePolicyReason = "Inline policies are used for resource-specific permissions that are " +
        "tightly scoped to specific resources. This approach provides better security than managed " +
        "policies for application-specific access patterns. Permissions follow least privilege.";

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-IAMNoInlinePolicy")
        .reason(inlinePolicyReason)
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-CloudWatchLogGroupEncrypted")
        .reason("CloudWatch Log Groups use AWS-managed encryption by default. KMS encryption " +
                "is configurable via security profile for environments requiring customer-managed keys.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-VPCSubnetAutoAssignPublicIpDisabled")
        .reason("Public subnets require auto-assign public IP for resources that need direct " +
                "internet access (ALB, NAT gateways). Private subnets do not auto-assign public IPs.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-VPCNoUnrestrictedRouteToIGW")
        .reason("Public subnets require routes to Internet Gateway for ALB and NAT gateway " +
                "functionality. Private subnets route through NAT gateways, not directly to IGW.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-ALBHttpDropInvalidHeaderEnabled")
        .reason("ALB drop invalid headers is configured via configureAlbSecurity() method. " +
                "This setting may not be detected by CDK-nag during synthesis.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-ELBLoggingEnabled")
        .reason("ELB logging is enabled for production security profiles. DEV/STAGING may " +
                "disable logging for cost optimization. Enable via albAccessLogging config.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-LambdaConcurrency")
        .reason("CDK AwsCustomResource Lambda functions are short-lived and don't require " +
                "concurrency limits. They execute only during CloudFormation operations.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-LambdaDLQ")
        .reason("CDK AwsCustomResource Lambda functions report errors through CloudFormation. " +
                "DLQ is not needed as failures are visible in stack events.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-LambdaInsideVPC")
        .reason("CDK AwsCustomResource Lambda functions only make AWS API calls and don't " +
                "require VPC access. Running in VPC would add unnecessary complexity.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-ELBv2ACMCertificateRequired")
        .reason("Private CA certificates are used for OIDC authentication when no custom domain " +
                "is configured. For HIPAA production environments, configure a custom domain with " +
                "ACM public certificate. Private CA certificates enable development/testing without " +
                "requiring domain registration.")
        .build());

    // =====================================================================
    // PCI.DSS.321 Rules
    // =====================================================================

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-IAMNoInlinePolicy")
        .reason(inlinePolicyReason)
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-CloudWatchLogGroupEncrypted")
        .reason("CloudWatch Log Groups use AWS-managed encryption by default. KMS encryption " +
                "is configurable via security profile for PCI-DSS environments.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-VPCSubnetAutoAssignPublicIpDisabled")
        .reason("Public subnets require auto-assign public IP for ALB and NAT gateways. " +
                "Cardholder data environment uses private subnets without public IPs.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-VPCNoUnrestrictedRouteToIGW")
        .reason("Public subnets require IGW routes for ALB traffic. Private subnets with " +
                "cardholder data route through NAT, providing network segmentation per Req 1.3.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-ALBHttpDropInvalidHeaderEnabled")
        .reason("ALB drop invalid headers is configured via configureAlbSecurity(). " +
                "Complies with PCI-DSS Req 4.1 for secure transmission.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-ELBLoggingEnabled")
        .reason("ELB logging is enabled for production per PCI-DSS Req 10. Non-production " +
                "environments may disable for cost optimization.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-LambdaInsideVPC")
        .reason("CDK custom resource Lambdas make AWS API calls only and don't access " +
                "cardholder data environment. VPC placement not required for PCI scope.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-SecretsManagerUsingKMSKey")
        .reason("Secrets Manager uses AWS-managed encryption by default. Customer-managed KMS keys " +
                "can be configured for production PCI-DSS environments requiring key management control. " +
                "Default encryption meets PCI-DSS Req 3.4 for encryption at rest.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-RDSLoggingEnabled")
        .reason("RDS logging is configured based on database engine type. PostgreSQL/MySQL enable " +
                "appropriate log exports. Some log types (e.g., upgrade) may not be applicable to all " +
                "configurations. Audit logging per PCI-DSS Req 10 is ensured via CloudTrail and " +
                "engine-specific logs.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("PCI.DSS.321-ELBv2ACMCertificateRequired")
        .reason("Private CA certificates are used for OIDC authentication when no custom domain " +
                "is configured. For PCI-DSS production, configure a custom domain with ACM public certificate.")
        .build());

    // HIPAA suppressions
    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-SecretsManagerRotationEnabled")
        .reason("Secrets rotation is application-dependent and configured per deployment requirements. " +
                "Applications using database credentials can enable rotation through deployment config.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-SecretsManagerUsingKMSKey")
        .reason("Secrets Manager uses AWS-managed encryption by default. Customer-managed KMS keys " +
                "can be configured for HIPAA environments requiring key management control.")
        .build());

    suppressions.add(NagPackSuppression.builder()
        .id("HIPAA.Security-RDSLoggingEnabled")
        .reason("RDS logging is configured based on database engine type. Some log types (e.g., upgrade) " +
                "may not be applicable. Audit logging per HIPAA is ensured via CloudTrail and engine-specific logs.")
        .build());

    NagSuppressions.addStackSuppressions(stack, suppressions, true);
    LOG.info("Applied " + suppressions.size() + " stack-level CDK-nag suppressions for " + security + " profile");
  }
}