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 ecs from 'aws-cdk-lib/aws-ecs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as sns_subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import { Construct } from 'constructs';
import { stagingConfig, stagingResources, productionConfig, productionResources } from './celery-config';
import { stagingConfig as beatStagingConfig, stagingResources as beatStagingResources, productionConfig as beatProductionConfig, productionResources as beatProductionResources } from './celery-beat-config';

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

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

    const accountStage = props.accountStage;

    const sourceRepository = ecr.Repository.fromRepositoryAttributes(this, 'CelerySourceRepository', {
      repositoryArn: `arn:aws:ecr:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:repository/studio-api`,
      repositoryName: 'studio-api',
    });

    const stagingBuildProject = this.createBuildProject();
    const prodBuildProject = this.createProductionBuildProject();

    // Additional build projects for celery-beat
    const beatStagingBuildProject = this.createBeatBuildProject();
    const beatProdBuildProject = this.createBeatProductionBuildProject();

    const { autoApprovalTopic } = this.createAutoApprovalResources();

    const pipeline = this.createPipeline(
      sourceRepository,
      stagingBuildProject,
      prodBuildProject,
      beatStagingBuildProject,
      beatProdBuildProject,
      autoApprovalTopic,
    );

    new cdk.CfnOutput(this, 'CeleryPipelineName', {
      value: pipeline.pipelineName,
      description: 'Name of the Celery CodePipeline',
    });
  }

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

    const autoApprovalFunction = new lambda.Function(this, 'AutoApprovalFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      functionName: 'celery-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 !== 'celery-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}:celery-pipeline/*`],
    }));

    return { autoApprovalTopic };
  }

  private createBuildProject(): codebuild.PipelineProject {
    // Create IAM role for CodeBuild project
    const buildRole = new iam.Role(this, 'CeleryBuildRole', {
      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: `celery-build`,
      role: buildRole,
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        privileged: false,
        computeType: codebuild.ComputeType.MEDIUM,
        environmentVariables: {
          STAGING_ACCOUNT_ID: {
            value: stagingConfig.accountId,
          },
          TASK_DEFINITION_FAMILY: {
            value: stagingConfig.taskDefinitionFamily,
          },
            CONTAINER_NAME: {
              value: stagingConfig.containerName,
            },
        },
      },
      timeout: cdk.Duration.hours(1),
      buildSpec: codebuild.BuildSpec.fromObjectToYaml({
        version: '0.2',
        env: {
          'exported-variables': [
            'COMMIT_ID',
          ],
        },
        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"',

              // Prepare imagedefinitions.json for ECS deploy action (container name must match task definition)
              `echo '[{"name":"'"$CONTAINER_NAME"'","imageUri":"'"$IMAGE_URI"'"}]' > imagedefinitions.json`,
              
              // 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)`,
              
              // Get staging task definition and only update the image
              `aws ecs describe-task-definition --task-definition $TASK_DEFINITION_FAMILY --region $AWS_DEFAULT_REGION | jq --arg image_uri "$IMAGE_URI" '.taskDefinition | .containerDefinitions[0].image = $image_uri | 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',
            ],
          },
        },
        artifacts: {
          'exported-variables': [
            'COMMIT_ID',
          ],
          files: ['taskdef.json', 'imagedefinitions.json'],
        },
      }),
    });

    return buildProject;
  }

  private createProductionBuildProject(): codebuild.PipelineProject {
    // Create IAM role for Production CodeBuild project
    const productionBuildRole = new iam.Role(this, 'CeleryProductionBuildRole', {
      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: `celery-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: productionConfig.taskDefinitionFamily,
          },
            CONTAINER_NAME: {
              value: productionConfig.containerName,
            },
        },
      },
      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"',
              
              // 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 only update the image
              `aws ecs describe-task-definition --task-definition $TASK_DEFINITION_FAMILY --region $AWS_DEFAULT_REGION | jq --arg image_uri "$IMAGE_URI" '.taskDefinition | .containerDefinitions[0].image = $image_uri | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy)' > taskdef.json`,

              // Prepare imagedefinitions.json for ECS deploy action
              `echo '[{"name":"'"$CONTAINER_NAME"'","imageUri":"'"$IMAGE_URI"'"}]' > imagedefinitions.json`,
              
              // Unset production credentials
              'unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN',
            ],
          },
        },
        artifacts: {
          files: ['taskdef.json', 'imagedefinitions.json'],
        },
      }),
    });

    return productionBuildProject;
  }

  private createBeatBuildProject(): codebuild.PipelineProject {
    const role = new iam.Role(this, 'BeatBuildRole', { assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com') });
    role.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/*:*`] }));
    role.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-*/*'] }));
    role.addToPolicy(new iam.PolicyStatement({ actions: ['kms:Decrypt','kms:DescribeKey','kms:Encrypt','kms:GenerateDataKey','kms:ReEncrypt*'], resources: ['*'] }));
    role.addToPolicy(new iam.PolicyStatement({ actions: ['ecr:DescribeImages','ecr:GetAuthorizationToken'], resources: ['*'] }));
    role.addToPolicy(new iam.PolicyStatement({ actions: ['sts:AssumeRole'], resources: ['*'] }));
    role.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/*`],
  }));

    return new codebuild.PipelineProject(this, 'BeatBuildProject', {
      projectName: `celery-beat-build`,
      role,
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        privileged: false,
        computeType: codebuild.ComputeType.MEDIUM,
        environmentVariables: {
          STAGING_ACCOUNT_ID: { value: beatStagingConfig.accountId },
          TASK_DEFINITION_FAMILY: { value: beatStagingConfig.taskDefinitionFamily },
          CONTAINER_NAME: { value: beatStagingConfig.containerName },
        },
      },
      timeout: cdk.Duration.hours(1),
      buildSpec: codebuild.BuildSpec.fromObjectToYaml({
        version: '0.2',
        env: {
          'exported-variables': [
            'COMMIT_ID',
          ],
        },
        phases: {
          build: {
            commands: [
              'IMAGE_URI=$(jq -r .ImageURI imageDetail.json)',
              'echo "Image URI: $IMAGE_URI"',
              'IMAGE_DIGEST=$(echo $IMAGE_URI | sed "s/.*@//")',
              '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)',
              'COMMIT_ID=""; for tag in $IMAGE_TAGS; do if [ "$tag" != "latest" ]; then COMMIT_ID="$tag"; break; fi; done',
              'echo "Commit ID: $COMMIT_ID"',
              `echo '[{"name":"'"$CONTAINER_NAME"'","imageUri":"'"$IMAGE_URI"'"}]' > imagedefinitions.json`,
              `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)`,
              `aws ecs describe-task-definition --task-definition $TASK_DEFINITION_FAMILY --region $AWS_DEFAULT_REGION | jq --arg image_uri "$IMAGE_URI" '.taskDefinition | .containerDefinitions[0].image = $image_uri | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy)' > taskdef.json`,
              'unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN',
            ],
          },
        },
        artifacts: {
          'exported-variables': [
            'COMMIT_ID',
          ],
          files: ['taskdef.json', 'imagedefinitions.json'],
        },
      }),
    });
  }

  private createBeatProductionBuildProject(): codebuild.PipelineProject {
    const role = new iam.Role(this, 'BeatProdBuildRole', { assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com') });
    role.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/*:*`] }));
    role.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-*/*'] }));
    role.addToPolicy(new iam.PolicyStatement({ actions: ['kms:Decrypt','kms:DescribeKey','kms:Encrypt','kms:GenerateDataKey','kms:ReEncrypt*'], resources: ['*'] }));
    role.addToPolicy(new iam.PolicyStatement({ actions: ['ecr:DescribeImages','ecr:GetAuthorizationToken'], resources: ['*'] }));
    role.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/*`],
    }));
    role.addToPolicy(new iam.PolicyStatement({
      actions: [
        "sts:AssumeRole"
      ],
      resources: ["*"]
    }));

    return new codebuild.PipelineProject(this, 'BeatProdBuildProject', {
      projectName: `celery-beat-prod-build`,
      role,
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
        privileged: false,
        computeType: codebuild.ComputeType.MEDIUM,
        environmentVariables: {
          PRODUCTION_ACCOUNT_ID: { value: beatProductionConfig.accountId },
          TASK_DEFINITION_FAMILY: { value: beatProductionConfig.taskDefinitionFamily },
          CONTAINER_NAME: { value: beatProductionConfig.containerName },
        },
      },
      timeout: cdk.Duration.hours(1),
      buildSpec: codebuild.BuildSpec.fromObjectToYaml({
        version: '0.2',
        env: {
          'exported-variables': [
            'COMMIT_ID',
          ],
        },
        phases: {
          build: {
            commands: [
              'IMAGE_URI=$(jq -r .ImageURI imageDetail.json)',
              'echo "Image URI: $IMAGE_URI"',
              'IMAGE_DIGEST=$(echo $IMAGE_URI | sed "s/.*@//")',
              '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)',
              'COMMIT_ID=""; for tag in $IMAGE_TAGS; do if [ "$tag" != "latest" ]; then COMMIT_ID="$tag"; break; fi; done',
              'echo "Commit ID: $COMMIT_ID"',
              `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)`,
              `aws ecs describe-task-definition --task-definition $TASK_DEFINITION_FAMILY --region $AWS_DEFAULT_REGION | jq --arg image_uri "$IMAGE_URI" '.taskDefinition | .containerDefinitions[0].image = $image_uri | del(.taskDefinitionArn) | del(.revision) | del(.status) | del(.compatibilities) | del(.registeredAt) | del(.registeredBy)' > taskdef.json`,
              `echo '[{"name":"'"$CONTAINER_NAME"'","imageUri":"'"$IMAGE_URI"'"}]' > imagedefinitions.json`,
              'unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN',
            ],
          },
        },
        artifacts: { files: ['taskdef.json', 'imagedefinitions.json'] },
      }),
    });
  }

  private createPipeline(
    sourceRepository: ecr.IRepository,
    stagingBuildProject: codebuild.PipelineProject,
    prodBuildProject: codebuild.PipelineProject,
    beatStagingBuildProject: codebuild.PipelineProject,
    beatProdBuildProject: codebuild.PipelineProject,
    autoApprovalTopic: sns.Topic,
  ): codepipeline.Pipeline {
    const pipelineRole = new iam.Role(this, 'CeleryPipelineRole', {
      assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
    });

    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-*',
      ],
    }));

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

    const buildActionRole = new iam.Role(this, 'CeleryBuildActionRole', {
      assumedBy: pipelineRole,
    });
    buildActionRole.addToPolicy(new iam.PolicyStatement({
      actions: ['codebuild:StartBuild', 'codebuild:BatchGetBuilds', 'codebuild:StopBuild'],
      resources: ['*'],
    }));

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

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

    const deployActionRole = new iam.Role(this, 'CeleryDeployActionRole', {
      assumedBy: pipelineRole,
    });
    deployActionRole.addToPolicy(new iam.PolicyStatement({
      actions: ['codebuild:StartBuild', 'codebuild:BatchGetBuilds', 'codebuild:StopBuild'],
      resources: ['*'],
    }));

    const ecrSourceRole = new iam.Role(this, 'CeleryEcrSourceRole', {
      assumedBy: pipelineRole,
    });

    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: ['*'],
    }));

    pipelineRole.addToPolicy(new iam.PolicyStatement({
      actions: ['sts:AssumeRole'],
      resources: [
        ecrSourceRole.roleArn,
        buildActionRole.roleArn,
        prodBuildActionRole.roleArn,
        deployActionRole.roleArn,
        'arn:aws:iam::590183763515:role/CrossAccount_Codepipeline_Role',
        'arn:aws:iam::734185074900:role/CrossAccount_Codepipeline_Role'
      ],
    }));

    const pipeline = new codepipeline.Pipeline(this, 'CeleryPipeline', {
      pipelineName: 'celery-pipeline',
      role: pipelineRole,
      crossAccountKeys: true,
      pipelineType: codepipeline.PipelineType.V2,
      executionMode: codepipeline.ExecutionMode.SUPERSEDED,
      stages: [
        {
          stageName: 'Source',
          actions: [
            new codepipeline_actions.EcrSourceAction({
              actionName: 'Source',
              repository: sourceRepository,
              imageTag: 'latest',
              output: new codepipeline.Artifact('SourceArtifact'),
              role: ecrSourceRole,
            }),
          ],
        },
        {
          stageName: 'Celery-Build',
          actions: [
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Celery-Build',
              project: stagingBuildProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              outputs: [new codepipeline.Artifact('StagingBuildArtifact')],
              role: buildActionRole,
              variablesNamespace: 'BuildVariables',
              runOrder: 1,
            }),
          ],
        },
        {
          stageName: 'Celery-Staging-Deploy',
          actions: [
            // Use ECS Deploy Action (imagedefinitions.json) for staging
            new codepipeline_actions.EcsDeployAction({
              actionName: 'Celery-Staging-Deploy',
              input: new codepipeline.Artifact('StagingBuildArtifact'),
              service: ecs.BaseService.fromServiceArnWithCluster(
                this,
                'StagingCeleryServiceRef',
                `arn:aws:ecs:${cdk.Aws.REGION}:${stagingConfig.accountId}:service/${stagingResources.clusterName}/${stagingConfig.serviceName}`
              ),
              role: iam.Role.fromRoleArn(
                this,
                'StagingEcsDeployCrossAccountRole',
                `arn:aws:iam::${stagingConfig.accountId}:role/CrossAccount_Codepipeline_Role`
              ),
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'Celery-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,
            }),
          ],
        },
        {
          stageName: 'CeleryBeat-Build',
          actions: [
            new codepipeline_actions.CodeBuildAction({
              actionName: 'CeleryBeat-Build',
              project: beatStagingBuildProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              outputs: [new codepipeline.Artifact('StagingBeatBuildArtifact')],
              role: buildActionRole,
              runOrder: 1,
            }),
          ],
        },
        {
          stageName: 'CeleryBeat-Staging-Deploy',
          actions: [
            new codepipeline_actions.EcsDeployAction({
              actionName: 'CeleryBeat-Staging-Deploy',
              input: new codepipeline.Artifact('StagingBeatBuildArtifact'),
              service: ecs.BaseService.fromServiceArnWithCluster(
                this,
                'StagingCeleryBeatServiceRef_Combined',
                `arn:aws:ecs:${cdk.Aws.REGION}:${beatStagingConfig.accountId}:service/${beatStagingResources.clusterName}/${beatStagingConfig.serviceName}`
              ),
              role: iam.Role.fromRoleArn(
                this,
                'StagingBeatEcsDeployCrossAccountRole',
                `arn:aws:iam::${beatStagingConfig.accountId}:role/CrossAccount_Codepipeline_Role`
              ),
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'CeleryBeat-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,
            }),
          ],
        },
        {
          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/commit/#{BuildVariables.COMMIT_ID}',
            }),
          ],
        },
        {
          stageName: 'Prod-Celery-Build',
          actions: [
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Prod-Celery-Build',
              project: prodBuildProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              outputs: [new codepipeline.Artifact('ProdBuildArtifact')],
              role: prodBuildActionRole,
            }),
          ],
        },
        {
          stageName: 'Prod-Celery-Deploy',
          actions: [
            new codepipeline_actions.EcsDeployAction({
              actionName: 'Prod-Celery-Deploy',
              input: new codepipeline.Artifact('ProdBuildArtifact'),
              service: ecs.BaseService.fromServiceArnWithCluster(
                this,
                'ProdCeleryServiceRef',
                `arn:aws:ecs:${cdk.Aws.REGION}:${productionConfig.accountId}:service/${productionResources.clusterName}/${productionConfig.serviceName}`
              ),
              role: iam.Role.fromRoleArn(
                this,
                'ProdEcsDeployCrossAccountRole',
                `arn:aws:iam::${productionConfig.accountId}:role/CrossAccount_Codepipeline_Role`
              ),
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'Celery-Prod-CommitId',
              additionalInformation: `Approving deployment to production for commit: #{BuildVariables.COMMIT_ID}`,
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/commit/#{BuildVariables.COMMIT_ID}',
              notificationTopic: autoApprovalTopic,
              runOrder: 1,
            }),
          ],
        },
        {
          stageName: 'Prod-Beat-Build',
          actions: [
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Prod-Beat-Build',
              project: beatProdBuildProject,
              input: new codepipeline.Artifact('SourceArtifact'),
              outputs: [new codepipeline.Artifact('ProdBeatBuildArtifact')],
              role: prodBuildActionRole,
            }),
          ],
        },
        {
          stageName: 'Prod-Beat-Deploy',
          actions: [
            new codepipeline_actions.EcsDeployAction({
              actionName: 'Prod-Beat-Deploy',
              input: new codepipeline.Artifact('ProdBeatBuildArtifact'),
              service: ecs.BaseService.fromServiceArnWithCluster(
                this,
                'ProdCeleryBeatServiceRef_Combined',
                `arn:aws:ecs:${cdk.Aws.REGION}:${beatProductionConfig.accountId}:service/${beatProductionResources.clusterName}/${beatProductionConfig.serviceName}`
              ),
              role: iam.Role.fromRoleArn(
                this,
                'ProdBeatEcsDeployCrossAccountRole',
                `arn:aws:iam::${beatProductionConfig.accountId}:role/CrossAccount_Codepipeline_Role`
              ),
            }),
            new codepipeline_actions.ManualApprovalAction({
              actionName: 'CeleryBeat-Prod-CommitId',
              additionalInformation: `Approving deployment to production for commit: #{BuildVariables.COMMIT_ID}`,
              externalEntityLink: 'https://github.com/suno-ai/glockenspiel/commit/#{BuildVariables.COMMIT_ID}',
              notificationTopic: autoApprovalTopic,
              runOrder: 1,
            }),
          ],
        },
      ],
    });

    return pipeline;
  }
}


