import * as cdk from 'aws-cdk-lib';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as codedeploy from 'aws-cdk-lib/aws-codedeploy';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sns_subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import * as ssm from 'aws-cdk-lib/aws-ssm';

import { Construct } from 'constructs';
import { stagingConfig, stagingResources, productionConfig, productionResources } from './config';

export interface CodePipelineStackProps extends cdk.StackProps {
  accountStage: string;
}

export class CodePipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: CodePipelineStackProps) {
    super(scope, id, props);

    const accountStage = props.accountStage;

    // Use staging and production resources from config file
    const {
      codeDeployApplicationName: stagingAppName,
      codeDeployDeploymentGroupName: stagingDeploymentGroupName
    } = stagingResources;

    const {
      codeDeployApplicationName: productionAppName,
      codeDeployDeploymentGroupName: productionDeploymentGroupName
    } = productionResources;

    // 1. Reference ECR repository from dev environment (same account)
    const sourceRepository = ecr.Repository.fromRepositoryAttributes(this, 'DevSourceRepository', {
      repositoryArn: `arn:aws:ecr:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:repository/studio-api`, // dev account (same as pipeline)
      repositoryName: 'studio-api',
    });

    // 2. Create CodeBuild projects
    const buildProject = this.createBuildProject(accountStage);
    const productionBuildProject = this.createProductionBuildProject(accountStage);

    // 3. Create Slack notification resources
    const { slackNotificationTopic, slackNotificationFunction } = this.createSlackNotificationResources();

    this.createApprovalNotifierResources();

    const { autoApprovalTopic } = this.createAutoApprovalResources();

    const unitTestProject = this.createUnitTestProject();
    const e2eTestProject = this.createE2ETestProject();

    this.createAutoRejectorResources();

    // 4. Create Pipeline with reference to existing CodeDeploy resources in staging and production
    const pipeline = this.createPipeline(
      sourceRepository,
      buildProject,
      productionBuildProject,
      stagingAppName,
      stagingDeploymentGroupName,
      productionAppName,
      productionDeploymentGroupName,
      slackNotificationTopic,
      autoApprovalTopic,
      unitTestProject,
      e2eTestProject,
    );

    // 4. Output important information
    new cdk.CfnOutput(this, 'PipelineName', {
      value: pipeline.pipelineName,
      description: 'Name of the CodePipeline',
    });

    new cdk.CfnOutput(this, 'BuildProjectName', {
      value: buildProject.projectName,
      description: 'Name of the staging CodeBuild project',
    });

    new cdk.CfnOutput(this, 'ProductionBuildProjectName', {
      value: productionBuildProject.projectName,
      description: 'Name of the production CodeBuild project',
    });

    new cdk.CfnOutput(this, 'StagingCodeDeployApplicationName', {
      value: stagingAppName,
      description: 'Name of the CodeDeploy application in staging account',
    });

    new cdk.CfnOutput(this, 'StagingCodeDeployDeploymentGroupName', {
      value: stagingDeploymentGroupName,
      description: 'Name of the CodeDeploy deployment group in staging account',
    });

    new cdk.CfnOutput(this, 'ProductionCodeDeployApplicationName', {
      value: productionAppName,
      description: 'Name of the CodeDeploy application in production account',
    });

    new cdk.CfnOutput(this, 'ProductionCodeDeployDeploymentGroupName', {
      value: productionDeploymentGroupName,
      description: 'Name of the CodeDeploy deployment group in production account',
    });

    // Slack integration outputs
    new cdk.CfnOutput(this, 'SlackNotificationTopicArn', {
      value: slackNotificationTopic.topicArn,
      description: 'ARN of the SNS topic for Slack notifications',
    });

    new cdk.CfnOutput(this, 'SlackNotificationFunctionName', {
      value: slackNotificationFunction.functionName,
      description: 'Name of the Lambda function for Slack notifications',
    });

    new cdk.CfnOutput(this, 'SlackWebhookSetupInstructions', {
      value: 'To enable Slack notifications, set the SLACK_WEBHOOK_URL environment variable for the Lambda function',
      description: 'Instructions for setting up Slack webhook',
    });
  }

  private createAutoApprovalResources(): { autoApprovalTopic: sns.Topic } {
    const autoApprovalTopic = new sns.Topic(this, 'AutoApprovalTopic', {
      topicName: 'studio-api-pipeline-auto-approval',
      displayName: 'Studio API Pipeline Auto Approval',
    });

    const autoApprovalFunction = new lambda.Function(this, 'AutoApprovalFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      functionName: 'studio-api-auto-approval',
      code: lambda.Code.fromInline(`
        const { CodePipelineClient, PutApprovalResultCommand } = require("@aws-sdk/client-codepipeline");

        exports.handler = async (event) => {
            console.log('Received event:', JSON.stringify(event, null, 2));

            const snsMessage = JSON.parse(event.Records[0].Sns.Message);
            const token = snsMessage.approval.token;
            const pipelineName = snsMessage.approval.pipelineName;
            const stageName = snsMessage.approval.stageName;
            const actionName = snsMessage.approval.actionName;

            const client = new CodePipelineClient({});
            if (pipelineName !== 'studio-api-pipeline') {
                console.log('Received event for non-celery pipeline. Skipping approval.');
                return;
            }

            const command = new PutApprovalResultCommand({
                pipelineName: pipelineName,
                stageName: stageName,
                actionName: actionName,
                result: {
                    status: 'Approved',
                    summary: 'Automatically approved by Lambda.'
                },
                token: token
            });

            try {
                await client.send(command);
                console.log('Approval successful for ' + pipelineName);
            } catch (err) {
                console.error('Approval failed', err);
                throw err;
            }
        };
      `),
      timeout: cdk.Duration.seconds(30),
    });

    autoApprovalTopic.addSubscription(new sns_subscriptions.LambdaSubscription(autoApprovalFunction));

    autoApprovalFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: ['codepipeline:PutApprovalResult'],
      resources: [`arn:aws:codepipeline:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:studio-api-pipeline/*`],
    }));

    return { autoApprovalTopic };
  }

  private createUnitTestProject(): codebuild.Project {
    const unitTestRole = new iam.Role(this, 'UnitTestRole', {
      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
    });

    unitTestRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        resources: [
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*`,
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*:*`
        ],
    }));

    // For private repositories, you would need to set up a source connection or use a secret with a PAT.
    unitTestRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        "ecr:DescribeImages",
        "ecr:GetAuthorizationToken",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability"
      ],
      resources: ["*"]
    }));

    const unitTestProject = new codebuild.Project(this, 'UnitTestProject', {
      projectName: `studio-api-unit-test`,
      role: unitTestRole,
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        privileged: true, // Required for Docker-in-Docker
        computeType: codebuild.ComputeType.MEDIUM,
        environmentVariables: {
          AWS_ACCOUNT_ID: { value: cdk.Aws.ACCOUNT_ID }
        }
      },
      // Source is not needed here as it comes from the pipeline artifact
      buildSpec: codebuild.BuildSpec.fromObjectToYaml({
        version: '0.2',
        phases: {
          pre_build: {
            commands: [
              'echo "Logging in to Amazon ECR..."',
              'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com',
              'echo "Getting image URI from artifact..."',
              'export IMAGE_URI=$(jq -r .ImageURI imageDetail.json)',
              'echo "Image to test is $IMAGE_URI"',
            ]
          },
          build: {
            commands: [
              'echo "Attempting to run the container for 15 seconds..."',
              'timeout 15s docker run $IMAGE_URI > container_run_log.txt 2>&1; EXIT_CODE=$?',
              'echo "--- Container Run Log ---"',
              'cat container_run_log.txt',
              'echo "---"',
              'if [ $EXIT_CODE -eq 124 ]; then echo "Container ran for 15 seconds without crashing (timeout reached). This is a SUCCESS."; else echo "Container exited with code $EXIT_CODE before timeout. Please check logs."; fi',
              'echo "Test step will always succeed, as requested."'
            ],
          },
        },
      }),
      timeout: cdk.Duration.minutes(30),
    });

    return unitTestProject;
  }

  private createE2ETestProject(): codebuild.Project {
    const e2eTestRole = new iam.Role(this, 'E2ETestRole', {
      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
    });

    e2eTestRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        resources: [
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*`,
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*:*`
        ],
    }));

    e2eTestRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "codebuild:BatchPutCodeCoverages",
            "codebuild:BatchPutTestCases",
            "codebuild:CreateReport",
            "codebuild:CreateReportGroup",
            "codebuild:UpdateReport"
        ],
        resources: [`arn:aws:codebuild:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:report-group/*`],
    }));

    // Add S3 permissions for CodePipeline artifacts
    e2eTestRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          's3:GetObject',
          's3:GetObjectVersion',
          's3:PutObject',
          's3:GetBucketLocation',
          's3:ListBucket',
        ],
        resources: [
          'arn:aws:s3:::codepipeline-*',
          'arn:aws:s3:::codepipeline-*/*',
          'arn:aws:s3:::aws-codebuild-*',
          'arn:aws:s3:::aws-codebuild-*/*',
          'arn:aws:s3:::studio-api-e2e-tests-*',
          'arn:aws:s3:::studio-api-e2e-tests-*/*',
        ],
      })
    );

    e2eTestRole.addToPolicy(new iam.PolicyStatement({
      actions: [
          "kms:Decrypt",
          "kms:DescribeKey",
          "kms:Encrypt",
          "kms:GenerateDataKey",
          "kms:ReEncrypt*",
      ],
      resources: ["*"], 
    }));

    // Add SSM parameter access for E2E test configuration
    e2eTestRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        "ssm:GetParameter",
        "ssm:GetParameters"
      ],
      resources: [
        `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/studio-api/e2e-tests/*`
      ]
    }));

    const e2eTestProject = new codebuild.Project(this, 'E2ETestProject', {
      projectName: `studio-api-e2e-test`,
      role: e2eTestRole,
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        privileged: false,
        computeType: codebuild.ComputeType.MEDIUM,
        environmentVariables: {
          TESTING_API_URL: { 
            value: 'https://studio-api.staging.suno.com'  
          },
          SUNO_TEST_EMAIL: {
            value: 'e2e1.test@suno.com'
          },
          SUNO_E2E_TOKEN: {
            value: '42341dba348649958615389fbf20e1aa'
          },
          SUNO_E2E_BASIC_PLAN_TOKEN: {
            value: '992f4f13d6a14630a310651dbf7f54e6'
          },
          E2E_TESTS_S3_BUCKET: {
            value: 'studio-api-e2e-tests-992382411129'  
          },
          E2E_TESTS_S3_KEY: {
            value: 'studio_api.zip'  
          },
        }
      },
      buildSpec: codebuild.BuildSpec.fromObjectToYaml({
        version: '0.2',
        phases: {
          install: {
            'runtime-versions': {
              python: '3.11'
            },
            commands: [
              'echo "Installing uv..."',
              'curl -LsSf https://astral.sh/uv/install.sh | sh',
              'export PATH="$HOME/.local/bin:$PATH"',
              'echo "uv version:"',
              'uv --version'
            ]
          },
          pre_build: {
            commands: [
              'export PATH="$HOME/.local/bin:$PATH"',
              'aws s3 cp s3://${E2E_TESTS_S3_BUCKET}/${E2E_TESTS_S3_KEY} studio-api-e2e-tests.zip',
              'unzip studio-api-e2e-tests.zip',
              'cd studio_api',
              'uv sync --frozen',
              'echo "Dependencies installed successfully"'
            ]
          },
          build: {
            commands: [
              'export PATH="$HOME/.local/bin:$PATH"',
              'export DJANGO_SETTINGS_MODULE="" ',
              'export export PYTHONPATH="." ',
              'export SKIP_CELERY_IMPORT=true',
              'echo "Running E2E tests..."',
              'uv run pytest studio_api/e2e_tests/ -v -rs --tb=short --override-ini="DJANGO_SETTINGS_MODULE="',
              'echo "E2E tests completed"'
            ],
          },
        },
        reports: {
          'e2e-test-results': {
            'file-format': 'JUNITXML',
            'files': ['studio_api/test-results.xml'],
            'base-directory': '.'
          }
        }
      }),
      timeout: cdk.Duration.minutes(10),
    });

    return e2eTestProject;
  }

  private createBuildProject(accountStage: string): codebuild.PipelineProject {
    // Create IAM role for CodeBuild project
    const buildRole = new iam.Role(this, 'BuildRole', {
      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
    });

    // Add necessary permissions to the build role based on the provided policy
    buildRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        resources: [
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*`,
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*:*`
        ],
    }));

    buildRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "codebuild:BatchPutCodeCoverages",
            "codebuild:BatchPutTestCases",
            "codebuild:CreateReport",
            "codebuild:CreateReportGroup",
            "codebuild:UpdateReport"
        ],
        resources: [`arn:aws:codebuild:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:report-group/*`],
    }));

    // Add S3 permissions for CodePipeline artifacts
    buildRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          's3:GetObject',
          's3:GetObjectVersion',
          's3:PutObject',
          's3:GetBucketLocation',
          's3:ListBucket',
        ],
        resources: [
          'arn:aws:s3:::codepipeline-*',
          'arn:aws:s3:::codepipeline-*/*',
          'arn:aws:s3:::aws-codebuild-*',
          'arn:aws:s3:::aws-codebuild-*/*',
        ],
      })
    );

    buildRole.addToPolicy(new iam.PolicyStatement({
      actions: [
          "kms:Decrypt",
          "kms:DescribeKey",
          "kms:Encrypt",
          "kms:GenerateDataKey",
          "kms:ReEncrypt*",
      ],
      // This policy is required to allow CodeBuild to access the S3 artifact bucket if it's encrypted with a KMS key.
      // We are using the bucket's key here.
      resources: ["*"], 
    }));

    buildRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        "sts:AssumeRole"
      ],
      resources: ["*"]
    }));

    // Add ECR permissions for describe-images
    buildRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        "ecr:DescribeImages",
        "ecr:GetAuthorizationToken"
      ],
      resources: ["*"]
    }));

    // Create CodeBuild project
    const buildProject = new codebuild.PipelineProject(this, 'BuildProject', {
      projectName: `studio-api-build`,
      role: buildRole,
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        privileged: false,
        computeType: codebuild.ComputeType.MEDIUM,
        environmentVariables: {
          STAGING_ACCOUNT_ID: {
            value: stagingConfig.accountId,
          },
          PRODUCTION_ACCOUNT_ID: {
            value: productionConfig.accountId,
          },
          TASK_DEFINITION_FAMILY: {
            value: 'BackendInfraStackStudioApiFargateServiceStudioApiTaskDefinition0972B978',
          },
        },
      },
      timeout: cdk.Duration.hours(1),
      buildSpec: codebuild.BuildSpec.fromObjectToYaml({
        version: '0.2',
        env: {
          'exported-variables': [
            'COMMIT_ID',
            'PROD_PREV_COMMIT',
            'STAGING_PREV_COMMIT',
          ],
        },
        phases: {
          build: {
            commands: [
              // Get image URI from artifact from ECR source action
              'IMAGE_URI=$(jq -r .ImageURI imageDetail.json)',
              'echo "Image URI: $IMAGE_URI"',
              
              // Extract image digest from IMAGE_URI (format: repo@sha256:digest)
              'IMAGE_DIGEST=$(echo $IMAGE_URI | sed "s/.*@//")',
              'echo "Image Digest: $IMAGE_DIGEST"',
              
              // Get the actual image tags from ECR using the digest
              'IMAGE_TAGS=$(aws ecr describe-images --repository-name studio-api --image-ids imageDigest=$IMAGE_DIGEST --region $AWS_DEFAULT_REGION --query "imageDetails[0].imageTags" --output text)',
              'echo "Image Tags: $IMAGE_TAGS"',
              
              // Extract commit ID (first tag that is not "latest")
              'COMMIT_ID=""',
              'for tag in $IMAGE_TAGS; do if [ "$tag" != "latest" ]; then COMMIT_ID="$tag"; break; fi; done',
              'echo "Commit ID: $COMMIT_ID"',
              
              // Get first 7 characters of commit ID for DD_VERSION
              'DD_VERSION=$(echo $COMMIT_ID | cut -c1-7)',
              'echo "DD_VERSION (first 7 chars of commit): $DD_VERSION"',

              // Lookup previous prod commit from latest active prod task definition
              'echo "Assuming production role to fetch previous commit..."',
              `PROD_CREDS=$(aws sts assume-role --role-arn arn:aws:iam::$PRODUCTION_ACCOUNT_ID:role/CrossAccount_Codepipeline_Role --role-session-name "prod-prev-commit-lookup")`,
              `export AWS_ACCESS_KEY_ID=$(echo $PROD_CREDS | jq -r .Credentials.AccessKeyId)`,
              `export AWS_SECRET_ACCESS_KEY=$(echo $PROD_CREDS | jq -r .Credentials.SecretAccessKey)`,
              `export AWS_SESSION_TOKEN=$(echo $PROD_CREDS | jq -r .Credentials.SessionToken)`,

              `LATEST_TASK_DEF_ARN=$(aws ecs list-task-definitions --family-prefix $TASK_DEFINITION_FAMILY --status ACTIVE --sort DESC --output json | jq -r '.taskDefinitionArns[0]')`,
              'echo "Latest Prod TaskDef: $LATEST_TASK_DEF_ARN"',
              `TASKDEF_JSON=$(aws ecs describe-task-definition --task-definition $LATEST_TASK_DEF_ARN --output json)`,
              `PROD_TASK_IMAGE=$(echo "$TASKDEF_JSON" | jq -r '.taskDefinition.containerDefinitions[0].image')`,
              'echo "Prod Task Image: $PROD_TASK_IMAGE"',
              `PROD_IMAGE_DIGEST=$(echo $PROD_TASK_IMAGE | sed "s/.*@//")`,
              `PROD_ECR_REGION=$(echo $PROD_TASK_IMAGE | sed -E 's|^.+\\.ecr\\.([a-z0-9-]+)\\.amazonaws\\.com.*|\\1|')`,
              'echo "Prod ECR Region: $PROD_ECR_REGION"',
              'echo "Prod Image Digest: $PROD_IMAGE_DIGEST"',
               
              // Unset production credentials
              'unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN',
               
              // Resolve previous prod commit via ECR tags (fallback to digest if missing)
              `PROD_PREV_COMMIT=$(aws ecr describe-images --repository-name studio-api --image-ids imageDigest=$PROD_IMAGE_DIGEST --region $PROD_ECR_REGION --output json | jq -r '.imageDetails[0].imageTags[]? | select(. != "latest")' | head -n1)`,
              `if [ -z "$PROD_PREV_COMMIT" ]; then PROD_PREV_COMMIT="$PROD_IMAGE_DIGEST"; fi`,
              'echo "Previous prod commit: $PROD_PREV_COMMIT"',
              'export PROD_PREV_COMMIT=$PROD_PREV_COMMIT',

              // Assume staging account role
              `STAGING_CREDS=$(aws sts assume-role --role-arn arn:aws:iam::$STAGING_ACCOUNT_ID:role/CrossAccount_Codepipeline_Role --role-session-name "get-staging-task-def")`,
              `export AWS_ACCESS_KEY_ID=$(echo $STAGING_CREDS | jq -r .Credentials.AccessKeyId)`,
              `export AWS_SECRET_ACCESS_KEY=$(echo $STAGING_CREDS | jq -r .Credentials.SecretAccessKey)`,
              `export AWS_SESSION_TOKEN=$(echo $STAGING_CREDS | jq -r .Credentials.SessionToken)`,

              // Capture previous staging commit from latest active task definition
              `LATEST_STAGING_TASK_DEF_ARN=$(aws ecs list-task-definitions --family-prefix $TASK_DEFINITION_FAMILY --status ACTIVE --sort DESC --output json | jq -r '.taskDefinitionArns[0]')`,
              'echo "Latest Staging TaskDef: $LATEST_STAGING_TASK_DEF_ARN"',
              `STAGING_TASKDEF_JSON=$(aws ecs describe-task-definition --task-definition $LATEST_STAGING_TASK_DEF_ARN --output json)`,
              `STAGING_TASK_IMAGE=$(echo "$STAGING_TASKDEF_JSON" | jq -r '.taskDefinition.containerDefinitions[0].image')`,
              'echo "Staging Task Image: $STAGING_TASK_IMAGE"',
              `STAGING_IMAGE_DIGEST=$(echo $STAGING_TASK_IMAGE | sed "s/.*@//")`,
              `STAGING_ECR_REGION=$(echo $STAGING_TASK_IMAGE | sed -E 's|^.+\\.ecr\\.([a-z0-9-]+)\\.amazonaws\\.com.*|\\1|')`,
              'echo "Staging ECR Region: $STAGING_ECR_REGION"',
              'echo "Staging Image Digest: $STAGING_IMAGE_DIGEST"',

              // Temporarily drop staging creds to query dev ECR for tag
              'unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN',
              `STAGING_PREV_COMMIT=$(aws ecr describe-images --repository-name studio-api --image-ids imageDigest=$STAGING_IMAGE_DIGEST --region $STAGING_ECR_REGION --output json | jq -r '.imageDetails[0].imageTags[]? | select(. != "latest")' | head -n1)`,
              `if [ -z "$STAGING_PREV_COMMIT" ]; then STAGING_PREV_COMMIT="$STAGING_IMAGE_DIGEST"; fi`,
              'echo "Previous staging commit: $STAGING_PREV_COMMIT"',
              'export STAGING_PREV_COMMIT=$STAGING_PREV_COMMIT',

              // Re-assume staging creds to build taskdef.json
              `export AWS_ACCESS_KEY_ID=$(echo $STAGING_CREDS | jq -r .Credentials.AccessKeyId)`,
              `export AWS_SECRET_ACCESS_KEY=$(echo $STAGING_CREDS | jq -r .Credentials.SecretAccessKey)`,
              `export AWS_SESSION_TOKEN=$(echo $STAGING_CREDS | jq -r .Credentials.SessionToken)`,
               
              // Get staging task definition and update both image and DD_VERSION
              `aws ecs describe-task-definition --task-definition $TASK_DEFINITION_FAMILY --region $AWS_DEFAULT_REGION | jq --arg image_uri "$IMAGE_URI" --arg dd_version "$DD_VERSION" '.taskDefinition | .containerDefinitions[0].image = $image_uri | .containerDefinitions[0].environment |= map(if .name == "DD_VERSION" then .value = $dd_version else . end) | if (.containerDefinitions[0].environment | map(.name) | contains(["DD_VERSION"]) | not) then .containerDefinitions[0].environment += [{"name": "DD_VERSION", "value": $dd_version}] else . end | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy)' > taskdef.json`,
               
              // Unset staging credentials
              'unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN',

              // Create staging appspec
              `cat << EOF > appspec.yaml
version: 1
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "<TASK_DEFINITION>"
        LoadBalancerInfo:
          ContainerName: "StudioApiContainer1"
          ContainerPort: 8005
Hooks:
  - BeforeInstall: "DbMigrationFunctionBeforeInstall"
  # - BeforeAllowTraffic: "studio-api-pre-traffic-hook"
EOF`,
            ],
          },
        },
        artifacts: {
          'exported-variables': [
            'COMMIT_ID',
            'PROD_PREV_COMMIT',
            'STAGING_PREV_COMMIT',
          ],
          files: ['taskdef.json', 'appspec.yaml'],
        },
      }),
    });

    return buildProject;
  }

  private createProductionBuildProject(accountStage: string): codebuild.PipelineProject {
    // Create IAM role for Production CodeBuild project
    const productionBuildRole = new iam.Role(this, 'ProductionBuildRole', {
      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
    });

    // Add necessary permissions to the production build role
    productionBuildRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        resources: [
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*`,
            `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/codebuild/*:*`
        ],
    }));

    productionBuildRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "codebuild:BatchPutCodeCoverages",
            "codebuild:BatchPutTestCases",
            "codebuild:CreateReport",
            "codebuild:CreateReportGroup",
            "codebuild:UpdateReport"
        ],
        resources: [`arn:aws:codebuild:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:report-group/*`],
    }));

    // Add S3 permissions for CodePipeline artifacts
    productionBuildRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          's3:GetObject',
          's3:GetObjectVersion',
          's3:PutObject',
          's3:GetBucketLocation',
          's3:ListBucket',
        ],
        resources: [
          'arn:aws:s3:::codepipeline-*',
          'arn:aws:s3:::codepipeline-*/*',
          'arn:aws:s3:::aws-codebuild-*',
          'arn:aws:s3:::aws-codebuild-*/*',
        ],
      })
    );

    productionBuildRole.addToPolicy(new iam.PolicyStatement({
      actions: [
          "kms:Decrypt",
          "kms:DescribeKey",
          "kms:Encrypt",
          "kms:GenerateDataKey",
          "kms:ReEncrypt*",
      ],
      resources: ["*"], 
    }));

    productionBuildRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        "sts:AssumeRole"
      ],
      resources: ["*"]
    }));

    // Add ECR permissions for describe-images
    productionBuildRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        "ecr:DescribeImages",
        "ecr:GetAuthorizationToken"
      ],
      resources: ["*"]
    }));

    // Create Production CodeBuild project
    const productionBuildProject = new codebuild.PipelineProject(this, 'ProductionBuildProject', {
      projectName: `studio-api-prod-build`,
      role: productionBuildRole,
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        privileged: false,
        computeType: codebuild.ComputeType.MEDIUM,
        environmentVariables: {
          PRODUCTION_ACCOUNT_ID: {
            value: productionConfig.accountId,
          },
          TASK_DEFINITION_FAMILY: {
            value: 'BackendInfraStackStudioApiFargateServiceStudioApiTaskDefinition0972B978',
          },
        },
      },
      timeout: cdk.Duration.hours(1),
      buildSpec: codebuild.BuildSpec.fromObjectToYaml({
        version: '0.2',
        env: {
          'exported-variables': [
            'COMMIT_ID',
          ],
        },
        phases: {
          build: {
            commands: [
              // Get image URI from previous build artifact
              'IMAGE_URI=$(jq -r .ImageURI imageDetail.json)',
              'echo "Image URI: $IMAGE_URI"',
              
              // Extract image digest from IMAGE_URI (format: repo@sha256:digest)
              'IMAGE_DIGEST=$(echo $IMAGE_URI | sed "s/.*@//")',
              'echo "Image Digest: $IMAGE_DIGEST"',
              
              // Get the actual image tags from ECR using the digest
              'IMAGE_TAGS=$(aws ecr describe-images --repository-name studio-api --image-ids imageDigest=$IMAGE_DIGEST --region $AWS_DEFAULT_REGION --query "imageDetails[0].imageTags" --output text)',
              'echo "Image Tags: $IMAGE_TAGS"',
              
              // Extract commit ID (first tag that is not "latest")
              'COMMIT_ID=""',
              'for tag in $IMAGE_TAGS; do if [ "$tag" != "latest" ]; then COMMIT_ID="$tag"; break; fi; done',
              'echo "Commit ID: $COMMIT_ID"',
              
              // Get first 7 characters of commit ID for DD_VERSION
              'DD_VERSION=$(echo $COMMIT_ID | cut -c1-7)',
              'echo "DD_VERSION (first 7 chars of commit): $DD_VERSION"',

              // Assume production account role
              `PROD_CREDS=$(aws sts assume-role --role-arn arn:aws:iam::$PRODUCTION_ACCOUNT_ID:role/CrossAccount_Codepipeline_Role --role-session-name "get-prod-task-def")`,
              `export AWS_ACCESS_KEY_ID=$(echo $PROD_CREDS | jq -r .Credentials.AccessKeyId)`,
              `export AWS_SECRET_ACCESS_KEY=$(echo $PROD_CREDS | jq -r .Credentials.SecretAccessKey)`,
              `export AWS_SESSION_TOKEN=$(echo $PROD_CREDS | jq -r .Credentials.SessionToken)`,
              
              // Get production task definition and update both image and DD_VERSION
              `aws ecs describe-task-definition --task-definition $TASK_DEFINITION_FAMILY --region $AWS_DEFAULT_REGION | jq --arg image_uri "$IMAGE_URI" --arg dd_version "$DD_VERSION" '.taskDefinition | .containerDefinitions[0].image = $image_uri | .containerDefinitions[0].environment |= map(if .name == "DD_VERSION" then .value = $dd_version else . end) | if (.containerDefinitions[0].environment | map(.name) | contains(["DD_VERSION"]) | not) then .containerDefinitions[0].environment += [{"name": "DD_VERSION", "value": $dd_version}] else . end | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy)' > taskdef.json`,
              
              // Unset production credentials
              'unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN',

              // Create production appspec
              `cat << EOF > appspec.yaml
version: 1
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "<TASK_DEFINITION>"
        LoadBalancerInfo:
          ContainerName: "StudioApiContainer1"
          ContainerPort: 8005
Hooks:
  - BeforeInstall: "DbMigrationFunctionBeforeInstall"
  - AfterAllowTraffic: "AfterAllowTrafficNotifier"  
  # - BeforeAllowTraffic: "studio-api-pre-traffic-hook"
EOF`,
            ],
          },
        },
        artifacts: {
          files: ['taskdef.json', 'appspec.yaml'],
        },
      }),
    });

    return productionBuildProject;
  }

  private createSlackNotificationResources(): { slackNotificationTopic: sns.Topic, slackNotificationFunction: lambda.Function } {
    // Create SNS topic for pipeline notifications
    const slackNotificationTopic = new sns.Topic(this, 'SlackNotificationTopic', {
      topicName: 'studio-api-pipeline-notifications',
      displayName: 'Studio API Pipeline Notifications',
    });

    // Create Lambda function for Slack notifications
    const slackNotificationFunction = new lambda.Function(this, 'SlackNotificationFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      functionName: 'studio-api-slack-notification',
      code: lambda.Code.fromAsset('lambda/slack-notification'),
      environment: {
        SLACK_WEBHOOK_URL: 'https://hooks.slack.com/services/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'
      },
      timeout: cdk.Duration.seconds(30),
    });

    // Subscribe Lambda to SNS topic
    slackNotificationTopic.addSubscription(new sns_subscriptions.LambdaSubscription(slackNotificationFunction));

    // Grant SNS permission to invoke Lambda
    slackNotificationFunction.addPermission('AllowSNSInvoke', {
      principal: new iam.ServicePrincipal('sns.amazonaws.com'),
      sourceArn: slackNotificationTopic.topicArn,
    });

    return { slackNotificationTopic, slackNotificationFunction };
  }

  private createApprovalNotifierResources(): void {
    const approvalNotifierFunction = new lambda.Function(this, 'ApprovalNotifierFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      functionName: 'studio-api-approval-notifier',
      timeout: cdk.Duration.seconds(60),
      environment: {
        SLACK_WEBHOOK_URL: 'https://hooks.slack.com/services/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'
      },
      code: lambda.Code.fromAsset('lambda/approval-notifier', {
        bundling: {
          image: lambda.Runtime.NODEJS_18_X.bundlingImage,
          command: [
            'bash', '-c',
            'npm install --cache /tmp/.npm && cp -r . /asset-output'
          ],
        },
      }),
    });

    // Add CodePipeline permission to the Lambda role
    approvalNotifierFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: ['codepipeline:ListActionExecutions'],
      resources: ['*'], // Be more specific for production
    }));

    // Create EventBridge rule to trigger on approval stage completion
    const approvalRule = new cdk.aws_events.Rule(this, 'ApprovalCompletionRule', {
      eventPattern: {
        source: ['aws.codepipeline'],
        detailType: ['CodePipeline Stage Execution State Change'],
        detail: {
          pipeline: ['studio-api-pipeline'],
          stage: ['Manual-Approval'],
          state: ['SUCCEEDED', 'FAILED'],
        },
      },
    });

    // Add Lambda as a target for the rule
    approvalRule.addTarget(new cdk.aws_events_targets.LambdaFunction(approvalNotifierFunction));
  }

  private createAutoRejectorResources(): void {
    const autoRejectorFunction = new lambda.Function(this, 'AutoRejectorFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      functionName: 'studio-api-auto-rejector',
      timeout: cdk.Duration.seconds(60),
      code: lambda.Code.fromAsset('lambda/auto-reject-manual-approval', {
        bundling: {
          image: lambda.Runtime.NODEJS_18_X.bundlingImage,
          command: [
            'bash', '-c',
            'npm install --cache /tmp/.npm && cp -r . /asset-output'
          ],
        },
      }),
    });

    // Add CodePipeline permission to the Lambda role
    autoRejectorFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: [
        'codepipeline:ListPipelineExecutions',
        'codepipeline:GetPipelineState',
        'codepipeline:PutApprovalResult'
      ],
      resources: [`arn:aws:codepipeline:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:studio-api-pipeline/*`, `arn:aws:codepipeline:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:studio-api-pipeline`], 
    }));

    // Create EventBridge rule to trigger on staging deployment success
    const stagingSuccessRule = new cdk.aws_events.Rule(this, 'StagingDeploySuccessRule', {
      eventPattern: {
        source: ['aws.codepipeline'],
        detailType: ['CodePipeline Stage Execution State Change'],
        detail: {
          pipeline: ['studio-api-pipeline'],
          stage: ['Staging-Deploy'],
          state: ['SUCCEEDED'],
        },
      },
    });

    // Add Lambda as a target for the rule
    stagingSuccessRule.addTarget(new cdk.aws_events_targets.LambdaFunction(autoRejectorFunction));
  }

  private createPipeline(
    sourceRepository: ecr.IRepository,
    buildProject: codebuild.PipelineProject,
    productionBuildProject: codebuild.PipelineProject,
    stagingCodeDeployApplicationName: string,
    stagingCodeDeployDeploymentGroupName: string,
    productionCodeDeployApplicationName: string,
    productionCodeDeployDeploymentGroupName: string,
    slackNotificationTopic: sns.Topic,
    autoApprovalTopic: sns.Topic,
    unitTestProject: codebuild.PipelineProject,
    e2eTestProject: codebuild.PipelineProject,
  ): codepipeline.Pipeline {
    // Create IAM role for Pipeline
    const pipelineRole = new iam.Role(this, 'PipelineRole', {
      assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
    });

    // Policy for S3 artifact access
    pipelineRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "s3:GetObject",
            "s3:GetObjectVersion",
            "s3:PutObject",
            "s3:GetBucketACL",
            "s3:GetBucketLocation",
            "s3:ListBucket"
        ],
        resources: [
            "arn:aws:s3:::codepipeline-*/*",
            "arn:aws:s3:::codepipeline-*"
        ]
    }));

    // Policy for KMS access for artifact encryption
    pipelineRole.addToPolicy(new iam.PolicyStatement({
        actions: [
            "kms:Decrypt",
            "kms:Encrypt",
            "kms:ReEncrypt*",
            "kms:GenerateDataKey*",
            "kms:DescribeKey"
        ],
        resources: ["*"] // For production, you should restrict this to the specific KMS keys used by CodePipeline.
    }));

    // The CodeDeploy action uses a cross-account role directly, so a dedicated role here is not needed.

    // Create dedicated role for CodeBuild action with static policies
    const codeBuildActionRole = new iam.Role(this, 'CodeBuildActionRole', {
      assumedBy: pipelineRole,
    });

    // Add comprehensive static policies for CodeBuild action
    codeBuildActionRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          'codebuild:StartBuild',
          'codebuild:BatchGetBuilds',
          "codebuild:StopBuild"
        ],
        resources: ['*'],
      })
    );

    // Create dedicated role for Production CodeBuild action
    const productionCodeBuildActionRole = new iam.Role(this, 'ProdCodeBuildActionRole', {
      assumedBy: pipelineRole,
    });

    // Add policies for Production CodeBuild action
    productionCodeBuildActionRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          'codebuild:StartBuild',
          'codebuild:BatchGetBuilds',
          "codebuild:StopBuild"
        ],
        resources: ['*'],
      })
    );

    // Create dedicated role for ECR Source action with static policies
    const ecrSourceRole = new iam.Role(this, 'EcrSourceRole', {
      assumedBy: pipelineRole,
    });

    // Add comprehensive static policies for ECR Source action
    ecrSourceRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          'ecr:GetDownloadUrlForLayer',
          'ecr:BatchGetImage',
          'ecr:DescribeImages'
        ],
        resources: [sourceRepository.repositoryArn],
      })
    );
    ecrSourceRole.addToPolicy(new iam.PolicyStatement({
        actions: [
          "s3:PutObject",
          "s3:PutObjectLegalHold",
          "s3:PutObjectRetention",
          "s3:PutObjectTagging",
          "s3:PutObjectVersionTagging"
        ],
        resources: ["arn:aws:s3:::codepipeline-*/*"]
    }));
    ecrSourceRole.addToPolicy(new iam.PolicyStatement({
      actions: [
          "kms:Decrypt",
          "kms:Encrypt",
          "kms:ReEncrypt*",
          "kms:GenerateDataKey*",
          "kms:DescribeKey"
      ],
      resources: ["*"] // For production, you should restrict this to the specific KMS keys used by CodePipeline.
  }));

      // Create dedicated role for test action
    const testActionRole = new iam.Role(this, 'TestActionRole', {
      assumedBy: pipelineRole,
    });

    testActionRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ['codebuild:StartBuild', 'codebuild:BatchGetBuilds'],
        resources: [unitTestProject.projectArn],
      })
    );

    // Create dedicated role for e2e test action
    const e2eTestActionRole = new iam.Role(this, 'E2ETestActionRole', {
      assumedBy: pipelineRole,
    });

    e2eTestActionRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ['codebuild:StartBuild', 'codebuild:BatchGetBuilds'],
        resources: [e2eTestProject.projectArn],
      })
    );

    // Add AssumeRole permissions for action-specific roles
    pipelineRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ['sts:AssumeRole'],
        resources: [
          ecrSourceRole.roleArn,
          codeBuildActionRole.roleArn,
          productionCodeBuildActionRole.roleArn,
          testActionRole.roleArn,
          e2eTestActionRole.roleArn,
          'arn:aws:iam::590183763515:role/CrossAccount_Codepipeline_Role',
          'arn:aws:iam::734185074900:role/CrossAccount_Codepipeline_Role'
        ]
      })
    );

    // Create Pipeline
    const pipeline = new codepipeline.Pipeline(this, 'Pipeline', {
      pipelineName: 'studio-api-pipeline',
      role: pipelineRole,
      crossAccountKeys: true, // Enable cross-account deployment
      pipelineType: codepipeline.PipelineType.V2, // Use V2 pipeline type for QUEUED execution
      executionMode: codepipeline.ExecutionMode.SUPERSEDED, // Explicitly set SUPERSEDED execution mode
      stages: [
        {
          stageName: 'Source',
          actions: [
            new codepipeline_actions.EcrSourceAction({
              actionName: 'Source',
              repository: sourceRepository,
              imageTag: 'latest',
              output: new codepipeline.Artifact('SourceArtifact'),
              role: ecrSourceRole,
            }),
          ],
        },
        {
          stageName: 'Build',
          actions: [
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Build',
              project: buildProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              outputs: [new codepipeline.Artifact('BuildArtifact')],
              role: codeBuildActionRole,
              variablesNamespace: 'BuildVariables',
              runOrder: 1,
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'CommitId',
              additionalInformation: `Building commit: #{BuildVariables.COMMIT_ID}`,
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/commit/#{BuildVariables.COMMIT_ID}',
              notificationTopic: autoApprovalTopic,
              runOrder: 2,
            }),
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Unit-Test',
              project: unitTestProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              role: testActionRole,
              environmentVariables: {
                'COMMIT_ID': { value: '#{BuildVariables.COMMIT_ID}' }
              },
              runOrder: 2,
            }),
          ],
        },
        {
          stageName: 'Staging-Deploy',
          actions: [
            new codepipeline_actions.CodeDeployEcsDeployAction({
              actionName: 'Staging-Deploy',
              deploymentGroup: codedeploy.EcsDeploymentGroup.fromEcsDeploymentGroupAttributes(this, 'StagingDeploymentGroup', {
                application: codedeploy.EcsApplication.fromEcsApplicationName(this, 'StagingApplication', stagingCodeDeployApplicationName),
                deploymentGroupName: stagingCodeDeployDeploymentGroupName,
              }),
              appSpecTemplateInput: new codepipeline.Artifact('BuildArtifact'),
              taskDefinitionTemplateInput: new codepipeline.Artifact('BuildArtifact'),
              role: iam.Role.fromRoleArn(this, 'CrossAccountRole', 'arn:aws:iam::590183763515:role/CrossAccount_Codepipeline_Role'),
              runOrder: 1,
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'Staging-CommitId',
              additionalInformation: `Approving deployment to staging for commit: #{BuildVariables.COMMIT_ID}`,
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/commit/#{BuildVariables.COMMIT_ID}',
              notificationTopic: autoApprovalTopic,
              runOrder: 1,
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'Staging-CommitDiff',
              additionalInformation: `Diff vs prev staging: #{BuildVariables.STAGING_PREV_COMMIT}...#{BuildVariables.COMMIT_ID}`,
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/compare/#{BuildVariables.STAGING_PREV_COMMIT}...#{BuildVariables.COMMIT_ID}',
              notificationTopic: autoApprovalTopic,
              runOrder: 1,
            }),
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Staging-E2E-Tests',
              project: e2eTestProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              role: e2eTestActionRole,
              runOrder: 2,
            }),
          ],
        },
        {
          stageName: 'Manual-Approval',
          actions: [
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'Manual-Approval',
              additionalInformation: `Please review staging deployment and approve for production deployment. Commit: #{BuildVariables.COMMIT_ID}`,
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/compare/#{BuildVariables.PROD_PREV_COMMIT}...#{BuildVariables.COMMIT_ID}',
              notificationTopic: slackNotificationTopic,
            }),
          ],
        },
        {
          stageName: 'Production-Build',
          actions: [
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Production-Build',
              project: productionBuildProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              outputs: [new codepipeline.Artifact('ProductionBuildArtifact')],
              role: productionCodeBuildActionRole,
            }),
          ],
        },
        {
          stageName: 'Prod-Deploy',
          actions: [
            new codepipeline_actions.CodeDeployEcsDeployAction({
              actionName: 'Prod-Deploy',
              deploymentGroup: codedeploy.EcsDeploymentGroup.fromEcsDeploymentGroupAttributes(this, 'ProductionDeploymentGroup', {
                application: codedeploy.EcsApplication.fromEcsApplicationName(this, 'ProductionApplication', productionCodeDeployApplicationName),
                deploymentGroupName: productionCodeDeployDeploymentGroupName,
              }),
              appSpecTemplateInput: new codepipeline.Artifact('ProductionBuildArtifact'),
              taskDefinitionTemplateInput: new codepipeline.Artifact('ProductionBuildArtifact'),
              role: iam.Role.fromRoleArn(this, 'CrossAccountProdRole', 'arn:aws:iam::734185074900:role/CrossAccount_Codepipeline_Role'),
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'Production-CommitId',
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/commit/#{BuildVariables.COMMIT_ID}',
              notificationTopic: autoApprovalTopic,
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'Production-CommitDiff',
              additionalInformation: `Diff vs prev prod: #{BuildVariables.PROD_PREV_COMMIT}...#{BuildVariables.COMMIT_ID}`,
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/compare/#{BuildVariables.PROD_PREV_COMMIT}...#{BuildVariables.COMMIT_ID}',
              notificationTopic: autoApprovalTopic,
            }),
          ],
        },
      ],
    });

    return pipeline;
  }
} 