AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template for Document Preprocessing Supervisor Agent with API Gateway and S3 integration'

Parameters:
  StandardizationToPDFECRRepositoryName:
    Type: String
    Default: standardization-to-pdf-repo
    Description: Name of the ECR repository to store the Docker image for PDF standardization.

##### For knowledge base creation ##############
  KnowledgeBaseName:
    Type: String
    Default: "InsuranceKBForUnstructuredDataLake"
    Description: Name of the Bedrock Knowledge Base.
  KnowledgeBaseDescription:
    Type: String
    Default: "Knowledge base for processed insurance documents"
    Description: Description of the knowledge base.
  OpenSearchCollectionName:
    Type: String
    Default: "processeddocs-kb-collection"
    Description: The name of the OpenSearch Serverless collection.
  IndexName:
    Type: String
    Default: "processeddocs-kb-index"
    Description: The name of the index to be created in OpenSearch Serverless.
##### For knowledge base creation ##############

# Step 1: Create S3 Bucket for Storing Uploaded Files
Resources:
  DocumentUploadBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    # Properties:
    #   BucketName: !Sub "document-upload-${AWS::AccountId}"

  
  PNGFilestUploadBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    # Properties:
    #   BucketName: !Sub "pngfiles-upload-${AWS::AccountId}"

  RawTranscriptJobBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    # Properties:
    #   BucketName: !Sub "rawtranscript-job-${AWS::AccountId}"

# New S3 Bucket for Processed Files (KB)
  KBDataBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    # Properties:
    #   BucketName: !Sub "kb-bucket-${AWS::AccountId}"


# New S3 Bucket for In-Progress Processing State
  InProgressProcessingBucket:
      Type: AWS::S3::Bucket
      DeletionPolicy: Delete
      # Properties:
      #   BucketName: !Sub "inprogress-processing-bucket-${AWS::AccountId}"

# New S3 bucket for storing lambda layers locally in the Account/Region from the main reference bucket
  DestinationBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete

# New S3 bucket for sample data
  SampleDataBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete

# New S3 bucket for sample data
  SourceBucketForDockerizedDocumentCoverter:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete

# New SQS Queue for In-Progress Processing State for UI ApI
  ProcessingQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: "DocumentProcessingQueue"
      VisibilityTimeout: 1

# New ECR repository for 
  StandardizationToPDFECRRepository:
    Type: AWS::ECR::Repository
    DeletionPolicy: Delete
    Properties:
      RepositoryName: !Ref StandardizationToPDFECRRepositoryName

# IAM Role for the Lambda Function to delete buckets for png upload, trascriptions, Raw data upload and Knowledge base for Bedrock 
  DeletePNGTranscriptionRawKBBucketsLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CopyAssetPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                  - s3:ListBucket
                  - s3:GetObject
                Resource: 
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}/*"
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}"
                  - !Sub "arn:aws:s3:::${PNGFilestUploadBucket}/*"
                  - !Sub "arn:aws:s3:::${PNGFilestUploadBucket}"
                  - !Sub "arn:aws:s3:::${RawTranscriptJobBucket}/*"
                  - !Sub "arn:aws:s3:::${RawTranscriptJobBucket}"
                  - !Sub "arn:aws:s3:::${KBDataBucket}/*"
                  - !Sub "arn:aws:s3:::${KBDataBucket}"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: 
                  - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"

##################
# Lambda Function to copy asset to local bucket and delete contents when stack is deleted
  DeleteBucketsLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: DeleteBucketsLambdaFunction
      Handler: index.handler
      Runtime: python3.10
      Timeout: 900  # Timeout set for 15 minutes
      Role: !GetAtt DeletePNGTranscriptionRawKBBucketsLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse  # Helper module to send responses back to CloudFormation

          def handler(event, context):
              s3 = boto3.resource('s3')
              bucket_names_input = event['ResourceProperties'].get('BucketNames', '')
              
              # Support comma-separated string or list of bucket names
              if isinstance(bucket_names_input, str):
                  bucket_names = [name.strip() for name in bucket_names_input.split(',') if name.strip()]
              elif isinstance(bucket_names_input, list):
                  bucket_names = bucket_names_input
              else:
                  bucket_names = []
              
              response_data = {}
              status = cfnresponse.SUCCESS  # default response is success

              try:
                  if event['RequestType'] == 'Delete':
                      for bucket_name in bucket_names:
                          bucket = s3.Bucket(bucket_name)
                          bucket.objects.all().delete()
                  response_data["Message"] = f"Bucket contents deleted successfully for buckets: {', '.join(bucket_names)}"
              except Exception as e:
                  # Log error details, but override status to SUCCESS
                  response_data["Error"] = str(e)
                  print("Error encountered:", e)
                  status = cfnresponse.SUCCESS  # Always send success even if errors occur
              finally:
                  # Always send the response to CloudFormation
                  cfnresponse.send(event, context, status, response_data)
  
# Custom Resource to trigger the Lambda function
  CustomDeleteBuckets:
    Type: Custom::CustomDeleteBuckets
    Properties:
      ServiceToken: !GetAtt DeleteBucketsLambdaFunction.Arn
      BucketNames: !Join [ ",", [ !Ref DocumentUploadBucket, !Ref PNGFilestUploadBucket, !Ref RawTranscriptJobBucket, !Ref KBDataBucket ] ]
      ServiceTimeout: 900  # Custom resource timeout set to 900 seconds

##################

##################
# IAM Role and lambda for Sync with Knowledge Base functionality
  SyncKnowledgeBaseLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: SyncKnowledgeBaseLambdaRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SyncKnowledgeBasePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - bedrock:StartIngestionJob
                  - bedrock:GetIngestionJob
                Resource: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:knowledge-base/${BedrockKnowledgeBase}"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "arn:aws:logs:*:*:*"

  SyncKnowledgeBaseFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "SyncKnowledgeBaseFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      Role: !GetAtt SyncKnowledgeBaseLambdaRole.Arn
      Timeout: 900
      Code:
        ZipFile: |
          import boto3
          import json
          import os
          import time
          
          # Initialize the Bedrock Agent client.
          bedrock_client = boto3.client('bedrock-agent', region_name=os.environ.get("AWS_REGION"))
          
          def lambda_handler(event, context):
              kb_id = os.environ.get("KB_ID")
              data_source_id = os.environ.get("DATA_SOURCE_ID")
                            
              if not kb_id or not data_source_id:
                  return {
                      "statusCode": 400,
                      "body": json.dumps({"message": "Missing KB_ID or DATA_SOURCE_ID in environment variables."})
                  }
              
              # If the data source ID contains a pipe, split and use the first part.
              if '|' in data_source_id:
                  data_source_id = data_source_id.split('|')[1]
              print(data_source_id) 

              try:
                  # Start the ingestion job.
                  start_response = bedrock_client.start_ingestion_job(
                      knowledgeBaseId=kb_id,
                      dataSourceId=data_source_id
                  )
                  job_id = start_response.get("JobId")
                  if not job_id:
                      raise Exception("No JobId returned from start_ingestion_job.")
                  
                  # Poll for the job status until COMPLETED or FAILED.
                  max_wait = 850  # Maximum wait time in seconds.
                  wait_interval = 5  # Seconds between each poll.
                  elapsed = 0
                  status = None
                  
                  while elapsed < max_wait:
                      job_response = bedrock_client.get_ingestion_job(JobId=job_id)
                      status = job_response.get("Status")
                      if status in ["COMPLETED", "FAILED"]:
                          break
                      time.sleep(wait_interval)
                      elapsed += wait_interval
                  
                  return {
                      "statusCode": 200,
                      "body": json.dumps({"jobId": job_id, "status": status})
                  }
              except Exception as e:
                  return {
                      "statusCode": 200,
                      "body": json.dumps({"message": "Failed to start ingestion job.", "error": str(e)})
                  }
      Environment:
        Variables:
          KB_ID: !Ref BedrockKnowledgeBase
          DATA_SOURCE_ID: !Ref BedrockDataSource

####################

# IAM Role for the Lambda Function to copy assets for lambda layers 
  CopyAssetLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CopyAssetPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource: 
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/bedrock-layer-13611-mac.zip
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/opencvheadlesspillownumpy310.zip
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/pymupdfpillow310.zip
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/lambda-layer-opensearch-python3.10-x86_64.zip
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                  - s3:ListBucket
                Resource: 
                  - !Sub "arn:aws:s3:::${DestinationBucket}/*"
                  - !Sub "arn:aws:s3:::${DestinationBucket}"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: 
                  - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"

# IAM Role for the Lambda Function to copy assets for lambda layers 
  CopyAssetSampleDataLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CopyAssetSampleDataPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource: 
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/carcollision.mp4
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/fnol.mp4
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/collision_center_estimate.xlsx
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/annotated_cardamage.png
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/ABC_Insurance_Policy.docx
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/ClaimDemandPackage.pdf
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                  - s3:ListBucket
                Resource: 
                  - !Sub "arn:aws:s3:::${SampleDataBucket}/*"
                  - !Sub "arn:aws:s3:::${SampleDataBucket}"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: 
                  - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"

# IAM Role for the Lambda Function to copy docker assets for lambda function creation for document converter 
  CopyAssetForDockerLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CopyAssetSampleDataPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource: 
                  - arn:aws:s3:::aws-blogs-artifacts-public/artifacts/ML-18173/source.zip
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:DeleteObject
                  - s3:ListBucket
                Resource: 
                  - !Sub "arn:aws:s3:::${SourceBucketForDockerizedDocumentCoverter}/*"
                  - !Sub "arn:aws:s3:::${SourceBucketForDockerizedDocumentCoverter}"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: 
                  - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"

# Lambda Function to copy asset to local bucket and delete contents when stack is deleted
  CopyAssetLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: CopyAssetFunction
      Handler: index.handler
      Runtime: python3.10
      Timeout: 900  # Timeout set for 15 minutes
      Role: !GetAtt CopyAssetLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse

          def handler(event, context):
              s3 = boto3.client('s3')
              destination_bucket = event['ResourceProperties']['DestinationBucket']

              try:
                  if event['RequestType'] in ['Create', 'Update']:
                      # Define the files to be copied
                      files_to_copy = [
                          {'source_key': 'artifacts/ML-18173/bedrock-layer-13611-mac.zip', 'destination_key': 'bedrock-layer-13611-mac.zip'},
                          {'source_key': 'artifacts/ML-18173/opencvheadlesspillownumpy310.zip', 'destination_key': 'opencvheadlesspillownumpy310.zip'},
                          {'source_key': 'artifacts/ML-18173/pymupdfpillow310.zip', 'destination_key': 'pymupdfpillow310.zip'},
                          {'source_key': 'artifacts/ML-18173/lambda-layer-opensearch-python3.10-x86_64.zip', 'destination_key': 'lambda-layer-opensearch-python3.10-x86_64.zip'}
                      ]

                      source_bucket = 'aws-blogs-artifacts-public'

                      for file in files_to_copy:
                          # Copy object to destination bucket
                          copy_source = {'Bucket': source_bucket, 'Key': file['source_key']}
                          s3.copy_object(Bucket=destination_bucket, CopySource=copy_source, Key=file['destination_key'])

                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Message': 'Files copied successfully'})

                  elif event['RequestType'] == 'Delete':
                      # Delete all objects in the bucket
                      bucket = boto3.resource('s3').Bucket(destination_bucket)
                      bucket.objects.all().delete()
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Message': 'Bucket emptied successfully'})

              except Exception as e:
                  print(e)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
  
# Custom Resource to trigger the Lambda function
  CustomCopyResource:
    Type: Custom::CopyAsset
    Properties:
      ServiceToken: !GetAtt CopyAssetLambdaFunction.Arn
      DestinationBucket: !Ref DestinationBucket
      ServiceTimeout: 300  # Custom resource timeout set to 300 seconds

# Lambda Function to copy asset to local bucket and delete contents when stack is deleted
  CopyAssetSampleDataLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: CopyAssetSampleDataFunction
      Handler: index.handler
      Runtime: python3.10
      Timeout: 900  # Timeout set for 15 minutes
      Role: !GetAtt CopyAssetSampleDataLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse

          def handler(event, context):
              s3 = boto3.client('s3')
              destination_bucket = event['ResourceProperties']['DestinationBucket']

              try:
                  if event['RequestType'] == 'Create':
                      # Define the files to be copied
                      files_to_copy = [
                          {'source_key': 'artifacts/ML-18173/annotated_cardamage.png', 'destination_key': 'annotated_cardamage.png'},
                          {'source_key': 'artifacts/ML-18173/collision_center_estimate.xlsx', 'destination_key': 'collision_center_estimate.xlsx'},
                          {'source_key': 'artifacts/ML-18173/fnol.mp4', 'destination_key': 'fnol.mp4'},
                          {'source_key': 'artifacts/ML-18173/carcollision.mp4', 'destination_key': 'carcollision.mp4'},
                          {'source_key': 'artifacts/ML-18173/ClaimDemandPackage.pdf', 'destination_key': 'ClaimDemandPackage.pdf'},
                          {'source_key': 'artifacts/ML-18173/ABC_Insurance_Policy.docx', 'destination_key': 'ABC_Insurance_Policy.docx'},
                      ]

                      source_bucket = 'aws-blogs-artifacts-public'

                      for file in files_to_copy:
                          # Copy object to destination bucket
                          copy_source = {'Bucket': source_bucket, 'Key': file['source_key']}
                          s3.copy_object(Bucket=destination_bucket, CopySource=copy_source, Key=file['destination_key'])

                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Message': 'Files copied successfully'})

                  elif event['RequestType'] == 'Delete':
                      # Delete all objects in the bucket
                      bucket = boto3.resource('s3').Bucket(destination_bucket)
                      bucket.objects.all().delete()
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Message': 'Bucket emptied successfully'})

              except Exception as e:
                  print(e)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
  
# Custom Resource to trigger the Lambda function
  CustomCopyResourceSampleData:
    Type: Custom::CopyAssetSampleData
    Properties:
      ServiceToken: !GetAtt CopyAssetSampleDataLambdaFunction.Arn
      DestinationBucket: !Ref SampleDataBucket
      ServiceTimeout: 900  # Custom resource timeout set to 900 seconds

# Lambda Function to copy asset to docker bucket
  CopyAssetsForDockerLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: CopyAssetsForDockerLambdaFunction
      Handler: index.handler
      Runtime: python3.10
      Timeout: 900  # Timeout set for 15 minutes
      Role: !GetAtt CopyAssetForDockerLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse

          def handler(event, context):
              s3 = boto3.client('s3')
              destination_bucket = event['ResourceProperties']['DestinationBucket']

              try:
                  if event['RequestType'] == 'Create':
                      # Define the files to be copied
                      files_to_copy = [
                          {'source_key': 'artifacts/ML-18173/source.zip', 'destination_key': 'source.zip'},
                      ]

                      source_bucket = 'aws-blogs-artifacts-public'

                      for file in files_to_copy:
                          # Copy object to destination bucket
                          copy_source = {'Bucket': source_bucket, 'Key': file['source_key']}
                          s3.copy_object(Bucket=destination_bucket, CopySource=copy_source, Key=file['destination_key'])

                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Message': 'Files copied successfully'})

                  elif event['RequestType'] == 'Delete':
                      # Delete all objects in the bucket
                      bucket = boto3.resource('s3').Bucket(destination_bucket)
                      bucket.objects.all().delete()
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Message': 'Bucket emptied successfully'})

              except Exception as e:
                  print(e)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})
  
# Custom Resource to trigger the Lambda function
  CustomCopyResourceForDockerAssets:
    Type: Custom::CopyAssetsForDocker
    Properties:
      ServiceToken: !GetAtt CopyAssetsForDockerLambdaFunction.Arn
      DestinationBucket: !Ref SourceBucketForDockerizedDocumentCoverter
      ServiceTimeout: 500  # Custom resource timeout set to 500 seconds

# Lambda Layer for MAC
  MACLambdaLayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      LayerName: "MACLambdaLayer"
      Description: "Lambda layer for Multi agent Collaboration"
      Content: 
        S3Bucket: !Ref DestinationBucket
        S3Key: "bedrock-layer-13611-mac.zip"
      CompatibleRuntimes:
        - python3.10
    DependsOn: CustomCopyResource

# Lambda Layer for PYMUPDF
  PYMUPDFPillowLambdaLayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      LayerName: "PYMUPDFPillowLambdaLayer"
      Description: "Lambda layer for pymupdf pillow for python 3.10"
      Content: 
        S3Bucket: !Ref DestinationBucket
        S3Key: "pymupdfpillow310.zip"
      CompatibleRuntimes:
        - python3.10
    DependsOn: CustomCopyResource

# Lambda Layer for OPENCVHEADLESSPILLOWNUMPY
  OpencvLambdaLayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      LayerName: "OpencvLambdaLayer"
      Description: "Lambda layer for open cv headless pillow numpy for python 3.10 used in audio/video transcription"
      Content: 
        S3Bucket: !Ref DestinationBucket
        S3Key: "opencvheadlesspillownumpy310.zip"
      CompatibleRuntimes:
        - python3.10
    DependsOn: CustomCopyResource

# IAM Role for CodeBuild to access ECR and S3
  StandardizationToPDFCodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess  # Grant ECR registry access, required for GetAuthorizationToken
      Policies:
        - PolicyName: StandardizationToPDFCodeBuildECRPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchCheckLayerAvailability
                  - ecr:PutImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
                Resource: !Sub "arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${StandardizationToPDFECRRepository}"
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${SourceBucketForDockerizedDocumentCoverter}/source.zip"
                  - !Sub "arn:aws:s3:::${SourceBucketForDockerizedDocumentCoverter}"
                  - !Sub "arn:aws:s3:::${SourceBucketForDockerizedDocumentCoverter}/*"


# CodeBuild Project to build Docker image and push to ECR
  StandardizationToPDFCodeBuildProject:
    Type: AWS::CodeBuild::Project
    DependsOn: CustomCopyResourceForDockerAssets
    Properties:
      Name: StandardizationToPDFDockerImageBuildProject
      Source:
        Type: S3
        Location: !Sub "arn:aws:s3:::${SourceBucketForDockerizedDocumentCoverter}/source.zip"
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              commands:
                - echo Logging in to Amazon ECR...
                - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REPO_URI
                - echo Verifying files in the working directory...
                - ls -la  # List files to verify if the source is already unpacked
                - echo Extracting source files...
            build:
              commands:
                - echo Building Docker image...
                - docker build -t $ECR_REPO_URI:$IMAGE_TAG .
            post_build:
              commands:
                - echo Pushing Docker image to ECR...
                - docker push $ECR_REPO_URI:$IMAGE_TAG
                - echo Build completed on `date`
      Environment:
        ComputeType: BUILD_GENERAL1_LARGE
        Image: aws/codebuild/standard:5.0
        PrivilegedMode: true
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: ECR_REPO_URI
            Value: !GetAtt StandardizationToPDFECRRepository.RepositoryUri
          - Name: AWS_REGION
            Value: !Ref AWS::Region
          - Name: IMAGE_TAG
            Value: latest
      ServiceRole: !GetAtt StandardizationToPDFCodeBuildServiceRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      TimeoutInMinutes: 120


# Lambda Function to trigger CodeBuild and wait for completion
  TriggerBuildFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "trigger-codebuild-${AWS::StackName}"
      Handler: index.handler
      Role: !GetAtt CodeBuildTriggerLambdaRole.Arn
      Runtime: python3.9
      Timeout: 300
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse

          def handler(event, context):
              print(f"Received event: {json.dumps(event)}")
              response_status = cfnresponse.SUCCESS
              try:
                  if event['RequestType'] in ['Create', 'Update']:
                      codebuild = boto3.client('codebuild')
                      project_name = event['ResourceProperties']['ProjectName']
                      build_response = codebuild.start_build(projectName=project_name)
                      print(f"Build started with ID: {build_response['build']['id']}")
                  elif event['RequestType'] == 'Delete':
                      print("Delete request received. No action needed.")
              except Exception as e:
                  print(f"Exception: {str(e)}")
                  response_status = cfnresponse.FAILED
              finally:
                  cfnresponse.send(event, context, response_status, {})

# IAM Role for Lambda to trigger CodeBuild
  CodeBuildTriggerLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: AllowCodeBuildStartAndMonitor
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuilds
                  - codebuild:ListBuilds
                Resource: "*"    

# Custom Resource to trigger the CodeBuild
  TriggerBuildCustomResource:
    Type: Custom::TriggerCodeBuild
    DependsOn: StandardizationToPDFCodeBuildProject
    Properties:
      ServiceToken: !GetAtt TriggerBuildFunction.Arn
      ProjectName: !Ref StandardizationToPDFCodeBuildProject
      ServiceTimeout: 900  # Custom resource timeout set to 400 seconds
      TriggerUUID: !Ref AWS::StackId  # Changes every time the stack is updated

# CloudFormation WaitCondition to delay further stack progress until CodeBuild completes
  BuildWaitConditionHandle:
    Type: AWS::CloudFormation::WaitConditionHandle

  BuildWaitCondition:
    Type: AWS::CloudFormation::WaitCondition
    DependsOn: TriggerBuildCustomResource
    Properties:
      Handle: !Ref BuildWaitConditionHandle
      Timeout: 1800  # Set as needed for your build duration

# Lambda to send a signal to the WaitCondition when CodeBuild completes
  CodeBuildSuccessSignalFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "codebuild-success-signal-${AWS::StackName}"
      Handler: index.handler
      Role: !GetAtt CodeBuildSuccessSignalLambdaRole.Arn
      Runtime: python3.9
      Timeout: 60
      Environment:
        Variables:
          WAIT_HANDLE_URL: !Ref BuildWaitConditionHandle
      Code:
        ZipFile: |
          import json
          import os
          import urllib3

          def handler(event, context):
              print("Received event: " + json.dumps(event))
              http = urllib3.PoolManager()
              signal_data = json.dumps({
                  "Status": "SUCCESS",
                  "Reason": "CodeBuild completed successfully",
                  "UniqueId": event.get('id', 'N/A'),
                  "Data": "Build complete"
              }).encode('utf-8')
              response = http.request('PUT', os.environ['WAIT_HANDLE_URL'], body=signal_data)
              print("Signal sent with response status: " + str(response.status))


  CodeBuildSuccessSignalLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole


# EventBridge rule that triggers the above Lambda when CodeBuild reports a SUCCEEDED state
  CodeBuildBuildSuccessRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub "CodeBuildBuildSuccessRule-${AWS::StackName}"
      EventPattern:
        source:
          - aws.codebuild
        detail-type:
          - "CodeBuild Build State Change"
        detail:
          build-status:
            - SUCCEEDED
          project-name:
            - !Ref StandardizationToPDFCodeBuildProject
      Targets:
        - Arn: !GetAtt CodeBuildSuccessSignalFunction.Arn
          Id: "CodeBuildSuccessSignalFunctionTarget"

  CodeBuildSuccessSignalPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref CodeBuildSuccessSignalFunction
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt CodeBuildBuildSuccessRule.Arn

# IAM Role for Lambda function to access S3
  StandardizationToPDFLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        # - arn:aws:iam::aws:policy/AmazonS3FullAccess
        # - arn:aws:iam::aws:policy/AdministratorAccess
      Policies:
      - PolicyName: S3AccessForSpecificBucket
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - s3:GetObject
                - s3:PutObject
              Resource:
                - !Sub "arn:aws:s3:::${DocumentUploadBucket}/*"
            - Effect: Allow
              Action:
                - s3:ListBucket
                - s3:GetBucketLocation
              Resource:
                - !Sub "arn:aws:s3:::${DocumentUploadBucket}"

# Lambda Function to convert documents to PDF
  StandardizationToPDFLambdaFunction:
    Type: AWS::Lambda::Function
    DependsOn: BuildWaitCondition
    Properties:
      FunctionName: !Sub "standardization-to-pdf-lambda-${AWS::AccountId}"
      Role: !GetAtt StandardizationToPDFLambdaExecutionRole.Arn
      PackageType: Image
      Code:
        ImageUri: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${StandardizationToPDFECRRepositoryName}:latest"
      Timeout: 120
      MemorySize: 4096
      Environment:
        Variables:
          INPUT_BUCKET: !Ref DocumentUploadBucket
          OUTPUT_BUCKET: !Ref DocumentUploadBucket


  ECRCleanupFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "cleanup-ecr-${AWS::StackName}"
      Handler: index.handler
      Role: !GetAtt ECRCleanupLambdaRole.Arn
      Runtime: python3.9
      Timeout: 300
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse

          def handler(event, context):
              print(f"Received event: {json.dumps(event)}")  # Log incoming event
              ecr = boto3.client('ecr')
              response_status = cfnresponse.SUCCESS  # Default response is SUCCESS
              response_data = {}

              try:
                  repository_name = event['ResourceProperties']['RepositoryName']
                  print(f"Repository to clean: {repository_name}")

                  if event['RequestType'] in ['Create', 'Update']:
                      print("Create or Update event detected. No action needed.")
                  
                  elif event['RequestType'] == 'Delete':
                      # List all images in the repository
                      images = ecr.list_images(repositoryName=repository_name)
                      image_ids = images.get('imageIds', [])

                      if image_ids:
                          print(f"Found images: {image_ids}. Deleting...")
                          ecr.batch_delete_image(repositoryName=repository_name, imageIds=image_ids)
                          print(f"Deleted images from repository: {repository_name}")
                      else:
                          print(f"No images found in repository: {repository_name}")

              except Exception as e:
                  print(f"Exception encountered: {str(e)}")
                  response_status = cfnresponse.FAILED  # Set response to FAILED on exception
                  response_data = {'Message': str(e)}

              finally:
                  print("Sending CloudFormation response...")
                  cfnresponse.send(event, context, response_status, response_data)


  ECRCleanupLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: AllowECRImageDeletion
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ecr:ListImages
                  - ecr:BatchDeleteImage
                Resource: "*"

  ECRCleanupCustomResource:
    Type: Custom::ECRCleanup
    DependsOn: StandardizationToPDFECRRepository
    Properties:
      ServiceToken: !GetAtt ECRCleanupFunction.Arn
      ServiceTimeout: 300  # Custom resource timeout set to 400 seconds
      RepositoryName: !Ref StandardizationToPDFECRRepositoryName


  # LambdaExecutionRole:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: LambdaExecutionRole
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: [lambda.amazonaws.com]
  #           Action: sts:AssumeRole
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for ease

  UserInterfaceLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: UserInterfaceLambdaExecutionRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: [lambda.amazonaws.com]
            Action: sts:AssumeRole
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for ease
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

  UploadLambdaFunctionExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: UploadLambdaFunctionExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: S3UploadAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetBucketLocation
                  - s3:ListBucket  # Optional: include if your function needs to list bucket contents
                Resource:
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}"
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleSupervisorAgent:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: AmazonBedrockExecutionRoleForAgents_DocProcessingSupAgent
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  # Step 2b: IAM Role for Bedrock Agent Execution
  BedrockAgentExecutionRoleSupervisorAgent:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_DocProcessingSupAgent
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"


# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleClassfnCollab:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: AmazonBedrockExecutionRoleForAgents_DocProcessingClassCollab
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleClassfnCollab:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_DocProcessingClassCollab
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleDocConversion:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: AmazonBedrockExecutionRoleForAgents_DocProcessingDocConverter
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleDocConversion:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_DocProcessingDocConverter
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleDocClassification:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: AmazonBedrockExecutionRoleForAgents_DocumentClassfn
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleDocClassification:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_DocumentClassfn
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleTranscription:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: AmazonBedrockExecutionRoleForAgents_Transcription
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleTranscription:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_Transcription
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleProcessDemandPackage:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: AmazonBedrockExecutionRoleForAgents_ProcessDemandPack
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleProcessDemandPackage:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_ProcessDemandPack
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleProcessVehicleDamageImage:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: AmazonBedrockExecutionRoleForAgents_ProcessVehicleDamage
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleProcessVehicleDamageImage:
    Type: AWS::IAM::Role
    Properties:
      RoleName: AmazonBedrockExecutionRoleForAgents_ProcessVehicleDamage
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockagentExecutionRoleForAudioVideoProcess:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: BedrockagentExecutionRoleForAudioVideoProcess
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockagentExecutionRoleForAudioVideoProcess:
    Type: AWS::IAM::Role
    Properties:
      RoleName: BedrockagentExecutionRoleForAudioVideoProcess
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleProcessVehicleRepair:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: BedrockAgentExecutionRoleProcessVehicleRepair
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleProcessVehicleRepair:
    Type: AWS::IAM::Role
    Properties:
      RoleName: BedrockAgentExecutionRoleProcessVehicleRepair
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleProcessInsurancePolicy:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: BedrockAgentExecutionRoleProcessInsurancePolicy
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleProcessInsurancePolicy:
    Type: AWS::IAM::Role
    Properties:
      RoleName: BedrockAgentExecutionRoleProcessInsurancePolicy
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 2b: IAM Role for Bedrock Agent Execution
  # BedrockAgentExecutionRoleProcessUnclassifiedDoc:
  #   Type: AWS::IAM::Role
  #   Properties:
  #     RoleName: BedrockAgentExecutionRoleProcessUnclassifiedDoc
  #     AssumeRolePolicyDocument:
  #       Version: "2012-10-17"
  #       Statement:
  #         - Effect: Allow
  #           Principal:
  #             Service: bedrock.amazonaws.com
  #           Action: sts:AssumeRole
  #           Condition:
  #             StringEquals:
  #               aws:SourceAccount: !Ref AWS::AccountId
  #             ArnLike:
  #               aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
  #     ManagedPolicyArns:
  #       - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now

  BedrockAgentExecutionRoleProcessUnclassifiedDoc:
    Type: AWS::IAM::Role
    Properties:
      RoleName: BedrockAgentExecutionRoleProcessUnclassifiedDoc
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      ManagedPolicyArns:
        # - arn:aws:iam::aws:policy/AdministratorAccess  # Admin access for now
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockAgentInvokeModelPolicy
      #     PolicyDocument:
      #       Version: '2012-10-17'
      #       Statement:
      #         - Effect: Allow
      #           Action: bedrock:InvokeModel
      #           Resource: 
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0
      #             - !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0
      #   - PolicyName: BedrockAgentPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #             # Add any other Bedrock actions your Supervisor Agent requires here
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"

# Step 3: Update UI Lambda Function Code
  UserInterfaceLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "UserInterfaceLambdaFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      Role: !GetAtt UserInterfaceLambdaExecutionRole.Arn
      # Role: !GetAtt LambdaExecutionRole.Arn
      Environment:
        Variables:
          API_INVOKE_URL: !Sub "https://${DocumentProcessingApi}.execute-api.${AWS::Region}.amazonaws.com/prod"
      Code:
        ZipFile: |
          import json
          import os

          def lambda_handler(event, context):
              api_url = os.environ.get('API_INVOKE_URL', '')

              return {
                  'statusCode': 200,
                  'headers': {
                      'Content-Type': 'text/html',
                      'Access-Control-Allow-Origin': '*'
                  },
                  'body': f"""
                  <!DOCTYPE html>
                  <html>
                  <head>
                      <title>Insurance Unstructured Data Preprocessing Hub</title>
                      <style>
                          body {{
                              font-family: Arial, sans-serif;
                              margin: 40px;
                              background-color: #f7f9fc;
                              color: #333;
                          }}
                          .container {{
                              max-width: 900px;
                              margin: auto;
                              background: #fff;
                              padding: 30px;
                              border-radius: 10px;
                              box-shadow: 0 4px 12px rgba(0,0,0,0.1);
                              text-align: center;
                          }}
                          h1 {{
                              font-size: 32px;
                              color: #2c3e50;
                              margin-bottom: 10px;
                          }}
                          .subtitle {{
                              font-size: 16px;
                              color: #555;
                              margin-bottom: 30px;
                          }}
                          .button-bar {{
                              display: flex;
                              flex-wrap: wrap;
                              justify-content: center;
                              align-items: center;
                              gap: 20px;
                              margin-bottom: 30px;
                          }}
                          .button {{
                              background: linear-gradient(135deg, #0073e6, #005bb5);
                              color: white;
                              padding: 12px 24px;
                              font-size: 15px;
                              font-weight: bold;
                              border: none;
                              cursor: pointer;
                              border-radius: 25px;
                              box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);
                              transition: all 0.3s ease-in-out;
                          }}
                          .button:hover {{
                              background: linear-gradient(135deg, #005bb5, #003e80);
                              box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.3);
                          }}
                          input[type="file"] {{
                              display: none;
                          }}
                          .file-upload-label {{
                              background: linear-gradient(135deg, #0073e6, #005bb5);
                              color: white;
                              padding: 12px 24px;
                              font-size: 15px;
                              font-weight: bold;
                              border: none;
                              cursor: pointer;
                              border-radius: 25px;
                              box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);
                              transition: all 0.3s ease-in-out;
                          }}
                          .file-upload-label:hover {{
                              background: linear-gradient(135deg, #005bb5, #003e80);
                              box-shadow: 3px 3px 12px rgba(0, 0, 0, 0.3);
                          }}
                          #file-info {{
                              margin-top: 10px;
                              color: #555;
                              font-size: 14px;
                          }}
                          .upload-section {{
                              margin-bottom: 20px;
                          }}
                          .response-section {{
                              display: none;
                              text-align: left;
                              margin-top: 20px;
                          }}

                          /* Aesthetic for the extracted metadata container */
                          .extracted-metadata {{
                              background: linear-gradient(to bottom right, #f0f0f0, #e8e8e8);
                              padding: 20px;
                              border-radius: 8px;
                              margin-top: 10px;
                          }}

                          .metadata-grid {{
                              display: grid;
                              grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
                              gap: 16px;
                          }}
                          .metadata-item {{
                              background: #fff;
                              border: 1px solid #ddd;
                              border-radius: 5px;
                              padding: 12px;
                              box-shadow: 0 2px 6px rgba(0,0,0,0.04);
                          }}
                          .metadata-item label {{
                              display: block;
                              font-weight: bold;
                              margin-bottom: 6px;
                          }}
                          .metadata-item .field-input {{
                              display: block;
                              width: 100%;
                              box-sizing: border-box;
                              padding: 8px;
                              font-size: 14px;
                              border: 1px solid #ccc;
                              border-radius: 5px;
                              font-family: inherit;
                          }}
                          .metadata-item .field-input[type="textarea"] {{
                              resize: vertical;
                              min-height: 60px;
                          }}
                          .next-steps {{
                              background: #eee;
                              padding: 15px;
                              border-radius: 5px;
                              margin-top: 10px;
                              font-size: 14px;
                          }}
                          .queue-container {{
                              background: #fff;
                              padding: 20px;
                              border-radius: 8px;
                              box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
                              text-align: center;
                              margin-top: 20px;
                          }}
                          #queue-table {{
                              width: 100%;
                              border-collapse: collapse;
                              margin-top: 10px;
                          }}
                          #queue-table th, #queue-table td {{
                              padding: 12px 15px;
                              border: 1px solid #ddd;
                              text-align: center;
                          }}
                          #queue-table th {{
                              background-color: #0073e6;
                              color: #ffffff;
                              font-weight: bold;
                          }}
                          #queue-table tr:nth-child(even) {{
                              background-color: #f9f9f9;
                          }}
                          #queue-table tr:hover {{
                              background-color: #f1f1f1;
                          }}
                          #queue-table td {{
                              color: #333;
                              font-size: 14px;
                          }}
                          .chat-section {{
                              background: #fafafa;
                              padding: 15px;
                              border-radius: 8px;
                              box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
                              margin-top: 20px;
                              text-align: left;
                          }}
                          .chat-section h2 {{
                              margin-top: 0;
                          }}
                          .chat-section input,
                          .chat-section textarea {{
                              display: block;
                              width: 100%;
                              margin-bottom: 10px;
                              font-size: 14px;
                              padding: 8px;
                              border-radius: 5px;
                              border: 1px solid #ccc;
                          }}
                          .chat-history {{
                              margin-top: 10px;
                              background: #fff;
                              border-radius: 5px;
                              padding: 10px;
                              height: 150px;
                              overflow-y: auto;
                              border: 1px solid #ccc;
                          }}
                          .loading {{
                              display: none;
                              font-size: 14px;
                              color: #0073e6;
                              font-weight: bold;
                          }}
                      </style>
                  </head>
                  <body>
                      <div class="container">
                          <h1>Insurance Unstructured Data Preprocessing Hub</h1>
                          <p class="subtitle">This system classifies insurance-related documents and prepares them for indexing and analysis.</p>

                          <div class="button-bar">
                              <label for="file-upload" class="file-upload-label">Upload File</label>
                              <button class="button" id="chat-with-docs-button">Chat with Your Documents</button> 
                              <button class="button" id="check-queue-btn">Check Processing Queue</button>
                          </div>

                          <div class="upload-section">
                              <input type="file" id="file-upload" accept="image/*,application/pdf,audio/*,video/*">
                              <p id="file-info" style="display:none;"></p>
                              <p id="loading-message" class="loading">Uploading...</p>
                          </div>

                          <div class="response-section" id="response-container">
                              <h2>Document Classification</h2>
                              <p id="classification-result">Processing...</p>

                              <h2>Extracted Metadata</h2>
                              <div class="extracted-metadata">
                                  <div class="metadata-grid" id="metadata-grid"></div>
                              </div>

                              <h2>Next Steps in Preprocessing</h2>
                              <div class="next-steps" id="next-steps"></div>

                              <button class="button" id="continue-button" style="display:none;">Continue with Preprocessing</button>
                          </div>

                          <div class="queue-container" id="queue-section" style="display: none;">
                              <h2>Processing Queue Status</h2>
                              <table id="queue-table">
                                  <thead>
                                      <tr>
                                          <th>File Name</th>
                                          <th>Status</th>
                                      </tr>
                                  </thead>
                                  <tbody>
                                      <tr><td colspan="2">No documents in queue.</td></tr>
                                  </tbody>
                              </table>
                          </div>

                          <div class="chat-section" id="chat-section" style="display:none;">
                              <h2>Chat with Your Documents</h2>
                              <input type="text" id="claim-number" placeholder="Enter Claim Number" />
                              <input type="text" id="policy-number" placeholder="Enter Policy Number" />
                              <textarea id="chat-input" placeholder="Ask a question..."></textarea>
                              <button class="button" id="send-chat">Send</button>
                              <button class="button" id="sync-kb-button">Sync Knowledge Base</button>
                              <div class="chat-history" id="chat-history"></div>
                          </div>
                      </div>

                      <script>
                          let sessionId = sessionStorage.getItem('sessionId') || '';
                          const dummy_status_find_docs_processed = true;
                          const dummy_invoke_processing_logic = false;
                          const use_simulated_response = false;

                          const simulatedResponses = {{
                              sessionId: 'dummy-session-id',
                              agentResponse: JSON.stringify({{
                                  category: 'Claims Documents Package',
                                  confidence: 0.95,
                                  extractedData: {{
                                      'Claim Number': '123456789',
                                      'Policy Number': 'POL987654',
                                      'Date of Loss': '2023-11-15',
                                      'Claimant Name': 'John Doe',
                                      'summary': 'A long text describing the claim in detail, possibly multiline...'
                                  }},
                                  nextSteps: [
                                      'Perform Mandatory Documents Check: Police Report, Medical Bills, Adjuster Notes, Lawyer\\'s Notes, Witness Statements',
                                      'Page-level Classification: Police Report, Medical Bills, Doctor\\'s Notes, Lawyer\\'s Notes, Witness Statements',
                                      'Attach Metadata to Each Page: Policy Number, Date of Loss, Claimant Name[SIM]',
                                      'Indexing & Search: Once processed, stored for advanced search & analysis[SIM]'
                                  ]
                              }})
                          }};

                          // Flatten objects/arrays
                          function flattenMetadata(obj, prefix = '') {{
                              let rows = [];
                              for (const [k, v] of Object.entries(obj)) {{
                                  const path = prefix ? prefix + '.' + k : k;
                                  if (v && typeof v === 'object' && !Array.isArray(v)) {{
                                      rows = rows.concat(flattenMetadata(v, path));
                                  }} else if (Array.isArray(v)) {{
                                      if (v.length && typeof v[0] === 'object') {{
                                          v.forEach((item, i) => {{
                                              if (item && typeof item === 'object') {{
                                                  rows = rows.concat(flattenMetadata(item, path + '[' + i + ']'));
                                              }} else {{
                                                  rows.push({{ key: path + '[' + i + ']', value: String(item) }});
                                              }}
                                          }});
                                      }} else {{
                                          rows.push({{ key: path, value: v.join(', ') }});
                                      }}
                                  }} else {{
                                      rows.push({{ key: path, value: v == null ? '' : String(v) }});
                                  }}
                              }}
                              return rows;
                          }}

                          // Re-nesting helper for final updated fields
                          function updateNestedField(obj, flattenedKey, newValue) {{
                              const parts = flattenedKey.split('.');
                              let current = obj;

                              for (let i = 0; i < parts.length; i++) {{
                                  let part = parts[i];
                                  const bracketMatch = part.match(/(.*)\\[(\\d+)\\]/);
                                  if (bracketMatch) {{
                                      let arrName = bracketMatch[1];
                                      let arrIndex = parseInt(bracketMatch[2], 10);

                                      if (!current[arrName]) {{
                                          current[arrName] = [];
                                      }}
                                      current = current[arrName];
                                      while (current.length <= arrIndex) {{
                                          current.push({{}});
                                      }}
                                      if (i === parts.length - 1) {{
                                          current[arrIndex] = newValue;
                                      }} else {{
                                          if (!current[arrIndex] || typeof current[arrIndex] !== 'object') {{
                                              current[arrIndex] = {{}};
                                          }}
                                          current = current[arrIndex];
                                      }}
                                  }} else {{
                                      if (i === parts.length - 1) {{
                                          current[part] = newValue;
                                      }} else {{
                                          if (!current[part] || typeof current[part] !== 'object') {{
                                              current[part] = {{}};
                                          }}
                                          current = current[part];
                                      }}
                                  }}
                              }}
                          }}
                          
                          function extractValidJSON(text) {{
                            const match = text.match(/(\{{[\s\S]*\}})/);
                            return match ? match[0] : text;
                          }}

                          function displayResults(data) {{
                              let jsonStr = extractValidJSON(data.agentResponse || '{{}}');
                              try{{
                                      const agentResponse = JSON.parse(jsonStr);
                                      sessionStorage.setItem('classificationResponse', JSON.stringify(agentResponse));

                                      document.getElementById('classification-result').innerText =
                                          'Identified Document Type: ' + (agentResponse.category || 'N/A');

                                      const extractedData = agentResponse.extractedData || {{}};
                                      const flattened = flattenMetadata(extractedData);

                                      const metadataGrid = document.getElementById('metadata-grid');
                                      metadataGrid.innerHTML = '';

                                      flattened.forEach(({{ key, value }}) => {{
                                          const isSummary = key.toLowerCase().includes('summary') || value.length > 60;

                                          let rowCount = 3;
                                          if (value.length > 120) {{
                                              rowCount = 6;
                                          }}

                                          // data-key for re-nesting
                                          let field;
                                          if (isSummary) {{
                                              field = '<textarea class="field-input meta-field" data-key="' + key + '" style="resize:vertical;" rows="' + rowCount + '">' + value + '</textarea>';
                                          }} else {{
                                              field = '<input type="text" class="field-input meta-field" data-key="' + key + '" value="' + value + '" />';
                                          }}

                                          metadataGrid.innerHTML += ''
                                            + '<div class="metadata-item">'
                                            +   '<label>' + key + '</label>'
                                            +   field
                                            + '</div>';
                                      }});

                                      let stepsHtml = '<ul>';
                                      (agentResponse.nextSteps || []).forEach(step => {{
                                          stepsHtml += '<li>' + step + '</li>';
                                      }});
                                      stepsHtml += '</ul>';
                                      document.getElementById('next-steps').innerHTML = stepsHtml;

                                      document.getElementById('response-container').style.display = 'block';
                                      document.getElementById('continue-button').style.display = 'block';
                                  }} catch (e) {{
                                      console.error("Failed to parse JSON:", e);
                                      document.getElementById('loading-message').innerText = 'Error during processing.';
                                  }}
                          }}

                          // On file change
                          document.getElementById('file-upload').addEventListener('change', function() {{
                              if (!this.files[0]) return;

                              document.getElementById('response-container').style.display = 'none';
                              document.getElementById('classification-result').innerText = 'Processing...';
                              document.getElementById('metadata-grid').innerHTML = '';
                              document.getElementById('next-steps').innerHTML = '';
                              document.getElementById('continue-button').style.display = 'none';

                              const file = this.files[0];
                              const fileInfo = document.getElementById('file-info');
                              const loadingMessage = document.getElementById('loading-message');
                              const uploadButton = document.querySelector('.file-upload-label'); // Using the file upload label as the button


                              fileInfo.style.display = 'block';
                              fileInfo.innerText = 'Selected: ' + file.name;
                              loadingMessage.style.display = 'block';
                              loadingMessage.innerText = 'Uploading...';
                              uploadButton.innerText = 'Sending...'; // Change button text to "Sending..."

                              const reader = new FileReader();
                              reader.onload = function(e) {{
                                  fetch('{api_url}/upload', {{
                                      method: 'POST',
                                      headers: {{ 'Content-Type': 'application/json' }},
                                      body: JSON.stringify({{
                                          image: e.target.result.split(',')[1],
                                          fileName: file.name,
                                          sessionId: sessionId
                                      }})
                                  }})
                                  .then(response => response.json())
                                  .then(data => {{
                                      loadingMessage.innerText = 'Upload Complete! Processing...';
                                      return fetch('{api_url}/invoke-supervisor', {{
                                          method: 'POST',
                                          headers: {{ 'Content-Type': 'application/json' }},
                                          body: JSON.stringify({{
                                              sessionId: sessionId,
                                              inputText: 'Classify document: ' + data.imageUrl + '.'
                                          }})
                                      }});
                                  }})
                                  .then(response => response.json())
                                  .then(agentData => {{
                                    // Log the response instead of using alert
                                    console.log("Supervisor agent response:", agentData);
                                    loadingMessage.innerText = ''; // Hide the loading message
                                    uploadButton.innerText = 'Upload File'; // Reset button text
                                    displayResults(use_simulated_response ? simulatedResponses : agentData)
                                  }})
                                  .catch(err => {{
                                      console.error('Error in upload or classification:', err);
                                      loadingMessage.innerText = 'Error during processing.';
                                      uploadButton.innerText = 'Upload File';
                                  }});
                              }};
                              reader.readAsDataURL(file);
                          }});

                          // Continue with Preprocessing
                          document.getElementById('continue-button').addEventListener('click', function() {{
                              const continueButton = this;
                              // Change the button text to indicate that processing has started
                              continueButton.innerText = "Sending.....";

                              let classificationResponse = JSON.parse(sessionStorage.getItem('classificationResponse') || '{{}}');
                              if (!classificationResponse.extractedData) {{
                                  classificationResponse.extractedData = {{}};
                              }}

                              // Re-nest user edits
                              const fields = document.querySelectorAll('.meta-field');
                              fields.forEach(field => {{
                                  const flattenedKey = field.getAttribute('data-key');
                                  updateNestedField(classificationResponse.extractedData, flattenedKey, field.value);
                              }});

                              // Re-save updated classification
                              sessionStorage.setItem('classificationResponse', JSON.stringify(classificationResponse));

                              // Build final input text
                              const category = classificationResponse.category || 'Unknown Category';
                              const extracted = classificationResponse.extractedData || {{}};
                              const claimNumber = extracted.claimNumber ||
                                                extracted['Claim Number'] ||
                                                extracted.claimDetails?.claimNumber ||
                                                'N/A';
                              const policyNumber = extracted.policyNumber ||
                                                  extracted['Policy Number'] ||
                                                  extracted.claimDetails?.policyNumber ||
                                                  'N/A';
                              const dateOfLoss = extracted.dateOfLoss ||
                                                extracted['Date of Loss'] ||
                                                extracted.claimDetails?.dateOfLoss ||
                                                'N/A';
                              const summary = extracted.summary || 'N/A';
                              const claimantName = extracted.claimantName ||
                                                  extracted['Claimant Name'] ||
                                                  extracted.claimDetails?.claimantName ||
                                                  'N/A';
                              const effectiveDate = extracted.effectiveDate || 'N/A';
                              const policyHolderName = extracted.policyHolderName || 'N/A';
                              const policyType = extracted.policyType || 'N/A';
                              const filenameToProcess = classificationResponse.documentPath || 'Unknown Filename';

                              let inputText = 'Process the document of type ' + category +
                              ' with the following ' +
                              ' File Name: ' + filenameToProcess +
                              ' Claim Number: ' + claimNumber +
                              ' Policy Number: ' + policyNumber +
                              ' Date of Loss: ' + dateOfLoss +
                              ' Summary: ' + summary +
                              ' Claimant Name: ' + claimantName +
                              ' Policy Effective Date: ' + effectiveDate +
                              ' Policy Holder Name: ' + policyHolderName +
                              ' Policy Type: ' + policyType;

                              // Append extra vehicle details if the category matches
                              if (category === "Vehicle Repair Estimate Summary") {{
                                  const vehicleMake = extracted.vehicleDetails?.make || 'N/A';
                                  const vehicleModel = extracted.vehicleDetails?.model || 'N/A';
                                  const vehicleYear = extracted.vehicleDetails?.year || 'N/A';
                                  const vehicleVin = extracted.vehicleDetails?.vin || 'N/A';
                                  const licensePlate = extracted.vehicleDetails?.licensePlate || 'N/A';
                                  const estimateDate = extracted.claimDetails?.estimateDate || 'N/A';
                                  const dateIn = extracted.claimDetails?.dateIn || 'N/A';
                                  const dateOut = extracted.claimDetails?.dateOut || 'N/A';
                                  const estimatorName = extracted.estimatorDetails?.name || 'N/A';
                                  const estimatorContact = extracted.estimatorDetails?.contact || 'N/A';
                              
                                  inputText += ' Vehicle Make: ' + vehicleMake +
                                              ' Vehicle Model: ' + vehicleModel +
                                              ' Vehicle Year: ' + vehicleYear +
                                              ' Vehicle VIN: ' + vehicleVin +
                                              ' License Plate: ' + licensePlate +
                                              ' Estimate Date: ' + estimateDate +
                                              ' Date In: ' + dateIn +
                                              ' Date Out: ' + dateOut +
                                              ' Estimator Name: ' + estimatorName +
                                              ' Estimator Contact: ' + estimatorContact;
                              }}

                              if (!dummy_invoke_processing_logic) {{
                                  fetch('{api_url}/invoke-supervisor', {{
                                      method: 'POST',
                                      headers: {{ 'Content-Type': 'application/json' }},
                                      body: JSON.stringify({{
                                          sessionId: sessionId,
                                          inputText: inputText
                                      }})
                                  }})
                                  .then(response => response.json())
                                  .then(data => {{
                                      console.log('Preprocessing completed: ' + data.agentResponse);
                                      // Revert the button text back to its original label
                                      continueButton.innerText = "Continue with Preprocessing";
                                  }})
                                  .catch(error => {{
                                      console.error('Error in preprocessing:', error);
                                      continueButton.innerText = "Continue with Preprocessing";
                                  }});
                              }}

                              console.log('Your document is being processed. You can upload more files or chat with processed documents.');
                          }});

                          // Check queue
                          document.getElementById('check-queue-btn').addEventListener('click', function() {{
                              fetch('{api_url}/check-queue', {{
                                  method: 'GET',
                                  headers: {{ 'Content-Type': 'application/json' }}
                              }})
                              .then(response => response.json())
                              .then(data => {{
                                  updateQueueTable(data.queue);
                                  document.getElementById('queue-section').style.display = 'block';
                              }})
                              .catch(error => {{
                                  console.error('Error fetching queue:', error);
                              }});
                          }});

                          document.getElementById('sync-kb-button').addEventListener('click', function() {{
                              const syncButton = document.getElementById('sync-kb-button');
                              syncButton.disabled = true;
                              syncButton.innerText = "Syncing...";
                              
                              fetch('{api_url}/sync-kb', {{
                                  method: 'POST',
                                  headers: {{ 'Content-Type': 'application/json' }}
                              }})
                              .then(response => response.json())
                              .then(data => {{
                                  // Check the returned status from the Lambda.
                                  const status = data.status;
                                  if (status === "COMPLETED") {{
                                      console.log("Knowledge Base sync completed successfully.");
                                  }} else if (status === "FAILED") {{
                                      console.log("Knowledge Base sync failed. Please try again.");
                                  }} else {{
                                      console.log("Sync finished with status: " + status);
                                  }}
                                  syncButton.disabled = false;
                                  syncButton.innerText = "Sync Knowledge Base";
                              }})
                              .catch(err => {{
                                  console.error('Error syncing Knowledge Base:', err);
                                  console.log('Error syncing Knowledge Base.');
                                  syncButton.disabled = false;
                                  syncButton.innerText = "Sync Knowledge Base";
                              }});
                          }});


                          function updateQueueTable(queue) {{
                              const queueTableBody = document.querySelector('#queue-table tbody');
                              queueTableBody.innerHTML = '';
                              if (!queue || queue.length === 0) {{
                                  queueTableBody.innerHTML = '<tr><td colspan="2">No documents in queue.</td></tr>';
                              }} else {{
                                  queue.forEach(item => {{
                                      queueTableBody.innerHTML += ''
                                          + '<tr>'
                                          +   '<td>' + item.fileName + '</td>'
                                          +   '<td>' + item.status + '</td>'
                                          + '</tr>';
                                  }});
                              }}
                          }}

                          // Chat logic
                          document.getElementById('chat-with-docs-button').addEventListener('click', function() {{
                              document.getElementById('chat-section').style.display = 'block';
                          }});

                          document.getElementById('send-chat').addEventListener('click', function() {{
                              const claimNumber = document.getElementById('claim-number').value;
                              const policyNumber = document.getElementById('policy-number').value;
                              const question = document.getElementById('chat-input').value;

                              if (!question.trim()) return;

                              const chatHistory = document.getElementById('chat-history');
                              chatHistory.innerHTML += '<p><strong>You:</strong> ' + question + '</p>';

                              // setTimeout(() => {{
                              //     chatHistory.innerHTML += '<p><strong>System:</strong> Based on Claim #' +
                              //         claimNumber + ' and Policy #' + policyNumber + ', here is the information you requested...</p>';
                              // }}, 1000);

                              // Call the ask-questions API endpoint 
                              fetch('{api_url}/ask-questions', {{
                                  method: 'POST', // Change to 'POST' if you update API Gateway accordingly.
                                  headers: {{ 'Content-Type': 'application/json' }},
                                  body: JSON.stringify({{
                                      policy_number: policyNumber,
                                      claim_number: claimNumber,
                                      question: question
                                  }})
                              }})
                              .then(response => response.json())
                              .then(data => {{
                                  // Expecting data.response to be the plain JSON answer.
                                  chatHistory.innerHTML += '<p><strong>System:</strong> ' + data.response + '</p>';
                              }})
                              .catch(err => {{
                                  console.error('Error in chat processing:', err);
                              }});
                          }});
                      </script>
                  </body>
                  </html>
                  """
              }


# Step 4: Lambda Function for File Upload
  UploadLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "UploadLambdaFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      Role: !GetAtt UploadLambdaFunctionExecutionRole.Arn
      # Role: !GetAtt LambdaExecutionRole.Arn
      Environment:
        Variables:
          UPLOAD_BUCKET: !Ref DocumentUploadBucket
      Code:
        ZipFile: |
          import json
          import base64
          import boto3
          import os
          import mimetypes

          s3 = boto3.client('s3')
          bucket_name = os.environ['UPLOAD_BUCKET']

          def lambda_handler(event, context):
              try:
                  body = json.loads(event['body'])

                  # Extract image data & file name
                  image_data = body.get('image')
                  file_name = body.get('fileName')  # Ensure fileName is sent in request

                  if not image_data or not file_name:
                      return {
                          'statusCode': 400,
                          'body': json.dumps({'message': 'No image data or file name provided'})
                      }

                  # Extract & validate file extension
                  _, file_extension = os.path.splitext(file_name)
                  if not file_extension:
                      return {
                          'statusCode': 400,
                          'body': json.dumps({'message': 'Invalid file name or extension'})
                      }

                  file_extension = file_extension.lower()  # Normalize extension
                  content_type = mimetypes.guess_type(file_name)[0] or 'application/octet-stream'

                  # Generate a unique key using original file name
                  image_key = f"uploads/{file_name}"  # Keeps the original filename

                  # Decode base64 image data
                  image_data = base64.b64decode(image_data)

                  # Upload to S3 with correct content type
                  s3.put_object(
                      Bucket=bucket_name,
                      Key=image_key,
                      Body=image_data,
                      ContentType=content_type
                  )

                  return {
                      'statusCode': 200,
                      'headers': {
                          'Content-Type': 'application/json',
                          'Access-Control-Allow-Origin': '*',
                          'Access-Control-Allow-Methods': 'OPTIONS,POST',
                          'Access-Control-Allow-Headers': 'Content-Type'
                      },
                      'body': json.dumps({'imageUrl': image_key, 'contentType': content_type})
                  }

              except Exception as e:
                  print(f"Error: {str(e)}")
                  return {
                      'statusCode': 500,
                      'body': json.dumps({'message': 'Error uploading file to S3', 'error': str(e)})
                  }

# Step 4b: Lambda Function to Invoke Supervisor Agent
  InvokeSupervisorLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: InvokeSupervisorLambdaExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: InvokeSupervisorAgentPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - bedrock:InvokeAgent
                Resource: 
                  - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/${DocumentPreprocessingSupervisorAgent}/TSTALIASID"  

  InvokeSupervisorLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "InvokeSupervisorLambdaFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt InvokeSupervisorLambdaExecutionRole.Arn
      Environment:
        Variables:
          BEDROCK_AGENT_ID: !Ref DocumentPreprocessingSupervisorAgent
          BEDROCK_AGENT_ALIAS_ID: "TSTALIASID"
      Timeout: 600
      Code:
        ZipFile: |
          import json
          import boto3
          import os
          import uuid
          import logging
          from botocore.config import Config
          from botocore.exceptions import ClientError

          logger = logging.getLogger()
          logger.setLevel(logging.INFO)
          region = os.environ['AWS_REGION']

          # bedrock_client = boto3.client('bedrock-agent-runtime')



          bedrock_client = boto3.client(
              'bedrock-agent-runtime',
              region_name=region,
              config=Config(connect_timeout=300, read_timeout=300)
          )



          def lambda_handler(event, context):
              logger.info(json.loads(event['body']))
              try:
                  body = json.loads(event['body'])
                  input_text = body.get('inputText')  # This will now handle both classification and preprocessing
                  session_id = body.get('sessionId') or str(uuid.uuid4()) # Generate session ID if not provided
                  agent_id = os.environ['BEDROCK_AGENT_ID']
                  agent_alias_id = os.environ['BEDROCK_AGENT_ALIAS_ID']

                  if not input_text:
                    return {
                        'statusCode': 400,
                        'body': json.dumps({'message': 'No input text provided'})
                    }
                  
                  input_text = input_text+" Response MUST be a JSON Structure. You will not drop any part of the response that comes back from the collaborator agents."
                  logger.info(f"Invoking Supervisor Agent with session_id={session_id}, input_text={input_text}")

                  # Invoke the Bedrock Supervisor Agent
                  try:
                      response = bedrock_client.invoke_agent(
                          agentId=agent_id,
                          agentAliasId=agent_alias_id,
                          sessionId=session_id,
                          inputText=input_text,
                          enableTrace=False,
                          endSession=False
                      )


                      # Process the agent response
                      completion = ""
                      for event in response.get("completion", []):
                          if "chunk" in event:
                              chunk = event["chunk"]
                              completion += chunk["bytes"].decode()

                      logger.info(f"Agent Response: {completion}")

                      return {
                          'statusCode': 200,
                          'headers': {
                              'Content-Type': 'application/json',
                              'Access-Control-Allow-Origin': '*'
                          },
                          'body': json.dumps({
                              'sessionId': session_id,
                              'agentResponse': completion
                          })

                      # return {
                      #     'statusCode': 200,
                      #     'headers': {
                      #         'Content-Type': 'application/json',
                      #         'Access-Control-Allow-Origin': '*',
                      #         'Access-Control-Allow-Methods': 'OPTIONS,POST',
                      #         'Access-Control-Allow-Headers': 'Content-Type'
                      #     },
                      #     'body': json.dumps({
                      #         'sessionId': session_id,
                      #         'agentResponse': "hi"
                      #     })



                      }

                  except ClientError as e:
                      logger.error(f"Error invoking agent: {e}")
                      return {
                          'statusCode': 500,
                          'body': json.dumps({'message': 'Agent invocation failed', 'error': str(e)})
                      }

              except Exception as e:
                  logger.error(f"Unexpected error: {str(e)}")
                  return {
                      'statusCode': 500,
                      'body': json.dumps({'message': 'Internal Server Error', 'error': str(e)})
                  }


# Step 5: Bedrock Supervisor Agent Definition
  DocumentPreprocessingSupervisorAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: DocumentPreprocessingSupervisor
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleSupervisorAgent.Arn
      Instruction: | 
        You are a document routing agent. 
        STRICT RULES:
        - Do not modify or summarize responses from collaborating agents. Forward their JSON responses EXACTLY.
        - Your ONLY job is to forward the request to the appropriate collaboration agent and relay back the response AS-IS.
        PROCESS:
        1. When a request arrives, forward it to the correct collaboration agent.
        2. DO NOT modify or interpret the collaborator's response.
        3. Return the response EXACTLY in its original JSON structure without any additional formatting or commentary.
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: UserInputAction
          ParentActionGroupSignature: AMAZON.UserInput
          ActionGroupState: DISABLED

# Step X: Claims Settlement Demand Package Collaborator Processing Agent
  ClaimsDocumentsPackageProcessingAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: ClaimsDocumentsPackageProcessingAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleProcessDemandPackage.Arn
      Instruction: |
        This agent preprocesses Claims Documentation Packages by converting PDF files to structured Markup Text and attaching relevant metadata.
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: ProcessClaimsDemandPackage
          Description: "Processes Claims Documents Packages and converts them to Markup Text."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Claims Documents Package Processing API",
                  "version": "1.0.0",
                  "description": "Processes Claims Documentation Package documents to structured Markup Text."
                },
                "paths": {
                  "/processdemandpackage": {
                    "post": {
                      "summary": "Processes Claims Documentation Package documents to structured Markup Text.",
                      "description": "Processes Claims Documentation Package documents to structured Markup Text.",
                      "operationId": "processdemandpackage",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "File name of the document that needs to be classified into categories."
                                },
                                "claimNumber": {
                                  "type": "string",
                                  "description": "Claim Number associated with the Claims Documentation Package."
                                },
                                "policyNumber": {
                                  "type": "string",
                                  "description": "Policy Number associated with the Claims Documentation Package."
                                }
                              },
                              "required": ["documentfilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Successful response",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "description": "Status of the processing."
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt ProcessClaimsDemandPackageDocumentsFunction.Arn


# Step X: Classification Collaborator Agent
  ClaimsDocumentsPackageProcessingAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref ClaimsDocumentsPackageProcessingAgent
      AgentAliasName: "ClaimsDocumentsPackageProcessingAgentAlias"



# Step X: Vehicle Repair Estimating  Collaborator Processing Agent
  VehicleRepairEstimateProcessingAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: VehicleRepairEstimateProcessingAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleProcessVehicleRepair.Arn
      Instruction: |
        This agent processes Vehicle Repair Estimate documents.
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: ProcessVehicleRepairEstimate
          Description: "Processes Vehicle Repair Estimate Summary documents to structured Markup Text."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Vehicle Repair Estimate Processing API",
                  "version": "1.0.0",
                  "description": "Processes Vehicle Repair Estimate Summary documents to structured Markup Text. The request includes vehicle and estimator details along with estimate-specific metadata."
                },
                "paths": {
                  "/processrepairestimate": {
                    "post": {
                      "summary": "Processes Vehicle Repair Estimate documents to structured Markup Text.",
                      "description": "Processes Vehicle Repair Estimate Summary documents to structured Markup Text.",
                      "operationId": "processrepairestimate",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "File name of the Vehicle Repair Estimate document."
                                },
                                "claimNumber": {
                                  "type": "string",
                                  "description": "Claim Number associated with the Vehicle Repair Estimate."
                                },
                                "policyNumber": {
                                  "type": "string",
                                  "description": "Policy Number associated with the Vehicle Repair Estimate."
                                },
                                "claimantName": {
                                  "type": "string",
                                  "description": "Claimant Name or Customer Name associated with the Vehicle Repair Estimate."
                                },
                                "estimateDate": {
                                  "type": "string",
                                  "description": "Estimate Date of the Vehicle Repair Estimate."
                                },
                                "dateIn": {
                                  "type": "string",
                                  "description": "Date In for the Vehicle Repair Estimate (if available)."
                                },
                                "dateOut": {
                                  "type": "string",
                                  "description": "Date Out for the Vehicle Repair Estimate (if available)."
                                },
                                "vehicleMake": {
                                  "type": "string",
                                  "description": "Vehicle Make."
                                },
                                "vehicleModel": {
                                  "type": "string",
                                  "description": "Vehicle Model."
                                },
                                "vehicleYear": {
                                  "type": "string",
                                  "description": "Vehicle Year."
                                },
                                "vehicleVIN": {
                                  "type": "string",
                                  "description": "Vehicle VIN."
                                },
                                "licensePlate": {
                                  "type": "string",
                                  "description": "License Plate (if available)."
                                },
                                "estimatorName": {
                                  "type": "string",
                                  "description": "Estimator Name (if available)."
                                },
                                "estimatorContact": {
                                  "type": "string",
                                  "description": "Estimator Contact (if available)."
                                }
                              },
                              "required": ["documentfilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Successful response",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "description": "Status of the processing."
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt ProcessVehicleRepairEstimateDocumentsFunction.Arn

# Step X: VehicleRepairEstimateProcessingAgent Alias
  VehicleRepairEstimateProcessingAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref VehicleRepairEstimateProcessingAgent
      AgentAliasName: "VehicleRepairEstimateProcessingAgentAlias"

# Step X+1: Lambda Function for starting the Vehicle Repair estimate processing function

  ProcessVehicleRepairEstimateDocsLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      # 1) Basic logging
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: InvokeAsyncLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  # 2) Only allow invoking your async processor function
                  - !GetAtt AsyncPDFMarkupTextProcessingLambda.Arn

  ProcessVehicleRepairEstimateDocumentsFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "ProcessVehicleRepairEstimateDocumentsFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt ProcessVehicleRepairEstimateDocsLambdaExecutionRole.Arn
      Timeout: 900
      MemorySize: 4096
      EphemeralStorage: 
        Size: 4096  # 4GB (Increase storage size)
      Environment:
        Variables:
          ASYNC_LAMBDA: !Ref AsyncPDFMarkupTextProcessingLambda
          # BEDROCK_MODEL: "anthropic.claude-3-5-sonnet-20241022-v2:0"  # Claude 3 Model
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os

          # Initialize Lambda client
          lambda_client = boto3.client('lambda')

          # Environment Variables
          ASYNC_LAMBDA = os.environ.get('ASYNC_LAMBDA', 'asyncPDFMarkupTextProcessingLambda')

          # Configure logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
              """
              Main Lambda function to initiate processing of Vehicle Repair Estimate documents.
              Extracts metadata and invokes the async processing Lambda.
              """
              logger.info(f"Event received: {json.dumps(event)}")

              # Extract properties from the event
              properties = event['requestBody']['content']['application/json']['properties']
              
              # Extract specific metadata values
              document_filename = get_property_value(properties, 'documentfilename')
              claim_number = get_property_value(properties, 'claimNumber')
              policy_number = get_property_value(properties, 'policyNumber')
              estimate_date = get_property_value(properties, 'estimateDate')
              date_in = get_property_value(properties, 'dateIn')
              date_out = get_property_value(properties, 'dateOut')
              vehicle_make = get_property_value(properties, 'vehicleMake')
              vehicle_model = get_property_value(properties, 'vehicleModel')
              vehicle_year = get_property_value(properties, 'vehicleYear')
              vehicle_vin = get_property_value(properties, 'vehicleVIN')
              license_plate = get_property_value(properties, 'licensePlate')
              estimator_name = get_property_value(properties, 'estimatorName')
              estimator_contact = get_property_value(properties, 'estimatorContact')
              
              # Hard-code documentType for Vehicle Repair Estimate processing
              documentType = "Vehicle_Repair_Estimate_Summary"
              
              # Collect all metadata dynamically and update with additional fields
              metadata = {prop['name']: prop.get('value', 'N/A') for prop in properties}
              metadata.update({
                  "claimNumber": claim_number,
                  "policyNumber": policy_number,
                  "estimateDate": estimate_date,
                  "dateIn": date_in,
                  "dateOut": date_out,
                  "vehicleMake": vehicle_make,
                  "vehicleModel": vehicle_model,
                  "vehicleYear": vehicle_year,
                  "vehicleVIN": vehicle_vin,
                  "licensePlate": license_plate,
                  "estimatorName": estimator_name,
                  "estimatorContact": estimator_contact
              })

              logger.info(f"Processing document: {document_filename} with metadata: {metadata}")

              # Invoke Async Lambda, passing documentType along with metadata
              invoke_async_lambda(document_filename, metadata, documentType)

              response_body = {
                  'application/json': {
                      'body': {
                          'status': f'Async processing started for {document_filename}.'
                      }
                  }
              }

              return {
                  'messageVersion': '1.0',
                  'response': {
                      'actionGroup': event['actionGroup'],
                      'apiPath': event['apiPath'],
                      'httpMethod': event['httpMethod'],
                      'httpStatusCode': 200,
                      'responseBody': response_body
                  }
              }

          def get_property_value(properties, name):
              """
              Helper function to retrieve a property value by name.
              """
              for prop in properties:
                  if prop.get('name') == name:
                      return prop.get('value', 'N/A')
              return 'N/A'

          def invoke_async_lambda(document_filename, metadata, documentType):
              """
              Invokes the asynchronous Lambda for further processing.
              """
              payload = {
                  'documentfilename': document_filename,
                  'metadata': metadata,
                  'documentType': documentType
              }
              
              response = lambda_client.invoke(
                  FunctionName=ASYNC_LAMBDA,
                  InvocationType='Event',  # Asynchronous invocation
                  Payload=json.dumps(payload)
              )
              logger.info(f"Async Lambda invoked successfully: {response}")

# Step X: Bedrock agent access to invoke the ClassificationExecutorLambda
  ProcessVehicleRepairEstimateDocumentsLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ProcessVehicleRepairEstimateDocumentsFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"  # Use the correct service principal for the Bedrock agent
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${VehicleRepairEstimateProcessingAgent}"

# Step X: Insurance Policy Document Processing Agent
  InsurancePolicyDocumentProcessingAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: InsurancePolicyDocumentProcessingAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleProcessInsurancePolicy.Arn
      Instruction: |
        This agent processes Insurance Policy Document files by extracting key policy details and converting them into structured Markup Text.
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: ProcessInsurancePolicyDocument
          Description: "Processes Insurance Policy Document files to structured Markup Text."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Insurance Policy Document Processing API",
                  "version": "1.0.0",
                  "description": "Processes Insurance Policy Document files to structured Markup Text by extracting key policy details."
                },
                "paths": {
                  "/processpolicydocument": {
                    "post": {
                      "summary": "Processes Insurance Policy Document files to structured Markup Text.",
                      "description": "Processes Insurance Policy Document files to structured Markup Text.",
                      "operationId": "processpolicydocument",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "File name of the Insurance Policy Document."
                                },
                                "policyNumber": {
                                  "type": "string",
                                  "description": "Policy Number associated with the document."
                                },
                                "policyType": {
                                  "type": "string",
                                  "description": "Type of policy (e.g. Auto, Home, Life, etc.)."
                                },
                                "effectiveDate": {
                                  "type": "string",
                                  "description": "Effective date of the policy."
                                },
                                "policyHolderName": {
                                  "type": "string",
                                  "description": "Name of the policy holder."
                                }
                              },
                              "required": ["documentfilename", "policyNumber"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Successful response",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "description": "Status of the processing."
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt ProcessInsurancePolicyDocumentFunction.Arn

# Step X+1: Agent Alias for Insurance Policy Document Processing Agent
  InsurancePolicyDocumentProcessingAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref InsurancePolicyDocumentProcessingAgent
      AgentAliasName: "InsurancePolicyDocumentProcessingAgentAlias"

# Step X+2: Lambda Function to process Insurance Policy Document files
  ProcessInsurancePolicyDocsLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      # 1) Basic logging
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: InvokeAsyncLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  # 2) Only allow invoking your async processor function
                  - !GetAtt AsyncPDFMarkupTextProcessingLambda.Arn

  ProcessInsurancePolicyDocumentFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "ProcessInsurancePolicyDocumentFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt ProcessInsurancePolicyDocsLambdaExecutionRole.Arn
      Timeout: 900
      MemorySize: 4096
      EphemeralStorage:
        Size: 4096  # 4GB storage
      Environment:
        Variables:
          ASYNC_LAMBDA: !Ref AsyncPDFMarkupTextProcessingLambda
          # BEDROCK_MODEL: "anthropic.claude-3-5-sonnet-20241022-v2:0"
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os

          # Initialize the Lambda client
          lambda_client = boto3.client('lambda')
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
              """
              Main Lambda function to initiate processing of Insurance Policy Document files.
              It extracts policy metadata and invokes an asynchronous Lambda for further processing.
              """
              logger.info(f"Event received: {json.dumps(event)}")

              properties = event['requestBody']['content']['application/json']['properties']
              document_filename = get_property_value(properties, 'documentfilename')
              policy_number = get_property_value(properties, 'policyNumber')
              policy_type = get_property_value(properties, 'policyType')
              effective_date = get_property_value(properties, 'effectiveDate')
              policy_holder_name = get_property_value(properties, 'policyHolderName')

              # Hard-code documentType for Insurance Policy Documents
              documentType = "Insurance_Policy_Document"

              # Collect and update metadata
              metadata = {prop['name']: prop.get('value', 'N/A') for prop in properties}
              metadata.update({
                  "policyNumber": policy_number,
                  "policyType": policy_type,
                  "effectiveDate": effective_date,
                  "policyHolderName": policy_holder_name
              })

              logger.info(f"Processing document: {document_filename} with metadata: {metadata}")

              invoke_async_lambda(document_filename, metadata, documentType)

              response_body = {
                  'application/json': {
                      'body': {
                          'status': f'Async processing started for {document_filename}.'
                      }
                  }
              }
              return {
                  'messageVersion': '1.0',
                  'response': {
                      'actionGroup': event['actionGroup'],
                      'apiPath': event['apiPath'],
                      'httpMethod': event['httpMethod'],
                      'httpStatusCode': 200,
                      'responseBody': response_body
                  }
              }

          def get_property_value(properties, name):
              """
              Helper function to retrieve a property value by name.
              """
              for prop in properties:
                  if prop.get('name') == name:
                      return prop.get('value', 'N/A')
              return 'N/A'

          def invoke_async_lambda(document_filename, metadata, documentType):
              """
              Invokes the asynchronous Lambda for further processing.
              """
              payload = {
                  'documentfilename': document_filename,
                  'metadata': metadata,
                  'documentType': documentType
              }
              response = lambda_client.invoke(
                  FunctionName=os.environ.get('ASYNC_LAMBDA'),
                  InvocationType='Event',
                  Payload=json.dumps(payload)
              )
              logger.info(f"Async Lambda invoked successfully: {response}")

# Step X+3: Lambda Permission for Bedrock Agent to invoke the processing Lambda
  ProcessInsurancePolicyDocumentLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ProcessInsurancePolicyDocumentFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${InsurancePolicyDocumentProcessingAgent}"


#####################
# Step X: Insurance Policy Document Processing Agent
  UnclassifiedDocumentProcessingAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: UnclassifiedDocumentProcessingAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleProcessUnclassifiedDoc.Arn
      Instruction: |
        This agent processes Miscellaneous Document files by extracting key  details and converting them into structured Markup Text.
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: ProcessUnclassifiedDocument
          Description: "Processes Miscellaneous Document files to structured Markup Text."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Miscellaneous Document Processing API",
                  "version": "1.0.0",
                  "description": "Processes Miscellaneous Document files to structured Markup Text by extracting key details."
                },
                "paths": {
                  "/processunclassifieddocument": {
                    "post": {
                      "summary": "Processes Miscellaneous Document files to structured Markup Text.",
                      "description": "Processes Miscellaneous Document files to structured Markup Text.",
                      "operationId": "processunclassifieddocument",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "File name of the Miscellaneous Document."
                                }
                              },
                              "required": ["documentfilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Successful response",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "description": "Status of the processing."
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt ProcessUnclassifiedDocumentFunction.Arn

# Step X+1: Agent Alias for Insurance Policy Document Processing Agent
  UnclassifiedDocumentProcessingAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref UnclassifiedDocumentProcessingAgent
      AgentAliasName: "UnclassifiedDocumentProcessingAgentAlias"

# Step X+2: Lambda Function to process Insurance Policy Document files
  ProcessUnclassifiedDocsLambdaExecutionRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service: lambda.amazonaws.com
              Action: sts:AssumeRole

        # 1) Basic logging
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

        Policies:
          - PolicyName: InvokeAsyncLambdaPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - lambda:InvokeFunction
                  Resource:
                    # 2) Only allow invoking your async processor function
                    - !GetAtt AsyncPDFMarkupTextProcessingLambda.Arn

  ProcessUnclassifiedDocumentFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "ProcessUnclassifiedDocumentFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt ProcessUnclassifiedDocsLambdaExecutionRole.Arn
      Timeout: 900
      MemorySize: 4096
      EphemeralStorage:
        Size: 4096  # 4GB storage
      Environment:
        Variables:
          ASYNC_LAMBDA: !Ref AsyncPDFMarkupTextProcessingLambda
          # BEDROCK_MODEL: "anthropic.claude-3-5-sonnet-20241022-v2:0"
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os

          # Initialize the Lambda client
          lambda_client = boto3.client('lambda')
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
              """
              Main Lambda function to initiate processing of Unclassified Document files.
              It extracts policy metadata and invokes an asynchronous Lambda for further processing.
              """
              logger.info(f"Event received: {json.dumps(event)}")

              properties = event['requestBody']['content']['application/json']['properties']
              document_filename = get_property_value(properties, 'documentfilename')

              # Hard-code documentType for Unclassified Documents
              documentType = "Unclassified_Document"

              # Collect and update metadata
              metadata = {prop['name']: prop.get('value', 'N/A') for prop in properties}
              metadata.update({
                  "documentType": documentType
              })

              logger.info(f"Processing document: {document_filename} with metadata: {metadata}")

              invoke_async_lambda(document_filename, metadata, documentType)

              response_body = {
                  'application/json': {
                      'body': {
                          'status': f'Async processing started for {document_filename}.'
                      }
                  }
              }
              return {
                  'messageVersion': '1.0',
                  'response': {
                      'actionGroup': event['actionGroup'],
                      'apiPath': event['apiPath'],
                      'httpMethod': event['httpMethod'],
                      'httpStatusCode': 200,
                      'responseBody': response_body
                  }
              }

          def get_property_value(properties, name):
              """
              Helper function to retrieve a property value by name.
              """
              for prop in properties:
                  if prop.get('name') == name:
                      return prop.get('value', 'N/A')
              return 'N/A'

          def invoke_async_lambda(document_filename, metadata, documentType):
              """
              Invokes the asynchronous Lambda for further processing.
              """
              payload = {
                  'documentfilename': document_filename,
                  'metadata': metadata,
                  'documentType': documentType
              }
              response = lambda_client.invoke(
                  FunctionName=os.environ.get('ASYNC_LAMBDA'),
                  InvocationType='Event',
                  Payload=json.dumps(payload)
              )
              logger.info(f"Async Lambda invoked successfully: {response}")

# Step X+3: Lambda Permission for Bedrock Agent to invoke the processing Lambda
  ProcessUnclassifiedDocumentLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ProcessUnclassifiedDocumentFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${UnclassifiedDocumentProcessingAgent}"

#####################

# Async PDF Markup Text Processing Lambda

  AsyncPDFMarkupTextProcessingLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: AsyncPDFMarkupTextProcessingPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:

              # 1) List and get bucket info
              - Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${PNGFilestUploadBucket}"
                  - !Sub "arn:aws:s3:::${KBDataBucket}"
                  - !Sub "arn:aws:s3:::${InProgressProcessingBucket}"

              # 2) Get, put, and delete objects
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:DeleteObject
                Resource:
                  - !Sub "arn:aws:s3:::${PNGFilestUploadBucket}/*"
                  - !Sub "arn:aws:s3:::${KBDataBucket}/*"
                  - !Sub "arn:aws:s3:::${InProgressProcessingBucket}/*"

              # 3) Invoke the Claude foundation model
              - Effect: Allow
                Action:
                  - bedrock:InvokeModel
                Resource:
                  - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0"

              # 4) Send and delete messages on the processing queue
              - Effect: Allow
                Action:
                  - sqs:SendMessage
                  - sqs:ReceiveMessage
                  - sqs:DeleteMessage
                  - sqs:GetQueueAttributes
                  - sqs:GetQueueUrl
                Resource:
                  - !GetAtt ProcessingQueue.Arn

  AsyncPDFMarkupTextProcessingLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: asyncPDFMarkupTextProcessingLambda
      Handler: index.lambda_handler
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt AsyncPDFMarkupTextProcessingLambdaExecutionRole.Arn
      Runtime: python3.10
      Timeout: 900
      MemorySize: 4096
      Environment:
        Variables:
          PNG_BUCKET: !Ref PNGFilestUploadBucket
          KB_BUCKET: !Ref KBDataBucket
          INPROGRESS_PROCESSING_BUCKET: !Ref InProgressProcessingBucket
          BEDROCK_MODEL_ID: "anthropic.claude-3-5-sonnet-20241022-v2:0"  # Bedrock Model ID
          PROCESSING_QUEUE_URL: !Ref ProcessingQueue  # Add this line
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os
          import urllib.parse
          from botocore.config import Config
          from botocore.exceptions import ClientError

          # Initialize clients
          s3_client = boto3.client('s3')
          lambda_client = boto3.client('lambda')

          # Initialize SQS client
          sqs_client = boto3.client('sqs')

          # Set custom timeout for Bedrock
          config = Config(connect_timeout=300, read_timeout=300)
          bedrock_runtime = boto3.client('bedrock-runtime', region_name=os.environ['AWS_REGION'], config=config)

          # Environment Variables
          PNG_BUCKET = os.environ['PNG_BUCKET']
          KB_BUCKET = os.environ['KB_BUCKET']
          INPROGRESS_BUCKET = os.environ['INPROGRESS_PROCESSING_BUCKET']
          BEDROCK_MODEL_ID = os.environ['BEDROCK_MODEL_ID']
          PROCESSING_QUEUE_URL = os.environ['PROCESSING_QUEUE_URL']

          # Configure logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
              logger.info(f"Event received: {json.dumps(event)}")
              try:
                  if 'Records' in event and 's3' in event['Records'][0]:
                      logger.info("S3 Event detected. Resuming from in-progress status file.")
                      
                      bucket_name = event['Records'][0]['s3']['bucket']['name']
                      key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'])
                      logger.info(f"Fetching state file from S3 bucket: {bucket_name}, key: {key}")

                      state_file_path = f'/tmp/{os.path.basename(key)}'
                      s3_client.download_file(bucket_name, key, state_file_path)
                      logger.info(f"State file downloaded successfully to {state_file_path}")

                      with open(state_file_path, 'r') as file:
                          state_data = json.load(file)
                      
                      document_filename = state_data['documentfilename']
                      document_type = state_data.get('documentType', None)
                      metadata = state_data['metadata']
                      start_page = state_data.get('start_page', 1)

                      logger.info(f"Resuming processing for document: {document_filename}, starting at page: {start_page}, documentType: {document_type}")
                  else:
                      # This was invoked from the Supevisor Agent->Classification Agent->Settlement Demand Agent -> Settlement Demand Lambda -> This (Hence will create entry in Q)
                      logger.info("Direct invocation detected from another Lambda.")
                      document_filename = event['documentfilename']
                      add_to_processing_queue(document_filename) 
                      metadata = event['metadata']
                      start_page = event.get('start_page', 1)
                      document_type = event.get('documentType', None)
                      logger.info(f"Processing new document: {document_filename}, starting at page: {start_page}, documentType: {document_type}")
                  
                  if document_type:
                      logger.info(f"Document type provided: {document_type}")
                  else:
                      logger.info("No document type provided.")

                  base_filename = os.path.splitext(os.path.basename(document_filename))[0]
                  logger.info(f"Base filename extracted: {base_filename}")
                  png_files = list_png_files(PNG_BUCKET, f"processed/{base_filename}_page", start_page)
                  logger.info(f"PNG files to process: {png_files}")

                  # Pass document_type to process_pages
                  process_pages(png_files, base_filename, metadata, context, start_page, document_type)

              except Exception as e:
                  logger.error(f"Error in lambda_handler: {str(e)}")
                  raise


          # Add to SQS Queue indicating processing has started - This is for the Table on UI
          def add_to_processing_queue(document_filename):
              # Extract just the filename without the directory and extension
              base_filename = os.path.splitext(os.path.basename(document_filename))[0]

              message_body = json.dumps({
                  'fileName': base_filename,
                  'status': 'In Progress'
              })
              
              try:
                  sqs_client.send_message(
                      QueueUrl=PROCESSING_QUEUE_URL,
                      MessageBody=message_body
                  )
                  logger.info(f"Added {base_filename} to processing queue.")
              except ClientError as e:
                  logger.error(f"Failed to add {base_filename} to processing queue: {e}")


          def list_png_files(bucket, prefix, start_page):
              """
              Lists PNG files with the pattern processed/{base_filename}_page*.png starting from the specified page.
              """
              logger.info(f"Listing PNG files in bucket: {bucket} with prefix: {prefix}")

              response = s3_client.list_objects_v2(Bucket=bucket, Prefix=prefix)
              if 'Contents' not in response:
                  logger.warning(f"No PNG files found in bucket: {bucket} with prefix: {prefix}")
                  return []

              # Filter and sort PNG files by page number (assuming processed/<filename>_page1.png)
              png_files = [obj['Key'] for obj in response.get('Contents', []) if obj['Key'].endswith('.png')]
              png_files = sorted(png_files, key=lambda x: int(x.split('_page')[-1].split('.png')[0]))

              logger.info(f"Total PNG files found: {len(png_files)}. Processing from page: {start_page}")
              logger.info(f"Sorted PNG files: {png_files}")
              return png_files[start_page - 1:]

          def process_pages(png_files, document_filename, metadata, context, start_page, document_type=None):
              """
              Processes PNG files by transcribing and saving text & metadata.
              The document_type parameter is passed to transcribe_with_bedrock and save_transcription.
              """
              output_folder = f'/tmp/{context.aws_request_id}'
              os.makedirs(output_folder, exist_ok=True)
              logger.info(f"Output folder created at {output_folder}")

              for idx, png_key in enumerate(png_files, start=start_page):
                  local_png_path = os.path.join(output_folder, os.path.basename(png_key))
                  logger.info(f"Downloading PNG file from S3: {png_key} to {local_png_path}")
                  s3_client.download_file(PNG_BUCKET, png_key, local_png_path)

                  logger.info(f"Transcribing PNG file: {local_png_path}")
                  transcription_text, page_classification = transcribe_with_bedrock(local_png_path, document_type)

                  logger.info(f"Saving transcription for page {idx} of document {document_filename}")
                  save_transcription(document_filename, idx, transcription_text, metadata, document_type, page_classification)

                  remaining_time = context.get_remaining_time_in_millis() / 1000
                  logger.info(f"Remaining execution time: {remaining_time} seconds")
                  if remaining_time < 120:
                      logger.info(f"Not enough time left. Saving processing state at page {idx + 1}")
                      save_processing_state(document_filename, idx + 1, metadata, document_type)
                      return


              logger.info(f"All pages processed for document: {document_filename}. Removing Inprogress from SQS queue.")
              remove_from_processing_queue(document_filename)
              logger.info(f"All pages processed for document: {document_filename}. Removing Inprogress from S3 Inprogress bucket.")
              delete_processing_state_file(document_filename)


          def transcribe_with_bedrock(image_path, document_type=None):
              """
              Transcribes the PNG file using Bedrock.
              
              For any document type, the prompt always includes detailed instructions to transcribe
              the text content verbatim using Markdown. Additionally, if document_type is 
              "Claims Documents Package", the prompt is extended with instructions to classify
              the page into one of the specified categories and return JSON output with two keys:
              "transcription" and "pageClassification".

              Returns a tuple: (transcription_text, page_classification). If no classification is
              expected, page_classification is None.
              """
              try:
                  with open(image_path, "rb") as f:
                      image_data = f.read()
                  logger.info(f"Processing image with Bedrock: {image_path}")

                  # The common transcription prompt (must not leave out any instructions)
                  common_prompt = """YOU MUST transcribe the text content without skipping any word from the image page and output in Markdown syntax (not code blocks). YOU MUST Follow these steps:

          1. Examine the provided page carefully.

          2. Identify all elements present in the page, including headers, body text, footnotes, tables, visualizations, captions, page numbers, signatures, form fields, and any other visible text.

          3. Use markdown syntax to format your output:
            - Headings: # for main, ## for sections, ### for subsections, etc.
            - Lists: * or - for bulleted, 1. 2. 3. for numbered
            - Indicate special text formatting (bold, italic, underline) using Markdown syntax

          4. If the element is a table or table of contents
            - Create a markdown table, ensuring every row has the same number of columns
            - Maintain cell alignment as closely as possible
            - Do not split a table into multiple tables
            - If a merged cell spans multiple rows or columns, place the text in the top-left cell and output ' ' for other cells
            - Use | for column separators, |-|-| for header row separators
            - If a cell has multiple items, list them in separate rows
            - If the table contains sub-headers, separate the sub-headers from the headers in another row

          5. For all text elements (paragraphs, headers, footers, footnotes, page numbers, watermarks):
            - Transcribe each text element verbatim as it appears, without skipping any word, number, or character

          6. For signatures:
            - Transcribe them exactly as they appear, including any handwritten or stylized text
            - Place them in square brackets with the title of the signatory, e.g. [Signature: Title]

          7. For form fields:
            - Transcribe all form fields, including checkboxes (use [ ] for unchecked and [X] for checked), text boxes, and any pre-filled or handwritten entries

          8. Preserve all original spacing, including blank lines, indentations, and alignment. Use multiple line breaks if necessary to replicate the original layout.

          9. If any text is unclear or partially obscured, transcribe what you can see and indicate uncertainty with [unclear] or [partially obscured].

          10. Review your transcription to ensure no text or element has been omitted. Your output should be a complete and accurate representation of all visible text in the image.

          11. YOU MUST NOT include any commentary before or after the transcription.

          Output Example:

          A bar chart showing annual sales figures, with the y-axis labeled "Sales ($Million)" and the x-axis labeled "Year". The chart has bars for 2018 ($12M), 2019 ($18M), 2020 ($8M), and 2021 ($22M).
          Figure 3: This chart shows annual sales in millions. The year 2020 was significantly down due to the COVID-19 pandemic.

          # Annual Report

          ## Financial Highlights

          * Revenue: $40M
          * Profit: $12M
          * EPS: $1.25

          | | Year Ended December 31, | |
          | | 2021 | 2022 |
          |-|-|-|
          | Cash provided by (used in): | | |
          | Operating activities | $ 46,327 | $ 46,752 |
          | Investing activities | (58,154) | (37,601) |
          | Financing activities | 6,291 | 9,718 |

          Here is the image."""
                  
                  # If the document is a Claims Documents package, add extra instructions.
                  if document_type == 'Claims_Documents_Package':
                      additional_instructions = """
          Additionally, you MUST classify the page into one of the following categories:
          "health and medical forms", "doctor's note", "pharmacy receipts", "paramedic notes", "xrays", "police report", "lawyers letter", "other injury evidence", "non classified".
          Return your output in JSON format with two keys:
          "transcription": <the transcribed text>,
          "pageClassification": <the identified classification>.
          DO NOT add any extra commentary.
          """
                      user_message = common_prompt + additional_instructions

                  elif document_type == 'Vehicle_Repair_Estimate_Summary':
                      additional_instructions = """
          Additionally, your output MUST adhere to the following:
          - The transcription MUST begin with a new line containing exactly:
          Vehicle Repair Estimate summary
          - Immediately after, provide the complete Markdown transcription of the page.
          - At the end of the transcription, on a new line, output:
          Summary:
          followed by a brief summary of the page.
          DO NOT output any page classification.
          """
                      user_message = common_prompt + additional_instructions
                  else:
                      user_message = common_prompt

                  messages = [{
                      "role": "user",
                      "content": [
                          {"image": {"format": "png", "source": {"bytes": image_data}}},
                          {"text": user_message}
                      ]
                  }]

                  response = bedrock_runtime.converse(
                      modelId=BEDROCK_MODEL_ID,
                      inferenceConfig={"maxTokens": 8192, "temperature": 0.0, "topP": 0.45},
                      messages=messages
                  )
                  output_text = response['output']['message']['content'][0]['text']
                  logger.info(f"Response from Bedrock for {image_path}: {output_text}")

                  if document_type == 'Claims_Documents_Package':
                      try:
                          output_json = json.loads(output_text,strict=False)
                          transcription = output_json.get("transcription", "")
                          page_classification = output_json.get("pageClassification", "non classified")
                          return transcription, page_classification
                      except Exception as e:
                          logger.error(f"Error parsing JSON output for {image_path}: {e}")
                          return output_text, "non classified"
                  else:
                      return output_text, None

              except Exception as e:
                  logger.error(f"Error in transcription for {image_path}: {e}")
                  if document_type == 'Claims_Documents_Package':
                      return "Transcription failed for Claims_Documents_Package.", "non classified"
                  return "Transcription failed.", None



          def save_transcription(document_filename, page_num, transcription_text, metadata, document_type=None, page_classification=None):
              """
              Saves the transcribed text and metadata JSON file to the KB bucket.
              All keys are nested under the "metadataAttributes" key.
              Always includes the page number and documentType.
              If page_classification is provided (for 'Claims Documents Package'), it is also included.
              """
              base_filename = f"{document_filename}_page_{page_num}.txt"
              metadata_filename = f"{base_filename}.metadata.json"

              try:
                  logger.info(f"Saving transcription to S3: {base_filename}")
                  s3_client.put_object(Bucket=KB_BUCKET, Key=base_filename, Body=transcription_text)
                  
                  # Combine the incoming metadata with additional attributes
                  metadata_attributes = metadata.copy() if isinstance(metadata, dict) else {}
                  metadata_attributes.update({
                      "pageNumber": page_num,
                      "documentType": document_type if document_type else "unknown"
                  })
                  if page_classification is not None:
                      metadata_attributes["pageClassification"] = page_classification
                  
                  # All metadata is now nested under "metadataAttributes"
                  metadata_content = {
                      "metadataAttributes": metadata_attributes
                  }
                  
                  logger.info(f"Saving metadata to S3: {metadata_filename} with content: {json.dumps(metadata_content)}")
                  s3_client.put_object(Bucket=KB_BUCKET, Key=metadata_filename, Body=json.dumps(metadata_content))

              except ClientError as e:
                  logger.error(f"Failed to save transcription or metadata for page {page_num}: {e}")



          def save_processing_state(document_filename, start_page, metadata, document_type=None):
              """
              Saves the processing state to the in-progress bucket if Lambda times out.
              The state file now includes documentType along with documentfilename, start_page, and metadata.
              """
              state_content = {
                  "documentfilename": document_filename,
                  "start_page": start_page,
                  "metadata": metadata,
                  "documentType": document_type if document_type else "unknown"
              }
              state_key = f"{document_filename}_state.json"

              try:
                  logger.info(f"Saving processing state to S3: {state_key} with documentType: {document_type}")
                  s3_client.put_object(Bucket=INPROGRESS_BUCKET, Key=state_key, Body=json.dumps(state_content))
              except ClientError as e:
                  logger.error(f"Failed to save processing state: {e}")


          # Function to remove the file from the processing queue
          def remove_from_processing_queue(document_filename):
              try:
                  messages = sqs_client.receive_message(
                      QueueUrl=PROCESSING_QUEUE_URL,
                      MaxNumberOfMessages=10,
                      WaitTimeSeconds=10  # Enable long polling for up to 10 seconds
                  ).get('Messages', [])
                  logger.info(messages)

                  for message in messages:
                      message_body = json.loads(message['Body'])
                      if message_body['fileName'] == document_filename:
                          sqs_client.delete_message(
                              QueueUrl=PROCESSING_QUEUE_URL,
                              ReceiptHandle=message['ReceiptHandle']
                          )
                          logger.info(f"Removed {document_filename} from processing queue.")
                          return  # Exit after deleting the correct message

                  logger.warning(f"No matching message found in the queue for {document_filename}.")

              except ClientError as e:
                  logger.error(f"Failed to remove {document_filename} from processing queue: {e}")

          # Function to delete the file from the S3 inprogress bucket
          def delete_processing_state_file(document_filename):
              """
              Deletes the processing state file from the in-progress S3 bucket.
              """
              state_key = f"{document_filename}_state.json"

              try:
                  logger.info(f"Deleting processing state file from S3: {state_key}")
                  s3_client.delete_object(Bucket=INPROGRESS_BUCKET, Key=state_key)
                  logger.info(f"Successfully deleted processing state file: {state_key}")
              except ClientError as e:
                  logger.error(f"Failed to delete processing state file {state_key}: {e}")


# Add Lambda Permission for S3 to Invoke the AsyncPDFMarkupTextProcessingLambda for inprogress bucket with state file
  AsyncPDFMarkupTextProcessingLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt AsyncPDFMarkupTextProcessingLambda.Arn
      Action: "lambda:InvokeFunction"
      Principal: "s3.amazonaws.com"
      SourceArn: !GetAtt InProgressProcessingBucket.Arn

# S3 Notification setup via custom resource for when a state file is placed in the inprogress bucket
  ConfigureS3NotificationLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: S3NotificationConfigPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutBucketNotificationConfiguration
                  - s3:PutBucketNotification
                  - s3:GetBucketNotificationConfiguration
                  - s3:GetBucketNotification
                Resource:
                  - !Sub "arn:aws:s3:::${InProgressProcessingBucket}"

  S3NotificationLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: configureS3NotificationLambda
      Handler: index.lambda_handler
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt ConfigureS3NotificationLambdaExecutionRole.Arn
      Runtime: python3.10
      Timeout: 300
      Code:
        ZipFile: |
          import json
          import boto3
          import cfnresponse

          def lambda_handler(event, context):
              s3_client = boto3.client('s3')
              print("Received event: " + json.dumps(event, indent=2))
              
              try:
                  # Extract parameters from event
                  bucket_name = event['ResourceProperties']['BucketName']
                  lambda_arn = event['ResourceProperties']['LambdaArn']
                  
                  if event['RequestType'] in ['Create', 'Update']:
                      # Set notification configuration
                      notification_configuration = {
                          'LambdaFunctionConfigurations': [
                              {
                                  'LambdaFunctionArn': lambda_arn,
                                  'Events': ['s3:ObjectCreated:*']
                              }
                          ]
                      }
                      
                      # Apply the notification configuration
                      s3_client.put_bucket_notification_configuration(
                          Bucket=bucket_name,
                          NotificationConfiguration=notification_configuration
                      )
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                  
                  elif event['RequestType'] == 'Delete':
                      # Remove notifications during stack deletion
                      s3_client.put_bucket_notification_configuration(
                          Bucket=bucket_name,
                          NotificationConfiguration={}
                      )
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                      
              except Exception as e:
                  print(f"Error: {str(e)}")
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})

# Custom resource for S3 trigger
  ConfigureS3Notification:
    Type: Custom::S3BucketNotification
    Properties:
      ServiceToken: !GetAtt S3NotificationLambda.Arn
      ServiceTimeout: 300
      BucketName: !Ref InProgressProcessingBucket
      LambdaArn: !GetAtt AsyncPDFMarkupTextProcessingLambda.Arn
            
# Step X+1: Lambda Function for starting the Claims Document Package PDF->TXT Trasformation 

  ProcessClaimsDemandPackageDocsLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      # 1) Basic logging
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: InvokeAsyncLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource:
                  # 2) Only allow invoking your async processor function
                  - !GetAtt AsyncPDFMarkupTextProcessingLambda.Arn

  ProcessClaimsDemandPackageDocumentsFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "ProcessClaimsDemandPackageDocumentsFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt ProcessClaimsDemandPackageDocsLambdaExecutionRole.Arn 
      Timeout: 900
      MemorySize: 4096
      EphemeralStorage: 
        Size: 4096  # 4GB (Increase storage size)
      Environment:
        Variables:
          ASYNC_LAMBDA: !Ref AsyncPDFMarkupTextProcessingLambda
          # BEDROCK_MODEL: "anthropic.claude-3-5-sonnet-20241022-v2:0"  # Claude 3 Model
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os

          # Initialize Lambda client
          lambda_client = boto3.client('lambda')

          # Environment Variables
          ASYNC_LAMBDA = os.environ.get('ASYNC_LAMBDA', 'asyncPDFMarkupTextProcessingLambda')

          # Configure logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
              """
              Main Lambda function to initiate processing of Claims Documents Packages.
              Extracts metadata and invokes the asyncPDFMarkupTextProcessingLambda for further processing.
              """
              logger.info(f"Event received: {json.dumps(event)}")

              # Extract properties from the event
              properties = event['requestBody']['content']['application/json']['properties']
              
              # Extract specific metadata values
              document_filename = get_property_value(properties, 'documentfilename')
              claim_number = get_property_value(properties, 'claimNumber')
              policy_number = get_property_value(properties, 'policyNumber')
              documentType = "Claims_Documents_Package"
              
              # Collect all metadata dynamically
              metadata = {prop['name']: prop.get('value', 'N/A') for prop in properties}

              logger.info(f"Processing document: {document_filename} with metadata: {metadata}")

              # Invoke Async Lambda
              invoke_async_lambda(document_filename, metadata, documentType)

              response_body = {
                  'application/json': {
                      'body': {
                          'status': f'Async processing started for {document_filename}.'
                      }
                  }
              }

              return {
                  'messageVersion': '1.0',
                  'response': {
                      'actionGroup': event['actionGroup'],
                      'apiPath': event['apiPath'],
                      'httpMethod': event['httpMethod'],
                      'httpStatusCode': 200,
                      'responseBody': response_body
                  }
              }

          def get_property_value(properties, name):
              """
              Helper function to get property value by name.
              """
              for prop in properties:
                  if prop.get('name') == name:
                      return prop.get('value', 'N/A')
              return 'N/A'

          def invoke_async_lambda(document_filename, metadata, documentType):
              """
              Invokes the asynchronous Lambda for processing the document.
              """
              payload = {
                  'documentfilename': document_filename,
                  'metadata': metadata,
                  'documentType': documentType
              }
              
              response = lambda_client.invoke(
                  FunctionName=ASYNC_LAMBDA,
                  InvocationType='Event',  # Async invocation
                  Payload=json.dumps(payload)
              )
              logger.info(f"Async Lambda invoked successfully: {response}")



# Step X: Bedrock agent access to invoke the ClassificationExecutorLambda
  ProcessClaimsDemandPackageDocumentsLambdaPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ProcessClaimsDemandPackageDocumentsFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"  # Use the correct service principal for the Bedrock agent
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${ClaimsDocumentsPackageProcessingAgent}"

# Step X: Vehicle Damage Analysis Processing Agent
  VehicleDamageAnalysisProcessingAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: VehicleDamageAnalysisProcessingAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleProcessVehicleDamageImage.Arn
      Instruction: |
        This agent processes vehicle damage images for insurance claims.
      FoundationModel: "anthropic.claude-3-5-sonnet-20241022-v2:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: VehicleDamageAnalysis
          Description: "Performs vehicle damage analysis on claim submission images."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Vehicle Damage Analysis API",
                  "version": "1.0.0",
                  "description": "Analyzes vehicle damage from images and stores results with metadata."
                },
                "paths": {
                  "/analyzevehicledamage": {
                    "post": {
                      "summary": "Performs vehicle damage analysis.",
                      "description": "Analyzes vehicle damage images and generates summaries.",
                      "operationId": "analyzevehicledamage",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "The filename of the vehicle damage image in S3."
                                },
                                "claimNumber": {
                                  "type": "string",
                                  "description": "Claim number associated with the vehicle damage."
                                },
                                "policyNumber": {
                                  "type": "string",
                                  "description": "Policy number associated with the vehicle damage."
                                },
                                "summary": {
                                  "type": "string",
                                  "description": "summary of the vehicle damages."
                                }
                              },
                              "required": ["documentfilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Successful response",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "description": "Status of the processing."
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt VehicleDamageAnalysisProcessingFunction.Arn

# Step X: Agent Alias for VehicleDamageAnalysis
  VehicleDamageAnalysisAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref VehicleDamageAnalysisProcessingAgent
      AgentAliasName: "VehicleDamageAnalysisAgentAlias"

# Step X: VehicleDamageAnalysisProcessingFunction Lambda
  VehicleDamageAnalysisProcessingLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: VehicleDamageAnalysisProcessingRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: VehicleDamageAnalysisS3Access
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              # 1) List and get bucket info
              - Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${KBDataBucket}"

              # 2) Get and put objects
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                Resource:
                  - !Sub "arn:aws:s3:::${KBDataBucket}/*"

  VehicleDamageAnalysisProcessingFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "VehicleDamageAnalysisProcessingFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt VehicleDamageAnalysisProcessingLambdaExecutionRole.Arn
      Timeout: 900
      MemorySize: 4096
      EphemeralStorage: 
        Size: 4096  # 4GB storage for temporary files
      Environment:
        Variables:
          KB_BUCKET: !Ref KBDataBucket
          # BEDROCK_MODEL: "anthropic.claude-3-5-sonnet-20241022-v2:0"  # Model ID (not needed for this version but included for future use)
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os
          import re
          from botocore.config import Config

          # Configure logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          # AWS Clients
          s3_client = boto3.client("s3")

          # Environment Variables
          KB_BUCKET = os.environ["KB_BUCKET"]

          def get_property_value(properties, name):
              for prop in properties:
                  if prop.get("name") == name:
                      return prop.get("value", "N/A")
              return "N/A"

          def extract_properties(event):
              properties = event['requestBody']['content']['application/json']['properties']
              document_filename = get_property_value(properties, 'documentfilename')
              claim_number = get_property_value(properties, 'claimNumber')
              policy_number = get_property_value(properties, 'policyNumber')
              date_of_loss = get_property_value(properties, 'dateOfLoss')
              summary = get_property_value(properties, 'summary')

              metadata = {
                  "documentfilename": document_filename,
                  "claimNumber": claim_number,
                  "policyNumber": policy_number,
                  "dateOfLoss": date_of_loss
              }

              logger.info(f"Extracted Metadata: {json.dumps(metadata, indent=4)}")
              return document_filename, summary, metadata

          def sanitize_filename(filename):
              base_name = re.sub(r'.*/', '', filename)  # Remove folder paths
              return re.sub(r'\.[^.]+$', '', base_name)  # Remove extension

          def store_results(bucket, key, content):
              try:
                  s3_client.put_object(
                      Bucket=bucket,
                      Key=key,
                      Body=content.encode("utf-8")
                  )
                  logger.info(f"Stored results at: s3://{bucket}/{key}")
              except Exception as e:
                  logger.error(f"Error storing results to S3: {e}", exc_info=True)

          def store_metadata(bucket, key, metadata):
              try:
                  metadata_content = {"metadataAttributes": metadata}
                  logger.info(f"Saving metadata to S3: {key}")
                  s3_client.put_object(
                      Bucket=bucket,
                      Key=key,
                      Body=json.dumps(metadata_content, indent=4)
                  )
                  logger.info(f"Metadata stored at: s3://{bucket}/{key}")
              except Exception as e:
                  logger.error(f"Error storing metadata to S3: {e}", exc_info=True)

          def lambda_handler(event, context):
              try:
                  logger.info(f"Received event: {json.dumps(event)}")

                  # Extract properties
                  document_filename, summary, metadata = extract_properties(event)
                  if not document_filename or not summary:
                      return {
                          "httpStatusCode": 400, 
                          "body": "Invalid request: Missing document filename or summary"
                      }

                  # Generate unique directory
                  request_id = context.aws_request_id
                  temp_dir = f"/tmp/{request_id}"
                  os.makedirs(temp_dir, exist_ok=True)

                  # Prepare filenames
                  base_name = sanitize_filename(document_filename)
                  text_key = f"{base_name}.txt"
                  metadata_key = f"{base_name}.txt.metadata.json"

                  # Store analysis results from the provided summary
                  combined_text = f"Vehicle Damage Image Analysis for Claim Submission:\n{summary}\n"
                  store_results(KB_BUCKET, text_key, combined_text)

                  # Store specific metadata
                  store_metadata(KB_BUCKET, metadata_key, metadata)

                  # Add sync KB function

                  # Build response
                  response_body = {
                      "application/json": {
                          "body": json.dumps({
                              "status": "Image analysis completed",
                              "textFile": text_key,
                              "metadataFile": metadata_key
                          })
                      }
                  }

                  return {
                      "messageVersion": "1.0",
                      "response": {
                          "actionGroup": event.get("actionGroup", ""),
                          "apiPath": event.get("apiPath", ""),
                          "httpMethod": event.get("httpMethod", ""),
                          "httpStatusCode": 200,
                          "responseBody": response_body
                      },
                      "sessionAttributes": event.get("sessionAttributes", {}),
                      "promptSessionAttributes": event.get("promptSessionAttributes", {})
                  }

              except Exception as e:
                  logger.error(f"Lambda processing error: {e}", exc_info=True)
                  error_response = {
                      "application/json": {
                          "body": json.dumps({
                              "error": str(e),
                              "message": "An error occurred during image analysis processing"
                          })
                      }
                  }
                  return {
                      "messageVersion": "1.0",
                      "response": {
                          "httpStatusCode": 500,
                          "responseBody": error_response
                      }
                  }


# Step 3: Lambda Permissions for VehicleDamageAnalysisProcessingFunction
  VehicleDamageAnalysisLambdaPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt VehicleDamageAnalysisProcessingFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${VehicleDamageAnalysisProcessingAgent}"

# Step X: AudioVideoTranscriptProcessingAgent Process Audio Video transcript of claims related recordings
  AudioVideoTranscriptProcessingAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: AudioVideoTranscriptProcessingAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockagentExecutionRoleForAudioVideoProcess.Arn
      Instruction: |
        This agent processes Audio-Video Transcript of claim-related recordings.
      FoundationModel: "anthropic.claude-3-5-sonnet-20241022-v2:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: ProcessAudioVideoTranscript
          Description: "Processes metadata from Audio-Video Transcript and stores it in S3."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Audio-Video Transcript Processing API",
                  "version": "1.0.0",
                  "description": "Processes Audio-Video transcripts."
                },
                "paths": {
                  "/processaudiovideotranscript": {
                    "post": {
                      "summary": "Processes Audio-Video Transcripts.",
                      "description": "Processes Audio-Video transcripts of claim related recordings",
                      "operationId": "processaudiovideotranscript",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "File name of the audio-video transcript document."
                                },
                                "claimNumber": {
                                  "type": "string",
                                  "description": "Claim Number from the transcript."
                                },
                                "policyNumber": {
                                  "type": "string",
                                  "description": "Policy Number from the transcript."
                                },
                                "dateOfLoss": {
                                  "type": "string",
                                  "description": "Date of Loss from the transcript."
                                }
                              },
                              "required": ["documentfilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Successful response",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "description": "Status of the processing."
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt ProcessAudioVideoTranscriptLambdaFunction.Arn

# Step X: AudioVideoTranscriptProcessing Agent Alias
  AudioVideoTranscriptProcessingAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref AudioVideoTranscriptProcessingAgent
      AgentAliasName: "AudioVideoTranscriptProcessingAgentAlias"

# Step X: AudioVideoTranscriptLambda Function
  ProcessAudioVideoTranscriptLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ProcessAudioVideoTranscriptLambdaExecutionRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: ProcessAudioVideoTranscriptS3Access
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              # 1) List and get bucket info
              - Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${KBDataBucket}"

              # 2) Get and put objects
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                Resource:
                  - !Sub "arn:aws:s3:::${KBDataBucket}/*"

  ProcessAudioVideoTranscriptLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "ProcessAudioVideoTranscriptLambdaFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt ProcessAudioVideoTranscriptLambdaExecutionRole.Arn
      Timeout: 900
      MemorySize: 4096
      EphemeralStorage: 
        Size: 4096  # 4GB storage
      Environment:
        Variables:
          OUTPUT_BUCKET: !Ref KBDataBucket
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os

          # Configure logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          # AWS Clients
          s3_client = boto3.client("s3")

          # Environment Variables
          OUTPUT_BUCKET = os.environ["OUTPUT_BUCKET"]

          def get_property_value(properties, name):
              """
              Helper function to get property value by name.
              """
              for prop in properties:
                  if prop.get("name") == name:
                      return prop.get("value", "N/A")
              return "N/A"

          def extract_properties(event):
              """
              Extracts specific metadata properties from the event.
              """
              properties = event['requestBody']['content']['application/json']['properties']

              document_filename = get_property_value(properties, 'documentfilename')
              claim_number = get_property_value(properties, 'claimNumber')
              policy_number = get_property_value(properties, 'policyNumber')
              date_of_loss = get_property_value(properties, 'dateOfLoss')

              metadata = {
                  "documentfilename": document_filename,
                  "claimNumber": claim_number,
                  "policyNumber": policy_number,
                  "dateOfLoss": date_of_loss
              }

              logger.info(f"Extracted Metadata: {json.dumps(metadata, indent=4)}")
              return document_filename, metadata

          def sanitize_filename(filename):
              """
              Removes folder paths and extensions from a filename.
              """
              import re
              base_name = re.sub(r'.*/', '', filename)  # Remove folder paths
              return re.sub(r'\.[^.]+$', '', base_name)  # Remove extension

          def store_metadata(bucket, key, metadata):
              """
              Stores metadata to S3 as a JSON file.
              """
              try:
                  metadata_content = {"metadataAttributes": metadata}
                  logger.info(f"Saving metadata to S3: {key}")
                  s3_client.put_object(
                      Bucket=bucket,
                      Key=key,
                      Body=json.dumps(metadata_content, indent=4)
                  )
                  logger.info(f"Metadata stored at: s3://{bucket}/{key}")
              except Exception as e:
                  logger.error(f"Error storing metadata to S3: {e}", exc_info=True)

          def lambda_handler(event, context):
              """
              Lambda handler for processing Audio-Video Transcript metadata.
              """
              try:
                  logger.info(f"Received event: {json.dumps(event)}")

                  # Extract properties
                  document_filename, metadata = extract_properties(event)
                  if not document_filename:
                      return {
                          "httpStatusCode": 400, 
                          "body": "Invalid request: Missing document filename"
                      }

                  # Generate unique directory
                  request_id = context.aws_request_id
                  temp_dir = f"/tmp/{request_id}"
                  os.makedirs(temp_dir, exist_ok=True)

                  # Prepare metadata filename
                  base_name = sanitize_filename(document_filename)
                  metadata_key = f"{base_name}.txt.metadata.json"

                  # Store metadata in S3
                  store_metadata(OUTPUT_BUCKET, metadata_key, metadata)

                  # Add async process to sync KB

                  # Build response
                  response_body = {
                      "application/json": {
                          "body": json.dumps({
                              "status": "Audio-Video Transcript metadata processing completed",
                              "metadataFile": metadata_key
                          })
                      }
                  }

                  return {
                      "messageVersion": "1.0",
                      "response": {
                          "actionGroup": event.get("actionGroup", ""),
                          "apiPath": event.get("apiPath", ""),
                          "httpMethod": event.get("httpMethod", ""),
                          "httpStatusCode": 200,
                          "responseBody": response_body
                      },
                      "sessionAttributes": event.get("sessionAttributes", {}),
                      "promptSessionAttributes": event.get("promptSessionAttributes", {})
                  }

              except Exception as e:
                  logger.error(f"Lambda processing error: {e}", exc_info=True)
                  error_response = {
                      "application/json": {
                          "body": json.dumps({
                              "error": str(e),
                              "message": "An error occurred during Audio-Video Transcript metadata processing"
                          })
                      }
                  }
                  return {
                      "messageVersion": "1.0",
                      "response": {
                          "httpStatusCode": 500,
                          "responseBody": error_response
                      }
                  }

# Step X: AudioVideoTranscriptLambdaFunction Permission
  AudioVideoTranscriptLambdaPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ProcessAudioVideoTranscriptLambdaFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${AudioVideoTranscriptProcessingAgent}"


# Step X: Bedrock agent to Stadardize documents
  DocumentConversionAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: DocumentConversionAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleDocConversion.Arn
      Instruction: |
        You are a document conversion agent.
        INSTRUCTIONS:
        1. When you receive a document in .docx, .ppt, or .xlsx format, convert it to a PDF.
        2. Return the converted document's PDF filename in the response.
        3. Do not modify, summarize, or interpret the document content.
        4. The response MUST be in the following JSON format:
          {
            "documentfilename": "filename.pdf"
          }
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: ConvertToPDF
          Description: "Converts documents (.docx, .ppt, .xlsx) to PDF format."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Document Conversion API",
                  "version": "1.0.0",
                  "description": "Converts documents like doc, docx, xls,xlsx,excel,ppt to PDF format."
                },
                "paths": {
                  "/convertToPDF": {
                    "post": {
                      "summary": "Convert document to PDF",
                      "description": "Converts the uploaded document like word documents(doc,docx), excel files( xlsx,xlsx,excel), power point (ppt)  to PDF format.",
                      "operationId": "convertToPDF",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "File name of the document to convert to PDF."
                                }
                              },
                              "required": ["documentfilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "PDF conversion successful",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "documentfilename": {
                                    "type": "string",
                                    "description": "File name of the document that needs to be classified into categories."
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt StandardizationToPDFLambdaFunction.Arn

# Step X: Documentconversion Collaborator Agent
  DocumentConversionAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref DocumentConversionAgent
      AgentAliasName: "DocumentConversionAgentAlias"


# Permission for DocumentConversionAgent Bedrock Agent to invoke StandardizationToPDFLambda Function
  StandardizationToPDFLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt StandardizationToPDFLambdaFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${DocumentConversionAgent}"


# Step X:  DocumentClassificationAgent
  DocumentClassificationAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: DocumentClassificationAgent
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleDocClassification.Arn
      Instruction: |
        You are responsible for classifying documents which are exclusively in PDF or Image format (PNG/JPEG).
        Your task is to:
        1. Classify the document into categories such as Claims Documents Package, Insurance Policy Document, Standard Operating Procedure, Vehicle Damage Images, or Chat Transcripts.
        2. Extract and return metadata associated with the identified document category.
        3. Provide a `"nextSteps"` field in the JSON response with recommendations for follow-up actions based on the document type.
        STRICT RULES:
        * Return responses ONLY in JSON format.
        * Include `"nextSteps"` for all types of Documents.
        * Format all output with keys `"category"`, `"confidence"`, `"extractedData"`, `"nextSteps"` and  `"documentPath"`.
        * DO NOT return partial JSON or exclude fields. Always return all keys, even if some values are null.
        * If `"extractedData"` has additional metadata fields, include them in `"extractedData"` as-is.    
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
      ActionGroups:
        - ActionGroupName: DocumentCategoryClassification
          Description: "Classifies uploaded documents into predefined categories."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Document Category Classification API",
                  "version": "1.0.0",
                  "description": "Classifies documents into specific categories."
                },
                "paths": {
                  "/classifyDocumentCategory": {
                    "post": {
                      "summary": "Classify a document into predefined categories",
                      "description": "Classifies the document and returns structured metadata associated with the identified document category.",
                      "operationId": "classifyDocumentCategory",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "documentfilename": {
                                  "type": "string",
                                  "description": "File name of the document that needs to be classified into categories."
                                }
                              },
                              "required": ["documentfilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Classification result",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "category": {"type": "string"},
                                  "confidence": {"type": "number"},
                                  "extractedData": {"type": "object"},
                                  "nextSteps": {
                                    "type": "array",
                                    "items": {"type": "string"},
                                    "description": "Recommended next steps based on document classification."
                                  },
                                  "documentPath": {"type": "string"}
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor: 
            Lambda: !GetAtt ClassificationExecutorLambdaFunction.Arn


# Step X: DocumentClassificationAgentAlias
  DocumentClassificationAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref DocumentClassificationAgent
      AgentAliasName: "DocClassificationAgentAlias"

# Step X+1: Lambda Function for Classification Executor , this is invoked by ClassificationCollaborator Agent.
  ClassificationExecutorLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ClassificationExecutorLambdaRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      # 1) Basic logging
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      # 2–4) Inline policy for S3 & Bedrock
      Policies:
        - PolicyName: ClassificationExecutorPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:

              # 2) List & get bucket info on both buckets
              - Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:GetBucketLocation
                Resource:
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}"
                  - !Sub "arn:aws:s3:::${PNGFilestUploadBucket}"

              # 3) Read objects from both buckets
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                Resource:
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}/*"
                  - !Sub "arn:aws:s3:::${PNGFilestUploadBucket}/*"

              # 4) Invoke the Claude foundation model
              - Effect: Allow
                Action:
                  - bedrock:InvokeModel
                Resource:
                  - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0"

  ClassificationExecutorLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "ClassificationExecutorLambdaFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt ClassificationExecutorLambdaExecutionRole.Arn
      Timeout: 900
      MemorySize: 4096
      EphemeralStorage: 
        Size: 4096  # 4GB (Increase storage size)
      Layers:
        # - arn:aws:lambda:us-west-2:477713232171:layer:pymupdfpillow310:1  # PyMuPDF + Pillow for PDF
        # - arn:aws:lambda:us-west-2:477713232171:layer:new-pymupdfpillow310:1 # PyMuPDF + Pillow for PDF
        - !Ref PYMUPDFPillowLambdaLayer # PyMuPDF + Pillow for PDF
      Environment:
        Variables:
          DOCUMENTUPLOAD_BUCKET: !Ref DocumentUploadBucket  # S3 bucket for uploaded images
          PNG_FILEBUCKET: !Ref PNGFilestUploadBucket # S3 bucket for png images
          CONFIDENCE_THRESHOLD: "0.85"  # Stop processing when confidence is high
          MAX_PAGES_TO_PROCESS: "5"  # Max pages to analyze
          BEDROCK_MODEL: "anthropic.claude-3-5-sonnet-20241022-v2:0"  # Claude 3 Model
      Code:
        ZipFile: |
          import json
          import logging
          import boto3
          import os
          import fitz  # PyMuPDF for PDF processing
          import mimetypes
          from PIL import Image
          from botocore.config import Config

          # Configure logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          # Initialize AWS clients
          s3_client = boto3.client("s3")
          config = Config(connect_timeout=300, read_timeout=300)
          bedrock_runtime = boto3.client("bedrock-runtime", region_name=os.environ["AWS_REGION"], config=config)

          # Load environment variables
          DOCUMENTUPLOAD_BUCKET = os.environ["DOCUMENTUPLOAD_BUCKET"]
          PNG_FILEBUCKET = os.environ["PNG_FILEBUCKET"]  # New S3 bucket for PNGs
          CONFIDENCE_THRESHOLD = float(os.environ["CONFIDENCE_THRESHOLD"])
          MAX_PAGES_TO_PROCESS = int(os.environ["MAX_PAGES_TO_PROCESS"])
          MODEL_ID = os.environ["BEDROCK_MODEL"]

          ### **Utility Functions**

          # Extract 'documentfilename' from event
          def extract_document_filename(event):
              try:
                  properties_list = event["requestBody"]["content"]["application/json"]["properties"]
                  
                  for prop in properties_list:
                      if prop.get("name") == "documentfilename":
                          filename = prop.get("value", "")
                          
                          # Add 'uploads/' prefix if not already present
                          if not filename.startswith("uploads/"):
                              filename = f"uploads/{filename}"
                          
                          logger.info(f"Extracted document filename: {filename}")
                          return filename
                          
                  logger.warning("Document filename not found in request.")
                  return ""
                  
              except Exception as e:
                  logger.error(f"Error extracting document filename: {str(e)}")
                  return ""

          # Convert PDF to PNG and upload to S3
          def pdf_to_png_and_upload(pdf_file, document_filename, context, max_pages=None):
              """
              Converts PDF pages to PNG images and uploads them to `PNG_FILEBUCKET`
              with filenames in the format: `<original_filename>_page<n>.png`
              """
              try:
                  base_filename = ".".join(document_filename.split('/')[-1].split('.')[:-1])  # Handles multiple dots
                  output_folder = f"/tmp/png-output-{context.aws_request_id}/"
                  os.makedirs(output_folder, exist_ok=True)

                  logger.info(f"Processing PDF: {pdf_file}, saving PNGs in: {output_folder}")

                  pdf_document = fitz.open(pdf_file)
                  total_pages = len(pdf_document)  # Get total number of pages
                  max_pages = max_pages if max_pages else total_pages  # Ensure all pages are processed

                  image_keys = []

                  for page_num in range(min(len(pdf_document), max_pages)):
                      page = pdf_document.load_page(page_num)
                      zoom_matrix = fitz.Matrix(2.0, 2.0)
                      pix = page.get_pixmap(matrix=zoom_matrix, alpha=False)

                      # Generate PNG filename
                      png_filename = f"{base_filename}_page{page_num+1}.png"
                      image_path = os.path.join(output_folder, png_filename)
                      pix.save(image_path)
                      resize_image(image_path)

                      # Upload PNG to S3
                      s3_key = f"processed/{png_filename}"
                      logger.info(f"File will be uploaded to S3 from :: {image_path}")
                      logger.info(f"S3 path is  :: {s3_key}")
                      s3_client.upload_file(image_path, PNG_FILEBUCKET, s3_key)
                      logger.info(f"Uploaded: {image_path} -> S3://{PNG_FILEBUCKET}/{s3_key}")

                      image_keys.append(image_path)

                  pdf_document.close()
                  return image_keys  # Return uploaded PNG file paths

              except Exception as e:
                  logger.error(f"Error converting and uploading PNGs: {str(e)}")
                  return []

          # Resize images before processing
          def resize_image(image_path, max_width=1024, max_height=1024):
              try:
                  img = Image.open(image_path)
                  img.thumbnail((max_width, max_height))
                  img.save(image_path)
                  logger.info(f"Resized image path is : {image_path}")
              except Exception as e:
                  logger.error(f"Error resizing image {image_path}: {str(e)}")

          # Process a page using Bedrock AI
          def process_page_with_bedrock(image_path, model_id):
              try:
                  with open(image_path, "rb") as f:
                      image = f.read()
                  logger.info(f"Processing image with Bedrock: {image_path}")

                  user_message = """
                      Summarize the information on this page. In addition to the summary, extract any available key information such as:
                      - Claim Number
                      - Policy Number
                      - Claimant Name
                      - Policyholder Name
                      - Date of Loss
                      - Policy Start Date
                      - Vehicle Details (if available)
                      - Repair components with costs
                      If a particular field is not found, indicate it as 'Not Available'.
                  """

                  messages = [{"role": "user", "content": [{"image": {"format": "png", "source": {"bytes": image}}}, {"text": user_message}]}]
                  summary = invoke_bedrock_model(image_path, messages, model_id)
                  print(f"Summary of page is :: {summary}")
                  return summary

              except Exception as e:
                  logger.error(f"Error processing page with Bedrock: {str(e)}")
                  return ""

          # Invoke Bedrock model
          def invoke_bedrock_model(image_path, messages, model_id, max_retries=5):
              retries = 0
              while retries < max_retries:
                  try:
                      response = bedrock_runtime.converse(modelId=model_id, inferenceConfig={"maxTokens": 8192, "temperature": 0.0, "topP": 0.45}, messages=messages)
                      return response["output"]["message"]["content"][0]["text"]
                  except Exception as e:
                      if "image exceeds" in str(e):
                          resize_image(image_path)
                      else:
                          raise e
                  retries += 1
              logger.error(f"Max retries exceeded for image {image_path}")
              return ""

          # Classify document based on summary text
          def classify_document(summary):
              try:
                  logger.info("Classifying document based on summary")
                  
                  # Main classification prompt
                  classification_prompt = f"""
                      You are an expert in insurance document classification. Based on the provided document summary, classify it into one of the following categories:

                      ### **Categories and Their Expected Contents**:
                      1. **Claims Documents Package**  
                          - A formal request for payment related to an insurance claim.  
                          - Typically contains more than one of the below mentioned documents:
                              - Claimant's medical records
                              - Police reports, accident reports
                              - Repair estimates, invoices, bills
                              - Legal correspondence (letters from attorneys)
                              - Adjuster notes

                      2. **Vehicle Repair Estimate Summary**:
                          - A detailed report outlining the estimated costs and scope of repairs for a damaged vehicle.
                          - Typically contains:
                          - Vehicle information (make, model, year, VIN)
                          - An itemized breakdown of repair work including parts, labor, and any additional fees
                          - Damage assessments and, optionally, supporting photos or diagrams
                          - Summary totals with subtotals for each repair component
                          - Recommendations or notes from the estimator

                      3. **Policy Document of Insurance**  
                          - A legally binding document outlining an insurance policy's terms and coverage.  
                          - Typically contains:
                              - Policyholder details
                              - Coverage limits, exclusions, and endorsements
                              - Terms of renewal and cancellation
                              - Premium payment details

                      4. **Vehicle Damage Photos**  
                          - A collection of images documenting vehicle damage for claims assessment.  
                          - Typically includes:
                              - Exterior and interior images of the vehicle
                              - Close-up shots of damaged parts
                              - VIN (Vehicle Identification Number)
                              - Accident scene photos

                      5. **Standard Operating Procedure (SOP) for Customer Service Reps**  
                          - Internal guidelines detailing customer service procedures for handling claims, inquiries, and escalations.  
                          - Typically contains:
                              - Step-by-step claim processing instructions
                              - Scripts for customer interactions
                              - Dispute resolution guidelines
                              - Compliance and regulatory policies

                      6. **Chat Transcript for Analysis**  
                          - A conversation log between an insurance agent and a customer, used for service quality analysis.  
                          - Typically includes:
                              - Conversations related to claim inquiries, policy details, and disputes
                              - Customer service responses and resolutions
                              - Timestamped interactions

                      7. **Miscellaneous**  
                        - For documents that don't adequately match any of the above categories classify them as miscellaneous.
                      ---

                      ### **Here is the provided document summary**
                      {summary}

                      ---

                      ### **Instructions**
                      - Carefully review the provided document summary and determine the most appropriate category.
                      - If the summary contains keywords or indicators of multiple categories, assign the **most specific** category.
                      - If uncertain, classify it under the closest possible match.
                      - Return a JSON object with:
                      - `"category"`: The classified document type.
                      - `"confidence"`: A confidence score (0-1) indicating classification certainty.
                      - Only return the JSON output.

                      **Example Output JSON:**
                      {{
                          "category": "Claims Documents Package",
                          "confidence": 0.92
                      }}
                  """

                  # Invoke Bedrock for classification
                  logger.info(f"Classification prompt: {classification_prompt}")
                  messages = [{"role": "user", "content": [{"text": classification_prompt}]}]
                  classification_response = invoke_bedrock_model("", messages, MODEL_ID)
                  logger.info(f"Classification Response: {classification_response}")

                  classification_result = json.loads(classification_response)

                  # Extract specific data if classified as Claims Documents Package
                  if classification_result["category"] == "Claims Documents Package":
                      extraction_prompt = f"""
                          You have classified this document as a **Claims Documents Package**.
                          
                          Now, extract the following details from the provided document summary:
                          - Claim Number
                          - Policy Number
                          - Date of Loss
                          - Claimant Name

                          If any of these details are missing or cannot be identified, return them as `null`.

                          ---

                          ### **Document Summary:**
                          {summary}

                          ---

                          Return the extracted information as a JSON object in this format:
                          {{
                              "Claim Number": "123456789",
                              "Policy Number": "POL987654",
                              "Date of Loss": "2023-11-15",
                              "Claimant Name": "John Doe"
                          }}
                      """

                      logger.info(f"Extraction prompt: {extraction_prompt}")
                      extraction_messages = [{"role": "user", "content": [{"text": extraction_prompt}]}]
                      extraction_response = invoke_bedrock_model("", extraction_messages, MODEL_ID)
                      logger.info(f"Extraction Response: {extraction_response}")

                      extracted_data = json.loads(extraction_response)

                      # Combine classification and extracted data
                      return {
                          "category": classification_result["category"],
                          "confidence": classification_result["confidence"],
                          "extractedData": extracted_data,
                          "nextSteps": [
                              "Perform Mandatory Documents Check: Police Report, Medical Bills, Adjuster Notes, Lawyer's Notes, Witness Statements",
                              "Page-level Classification: Police Report, Medical Bills, Doctor's Notes, Lawyer's Notes, Witness Statements",
                              "Attach Metadata to Each Page: Policy Number, Date of Loss, Claimant Name",
                              "Indexing & Search: Once processed, stored for advanced search & analysis"
                          ]
                      }
                  # Extract specific data if classified as Insurance Policy Document
                  elif classification_result["category"] == "Policy Document of Insurance":
                      extraction_prompt = f"""
                          You have classified this document as a **Policy Document of Insurance**.
                          
                          Now, extract the following details from the provided document summary:
                          - Policy Type
                          - Policy Number
                          - Effective Date
                          - Policyholder Name

                          If any of these details are missing or cannot be identified, return them as `null`.

                          ---

                          ### **Document Summary:**
                          {summary}

                          ---

                          Return the extracted information in JSON format ONLY with the JSON keynames shown exactly as in the example, like this:
                          {{
                              "policyType": "<Identify Insurance Policy Type from Document>",
                              "policyNumber": "<Identify Policy Number from Document>",
                              "effectiveDate": "<Identify Policy's Effective Date from Document>",
                              "policyHolderName": "<Identify Policy Holder Name from Document"
                          }}
                      """

                      logger.info(f"Extraction prompt: {extraction_prompt}")
                      extraction_messages = [{"role": "user", "content": [{"text": extraction_prompt}]}]
                      extraction_response = invoke_bedrock_model("", extraction_messages, MODEL_ID)
                      logger.info(f"Extraction Response: {extraction_response}")

                      extracted_data = json.loads(extraction_response)

                      # Combine classification and extracted data
                      return {
                          "category": classification_result["category"],
                          "confidence": classification_result["confidence"],
                          "extractedData": extracted_data,
                          "nextSteps": [
                              "Extraction of Page level data: Extract policy data i markup format page by page",
                              "Attach Metadata to Each Page: Policy Number, Effective Date, Polic yHolder Name, Policy Type",
                              "Indexing & Search: Once processed, stored for advanced search & analysis"
                          ]
                      }
                  # Special Handling for Vehicle Damage Photos
                  elif classification_result["category"] == "Vehicle Damage Photos":
                      extraction_prompt = f"""
                          You have classified this document as **Vehicle Damage Photos**.

                          Extract the following information from the provided image summary:
                          - Vehicle Information:
                              * Make (e.g., Hyundai)
                              * Model (e.g., SUV/Crossover)
                              * Color (e.g., Red)
                              * Year (if available from license plate or visible metadata)
                              * VIN (if visible in the image)
                          
                          - Damage Details:
                              * Type of damage (e.g., Front-end collision, Rear-end, Side impact)
                              * Severity of damage (Severe, Moderate, Minor)
                              * Damaged areas (e.g., Front bumper, Hood, Windshield)

                          - Scene Information:
                              * Accident location type (e.g., Parking lot, Intersection)
                              * Presence of other vehicles (e.g., Red vehicle partially visible)
                              * Weather conditions (if visible, e.g., Rainy, Clear)

                          - Summary Information:
                              * Document Summary explaining the nature of the damage in detail

                          - Claim information:
                              * Claim Number
                              * Policy Number
                              * Date Of Loss


                          If any of the above details are missing or cannot be identified return them as `null`.

                          ### Document Summary is as follows:
                          {summary}

                          Return the extracted information in JSON format ONLY with the JSON keynames shown exactly as in the example, like this:
                          {{
                              "claimDetails": {{
                                  "claimNumber" : "<Identify Claim Number from Image if annotated>",
                                  "policyNumber": "<Identify Policy Number from Image if annotated>",
                                  "dateOfLoss": "<Identify Date Of Loss from Image if annotated>"
                              }},

                              "vehicleDetails": {{
                                  "make": "<Identify from Image>",
                                  "model": "<Identify from Image>",
                                  "color": "<Identify from Image>",
                                  "year": "<Identify from Image>",
                                  "vin": "<Identify from Image>"
                              }},
                              "damageDetails": {{
                                  "type": "<Identify from Image>",
                                  "severity": "<Identify from Image>",
                                  "damagedAreas": ["<Identify from Image>", "<Identify from Image>", "<Identify from Image>"]
                              }},
                              "sceneDetails": {{
                                  "locationType": "<Identify location from Image>",
                                  "otherVehiclesVisible": "<Identify if other vehicles are there in Image>",
                                  "weatherConditions": "<Identify weather from Image>"
                              }},
                              "summaryDetails": {{
                                  "summary": "<Identify summary of vehicle damage from Image>"
                              }}
                          }}
                      """

                      logger.info(f"Extraction prompt for Vehicle Damage Photos: {extraction_prompt}")
                      extraction_messages = [{"role": "user", "content": [{"text": extraction_prompt}]}]
                      extraction_response = invoke_bedrock_model("", extraction_messages, MODEL_ID)
                      logger.info(f"Vehicle Damage Extraction Response: {extraction_response}")

                      extracted_data = json.loads(extraction_response)

                      # Combine classification and extracted data
                      return {
                          "category": classification_result["category"],
                          "confidence": classification_result["confidence"],
                          "extractedData": extracted_data,
                          "nextSteps": [
                              "Metadata association: Extracted and user-supplied metadata is associated with uploaded images for advanced search and analytics.",
                              "Claim evidence association: Link vehicle damage photos with the associated insurance claim record.",
                              "Automated damage summary report: Generate an automated damage assessment report and associate with claim for downstream analytics"
                          ]
                      }
                  # Special Handling for Vehicle Repair Estimate Summary
                  elif classification_result["category"] == "Vehicle Repair Estimate Summary":
                      extraction_prompt = f"""
                          You have classified this document as **Vehicle Repair Estimate Summary**.

                          Extract the following information from the provided document summary:
                          - Claim Details:
                              * Claim Number (e.g., CLM20250215)
                              * Policy Number (e.g., SF9988776655)
                              * Estimate Date (e.g., D2025-02-15)
                              * Date In / Date Out if available
                              * Claimant Name (sometimes also mentioned as Customer Name)
                          - Vehicle Details:
                              * Make (e.g., Ford)
                              * Model (e.g., F-150)
                              * Year (e.g., 2021)
                              * VIN (e.g., 1FTFW1E58MFC12345)
                              * License Plate (if available)
                          - Estimator Details:
                              * Estimator Name (if available)
                              * Estimator Contact (if available)

                          If any of the above details are missing or cannot be identified, return them as `null`.

                          ### Document Summary is as follows:
                          {summary}

                          Return the extracted information in JSON format ONLY with the JSON keynames shown exactly as in the example, like this:
                          {{
                              "claimDetails": {{
                                  "claimNumber": "<Identify Claim Number>",
                                  "policyNumber": "<Identify Policy Number>",
                                  "estimateDate": "<Identify Estimate Date>",
                                  "dateIn": "<Identify Date In if available>",
                                  "dateOut": "<Identify Date Out if available>",
                                  "claimantName" : "<Identify Claimant Name or Customer Name if available>"
                              }},
                              "vehicleDetails": {{
                                  "make": "<Identify Vehicle Make>",
                                  "model": "<Identify Vehicle Model>",
                                  "year": "<Identify Vehicle Year>",
                                  "vin": "<Identify VIN>",
                                  "licensePlate": "<Identify License Plate if available>"
                              }},
                              "estimatorDetails": {{
                                  "name": "<Identify Estimator Name if available>",
                                  "contact": "<Identify Estimator Contact if available>"
                              }}
                          }}
                      """
                      logger.info(f"Extraction prompt for Vehicle Repair Estimate Summary: {extraction_prompt}")
                      extraction_messages = [{"role": "user", "content": [{"text": extraction_prompt}]}]
                      extraction_response = invoke_bedrock_model("", extraction_messages, MODEL_ID)
                      logger.info(f"Vehicle Repair Estimate Extraction Response: {extraction_response}")

                      extracted_data = json.loads(extraction_response)

                      # Combine classification and extracted data, and update next steps accordingly.
                      return {
                          "category": classification_result["category"],
                          "confidence": classification_result["confidence"],
                          "extractedData": extracted_data,
                          "nextSteps": [
                              "Detailed Repair analysis and cost breakdown: Proceed with cost summary extraction and detailed cost breakdown.",
                              "Summarization: Perform page-wise summarization for comprehensive analysis.",
                              "Record association: Link repair estimate information with corresponding claim records for further processing.",
                              "Metadata association: Extracted and user-supplied metadata is associated with uploaded repair estimates for advanced search and analytics."
                          ]
                      }


                  else:
                      return classification_result

              except Exception as e:
                  logger.error(f"Error classifying document: {str(e)}")
                  return {"category": "Unknown", "confidence": 0.0}

          ### **Lambda Handler**
          def lambda_handler(event, context):
              try:
                  logger.info("Lambda Event Received")
                  logger.info(json.dumps(event))

                  # Extract document filename
                  document_filename = extract_document_filename(event)
                  if not document_filename:
                      logger.error("No document filename provided.")
                      return {"statusCode": 400, "body": json.dumps({"error": "No document filename provided"})}

                  # Generate unique temp directory per invocation
                  temp_dir = f"/tmp/{context.aws_request_id}"
                  os.makedirs(temp_dir, exist_ok=True)

                  # Download file from S3
                  pdf_path = f"{temp_dir}/{document_filename.split('/')[-1]}"
                  logger.info(f"Downloading file from S3: {DOCUMENTUPLOAD_BUCKET}/{document_filename} -> {pdf_path}")
                  s3_client.download_file(DOCUMENTUPLOAD_BUCKET, document_filename, pdf_path)

                  # Convert PDF to PNGs and upload to PNG_FILEBUCKET
                  image_keys = pdf_to_png_and_upload(pdf_path, document_filename, context)
                  summary_text = ""
                  classification_result = {"category": "Unknown", "confidence": 0.0}
                  pages_analyzed = 0
                  
                  for image_key in image_keys:
                      if pages_analyzed >= MAX_PAGES_TO_PROCESS:
                          logger.info("Reached max allowed pages for classification.")
                          break

                      logger.info(f"File for local summarization::: {image_key}")
                      local_image_path = image_key

                      page_summary = process_page_with_bedrock(local_image_path, MODEL_ID)
                      summary_text += f"\n{page_summary}\n"
                      logger.info(f"Aggregated Summary----: {summary_text}")
                      classification_result = classify_document(summary_text)
                      classification_result["documentPath"] = document_filename  # Add document filename to response
                      logger.info(f"Classification result: {classification_result}")

                      pages_analyzed += 1

                      if classification_result["confidence"] >= CONFIDENCE_THRESHOLD:
                          break

                  # After processing the initial pages
                  logger.info(f"Pages analyzed::: {pages_analyzed}")


                  response_body = {
                      "application/json": {
                          "body": json.dumps(classification_result)
                      }
                  }

                  return {
                      "messageVersion": "1.0",
                      "response": {
                          "actionGroup": event["actionGroup"],
                          "apiPath": event["apiPath"],
                          "httpMethod": event["httpMethod"],
                          "httpStatusCode": 200,
                          "responseBody": response_body
                      },
                      "sessionAttributes": event.get("sessionAttributes", {}),
                      "promptSessionAttributes": event.get("promptSessionAttributes", {}),
                  }

              except Exception as e:
                  logger.error(f"Error processing document: {str(e)}")
                  return {"statusCode": 500, "body": json.dumps({"error": str(e)})}

                                    

# Step X: Bedrock agent access to invoke the ClassificationExecutorLambda
  ClassificationExecutorLambdaPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ClassificationExecutorLambdaFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"  # Use the correct service principal for the Bedrock agent
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${DocumentClassificationAgent}"

# Step X: Classification Collaborator Agent
  ClassificationCollaboratorAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: ClassificationCollaborator
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleClassfnCollab.Arn
      Instruction: |
        You are a document conversion and classification agent whose goal is to ensure that all documents are properly converted to standard format like PDF  and accurately classified into relevant categories.
        Here are the steps you MUST FOLLOW in exact order when processing documents:
        Step 1. When a request arrives for classification, you MUST ALWAYS FIRST identify the file type of the data to decide if conversion must be applied before classification.
        Step 2. If the document is PDF, PNG, mp3, mp4 format you MUST directly classify the document into the correct category.
        Step 3. If the document is DOCX, PPT, XLSX, XLS, excel or any other non-standard format, you MUST first convert it into a standard format like PDF. After the document has been converted to PDF, you MUST always classify the document. 
        Step 4. You MUST NOT stop after conversion. Classification is always required for all documents before sending a final response back.
        STRICT RULES:
          * Return responses ONLY in JSON format. DO NOT add your own commentary around the JSON response before returning.
          * Include `"nextSteps"` for all types of  document categories. 
          * Format all output with keys `"category"`, `"confidence"`, `"extractedData"`, `"nextSteps"` and  `"documentPath"`.
          * DO NOT return partial JSON or exclude fields. Always return all keys, even if some values are null.
          * If `"extractedData"` has additional metadata fields, include them in `"extractedData"` as-is. 
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600
   
# Step X: Classification Collaborator Agent
  ClassificationCollaboratorAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref ClassificationCollaboratorAgent
      AgentAliasName: "ClassificationCollabAgentAlias"

# Step X: TranscriptionClassificationAgent  for chat transcripts mp3 audio recordings
  TranscriptionClassificationAgent:
    Type: AWS::Bedrock::Agent
    Properties:
      AgentName: "TranscriptionClassificationAgent"
      AutoPrepare: true
      AgentResourceRoleArn: !GetAtt BedrockAgentExecutionRoleTranscription.Arn
      Instruction: |
        You are an audio and video transcript classification agent responsible for analyzing transcripts from mp3 and mp4 formats to identify insurance-related scenarios. 
        
        ### TASKS:
        1. Classify transcripts into categories.  
        2. Extract relevant metadata and nextSteps from the raw response as is.  
        ### STRICT RULES:  
        - **RETURN RESPONSES EXACTLY AS PROVIDED IN THE ORIGINAL RAW RESPONSE**  
        - **DO NOT ADD NEW FIELDS OR OMIT ANY FIELDS FROM THE ORIGINAL RAW RESPONSE.**  
        - **DO NOT GENERATE OR MODIFY ANY PART OF THE RESPONSE.**  
        - **RELAY THE RESPONSE AS-IS IN JSON FORMAT.**  

        ### OUTPUT REQUIREMENTS:  
        - Return ONLY in JSON format.  
        - **MANDATORY FIELDS:** Every response must include:  
          - `"category"`  
          - `"confidence"`  
          - `"extractedData"`  
          - `"nextSteps"`  
          - `"documentPath"`
        - Return **all keys exactly as provided**, even if some values are `null`.  
        - Do **not add extra keys or fields** to `"extractedData"` or `"nextSteps"`.  
        - If `"extractedData"` or `"nextSteps"` contain additional fields in the raw response, then  **pass them through without alteration**.  

        ### ERROR HANDLING:  
        - If the collaborator returns an incomplete JSON, return it **as-is without alteration**.  
        - If a field is missing, do **not insert `null` unless the collaborator provided it**.  

        ### RESPONSE FORMAT (EXACT MATCH):  
        Return the collaborator's response in this exact format:  
        ```json
        {
          "category": "<provided_value>",
          "confidence": <provided_value>,
          "extractedData": <provided_value>,
          "nextSteps": <provided_value>,
          "documentPath": <provided_value>
        }
      FoundationModel: "anthropic.claude-3-5-haiku-20241022-v1:0"
      IdleSessionTTLInSeconds: 3600

      ActionGroups:
        - ActionGroupName: TranscriptionCategoryClassification
          Description: "Classifies audio transcripts into various categories and identify metadata."
          ApiSchema:
            Payload: |
              {
                "openapi": "3.0.0",
                "info": {
                  "title": "Transcription Category Classification API",
                  "version": "1.0.0",
                  "description": "Classifies audio and video transcripts into specific categories."
                },
                "paths": {
                  "/classifyTranscript": {
                    "post": {
                      "summary": "Classify an audio or video transcript into predefined categories",
                      "description": "Classifies the audio or video transcript and returns structured metadata associated with the identified  category.",                    
                      "operationId": "classifyTranscript",
                      "requestBody": {
                        "required": true,
                        "content": {
                          "application/json": {
                            "schema": {
                              "type": "object",
                              "properties": {
                                "audiovideofilename": {"type": "string", "description": "Audio or Video file name."}
                              },
                              "required": ["audiovideofilename"]
                            }
                          }
                        }
                      },
                      "responses": {
                        "200": {
                          "description": "Classification result",
                          "content": {
                            "application/json": {
                              "schema": {
                                "type": "object",
                                "properties": {
                                  "category": {"type": "string"},
                                  "confidence": {"type": "number"},
                                  "extractedData": {"type": "object"},
                                  "nextSteps": {
                                    "type": "array",
                                    "items": {"type": "string"},
                                    "description": "Recommended next steps based on document classification."
                                  },
                                  "documentPath": {"type": "string"}
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
          ActionGroupState: ENABLED
          ActionGroupExecutor:
            Lambda: !GetAtt TranscriptionExecutorLambdaFunction.Arn

  TranscriptionClassificationAgentAlias:
    Type: AWS::Bedrock::AgentAlias
    Properties:
      AgentId: !Ref TranscriptionClassificationAgent
      AgentAliasName: "TranscriptionAgentAlias"

  TranscriptionExecutorLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: TranscriptionExecutorLambdaRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
        - arn:aws:iam::aws:policy/AmazonTranscribeFullAccess
      Policies:
        - PolicyName: TranscriptionExecutorExtras
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:DeleteObject
                  - s3:ListBucket
                  - s3:GetBucketLocation
                  - s3:GetObjectVersion
                Resource:
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}/*"
                  - !Sub "arn:aws:s3:::${RawTranscriptJobBucket}/*"
                  - !Sub "arn:aws:s3:::${KBDataBucket}/*"
                  - !Sub "arn:aws:s3:::${DocumentUploadBucket}"
                  - !Sub "arn:aws:s3:::${RawTranscriptJobBucket}"
                  - !Sub "arn:aws:s3:::${KBDataBucket}"

  TranscriptionExecutorLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "TranscriptionExecutorLambdaFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt TranscriptionExecutorLambdaExecutionRole.Arn
      Layers:
        # - arn:aws:lambda:us-west-2:XXXXXX:layer:opencvpillownumpy310:1 # For python 3.10 , headless open cv , pillow, numpy
        # - arn:aws:lambda:us-west-2:XXXXXX:layer:new-opencvpillownumpy310:1 # For python 3.10 , headless open cv , pillow, numpy
        - !Ref OpencvLambdaLayer # For python 3.10 , headless open cv , pillow, numpy
      Timeout: 900
      MemorySize: 4096
      Environment:
        Variables:
          OUTPUT_BUCKET: !Ref KBDataBucket
          TRANSCRIPTION_BUCKET: !Ref RawTranscriptJobBucket
          VIDEO_BUCKET: !Ref DocumentUploadBucket
          AUDIO_BUCKET: !Ref DocumentUploadBucket
          BEDROCK_MODEL: "anthropic.claude-3-5-sonnet-20241022-v2:0"
      Code:
        ZipFile: |
          import json
          import boto3
          import cv2
          import os
          import logging
          import time
          import re
          import uuid
          import numpy as np
          from datetime import timedelta
          from botocore.config import Config
          from PIL import Image


          # Initialize Logger
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          # AWS Clients
          s3_client = boto3.client("s3")
          transcribe_client = boto3.client("transcribe")
          bedrock_runtime = boto3.client(
              "bedrock-runtime",
              region_name=os.environ["AWS_REGION"],
              config=Config(connect_timeout=300, read_timeout=300)
          )

          # Environment Variables
          AUDIO_BUCKET = os.environ["AUDIO_BUCKET"]
          VIDEO_BUCKET = os.environ["VIDEO_BUCKET"]
          TRANSCRIPTION_BUCKET = os.environ["TRANSCRIPTION_BUCKET"]
          OUTPUT_BUCKET = os.environ["OUTPUT_BUCKET"]
          BEDROCK_MODEL_ID = os.environ.get("BEDROCK_MODEL_ID", "anthropic.claude-3-5-sonnet-20241022-v2:0")
          FRAME_RATE = int(os.environ.get("FRAME_RATE", 1))
          MAX_FRAMES = int(os.environ.get("MAX_FRAMES", 100))


          ### Step 1: Extract Filename from Event ###
          def extract_filename(event):
              try:
                  properties_list = event["requestBody"]["content"]["application/json"]["properties"]
                  for prop in properties_list:
                      if prop.get("name") == "audiovideofilename":
                          filename = prop.get("value", "")
                          if not filename.startswith("uploads/"):
                              filename = f"uploads/{filename}"
                          logger.info(f"Extracted document filename: {filename}")
                          return filename
                  logger.warning("Document filename not found in request.")
                  return ""
              except Exception as e:
                  logger.error(f"Error extracting document filename: {str(e)}")
                  return ""


          ### Step 2: Transcribe Audio ###
          def transcribe_audio(audio_filename):
              logger.info(f"Starting transcription for {audio_filename}")
              unique_id = uuid.uuid4().hex[:6]  # short random suffix
              job_name = f"{os.path.basename(audio_filename).replace('.', '_')}_{int(time.time())}_{unique_id}"
              # try:
              #     # Check if a job with the same name already exists
              #     existing = transcribe_client.get_transcription_job(TranscriptionJobName=job_name)
              #     status = existing['TranscriptionJob']['TranscriptionJobStatus']
              #     if status in ('IN_PROGRESS', 'QUEUED'):
              #         logger.info(f'Transcribe job {job_name} still in progress. Reusing it instead of starting a new one.')
              #         # Optionally just poll until it finishes
              #     else:
              #         # Possibly stop or delete the completed/failed job to re-run
              #         transcribe_client.delete_transcription_job(TranscriptionJobName=job_name)

              # except transcribe_client.exceptions.NotFoundException:
              #     # No existing job -> Start new job
              #     pass

              # Now we know we can safely start a new job
              try:
                  transcribe_client.start_transcription_job(
                      TranscriptionJobName=job_name,
                      Media={"MediaFileUri": f"s3://{AUDIO_BUCKET}/{audio_filename}"},
                      MediaFormat="mp4",
                      LanguageCode="en-US",
                      OutputBucketName=TRANSCRIPTION_BUCKET
                  )

                  while True:
                      status = transcribe_client.get_transcription_job(TranscriptionJobName=job_name)
                      if status["TranscriptionJob"]["TranscriptionJobStatus"] in ["COMPLETED", "FAILED"]:
                          break
                      time.sleep(3)

                  if status["TranscriptionJob"]["TranscriptionJobStatus"] == "COMPLETED":
                      result_uri = status["TranscriptionJob"]["Transcript"]["TranscriptFileUri"]
                      transcript_obj = s3_client.get_object(
                          Bucket=TRANSCRIPTION_BUCKET,
                          Key=result_uri.split("/")[-1]
                      )
                      transcript_data = json.loads(transcript_obj["Body"].read().decode("utf-8"))
                      transcript_text = transcript_data["results"]["transcripts"][0]["transcript"]
                      logger.info(f"Transcription completed with {len(transcript_text.split())} words.")
                      return transcript_text
                  else:
                      logger.warning(f"Transcription failed for {audio_filename}")
                      return None
              except Exception as e:
                  logger.error(f"Audio transcription error: {e}", exc_info=True)
                  return None


          ### Step 3: Extract Frames from Video (Store Locally in /tmp) ###
          def extract_frames(video_path, request_id, black_frame_threshold=5):
              """
              Extract frames from the video.
              - Return None if:
                  1. No frames extracted
                  2. First 'black_frame_threshold' frames are completely black (early exit)
                  3. All frames are black
                  4. On any error
              """
              try:
                  output_folder = f"/tmp/{request_id}/frames"
                  os.makedirs(output_folder, exist_ok=True)
                  logger.info(f"Extracting frames into local directory: {output_folder}")

                  # Validate video path
                  if not os.path.exists(video_path):
                      logger.error(f"Video file not found: {video_path}")
                      return None

                  cap = cv2.VideoCapture(video_path)
                  if not cap.isOpened():
                      logger.error(f"Failed to open video file: {video_path}")
                      return None

                  fps = cap.get(cv2.CAP_PROP_FPS) or 30
                  frame_interval = max(int(fps / FRAME_RATE), 1)
                  frame_paths = []
                  frame_count = 0
                  black_frame_count = 0

                  while cap.isOpened() and frame_count < MAX_FRAMES:
                      ret, frame = cap.read()
                      if not ret:
                          break

                      # Check if frame is completely black
                      if np.mean(frame) == 0:
                          black_frame_count += 1
                          # Early exit if first few frames are all black
                          if frame_count < black_frame_threshold and black_frame_count == black_frame_threshold:
                              logger.warning(f"First {black_frame_threshold} frames are black. Exiting early.")
                              cap.release()
                              return None
                      else:
                          if frame_count % frame_interval == 0:
                              timestamp = str(timedelta(seconds=int(cap.get(cv2.CAP_PROP_POS_MSEC) / 1000)))
                              frame_filename = f"frame_{frame_count}_{timestamp}.png"
                              frame_path = os.path.join(output_folder, frame_filename)
                              if cv2.imwrite(frame_path, frame):
                                  frame_paths.append((frame_filename, frame_path, timestamp))

                      frame_count += 1

                  cap.release()

                  if not frame_paths:
                      logger.warning("No frames extracted from video.")
                      return None

                  if black_frame_count == frame_count:
                      logger.warning("All frames are completely black.")
                      return None

                  logger.info(f"Extracted {len(frame_paths)} frames (Non-black frames).")
                  return frame_paths

              except Exception as e:
                  logger.error(f"Error extracting frames from video: {e}", exc_info=True)
                  return None




          ### Step 4: Generate Video Summary Using Bedrock (From Local Frames) ###
          def generate_video_summary(frame_paths):
              logger.info("Generating video summary from frames using Bedrock")

              frame_descriptions = []
              for _, frame_path, timestamp in frame_paths:
                  with open(frame_path, "rb") as image_file:
                      image_bytes = image_file.read()

                  prompt = f"""
                  Analyze this frame (timestamp {timestamp}) from an insurance claim video.
                  Describe key events (e.g., car damage, collisions, interactions) in one sentence.
                  Return JSON ONLY with: {{"summary": "<frame description>"}}
                  """
                  messages = [
                      {"role": "user", "content": [
                          {"image": {"format": "png", "source": {"bytes": image_bytes}}},
                          {"text": prompt}
                      ]}
                  ]
                  response = invoke_bedrock(messages)
                  logger.info(f"Frame summary (timestamp {timestamp}): {response}")
                  frame_descriptions.append(response.get("summary", ""))

              # Aggregate summaries
              aggregated_summary = " ".join(frame_descriptions)
              logger.info(f"Aggregated video summary: {aggregated_summary}")
              return aggregated_summary


          ### Step 5: Invoke Bedrock ###
          def invoke_bedrock(messages):
              try:
                  response = bedrock_runtime.converse(
                      modelId=BEDROCK_MODEL_ID,
                      inferenceConfig={"maxTokens": 8192, "temperature": 0.0, "topP": 0.45},
                      messages=messages
                  )
                  raw_output = response["output"]["message"]["content"][0]["text"]
                  logger.debug(f"Raw Bedrock output: {raw_output[:500]}...")

                  json_match = re.search(r'\{.*\}', raw_output, re.DOTALL)
                  if json_match:
                      json_content = json_match.group(0)
                      return json.loads(json_content)
                  else:
                      logger.error("Bedrock output did not contain valid JSON.")
                      return {"error": "Invalid JSON response from Bedrock"}
              except json.JSONDecodeError as e:
                  logger.error(f"JSON decoding failed: {e}", exc_info=True)
                  return {"error": "Invalid JSON structure"}
              except Exception as e:
                  logger.error(f"Bedrock invocation error: {e}", exc_info=True)
                  return {"error": "Bedrock invocation failed"}


          ### Step 6: Generate Combined Insights (Audio + Video) ###
          def generate_insights(audio_summary=None, video_summary=None):
              logger.info("Generating combined insights from audio and video summaries")
              audio_summary_text = f"- Audio Summary: {audio_summary}" if audio_summary else "- Audio Transcript: Not available"
              video_summary_text = f"- Video Summary: {video_summary}" if video_summary else "- Video Frames: Not available"

              prompt = f"""
              You are an insurance audio-video recording classification assistant.

              You must classify this media based on both the audio transcript and video analysis:

              {audio_summary_text}
              {video_summary_text}

              If both audio and video signals provide complementary information, assign a more specific subcategory.  
              If there is a conflict, rely on the most insurance-relevant signal. 

              Return results in JSON format ONLY without any commentary:
              {{
                "category": "Audio-Video Transcript - Collision with FNOL Call",
                "confidence": 0.98,
                "extractedData": {{
                  "subcategory": "Provide a more specific sub category",
                  "claimNumber": "This should have the claim number identified from the transcript",
                  "policyNumber": "This should have the policy number identified from the transcript",
                  "dateOfLoss": "Date Of Loss from the transcript",
                  "claimantName: "Name of the claimant",
                  "policyholderName": "Policy Holder's name if mentioned in the transcript",
                  "summary": "Summary of what has happened in the transcript."
                }}
              }}
              """
              messages = [{"role": "user", "content": [{"text": prompt}]}]
              classification_result = invoke_bedrock(messages)
              logger.info(f"Classification Response: {classification_result}")

              
              # Create structured response similar to your reference pattern
              structured_result = {
                  "category": classification_result.get("category", "Unknown"),
                  "confidence": classification_result.get("confidence", 0.0),
                  "extractedData": classification_result.get("extractedData", {}),
                  "nextSteps": [
                      "Attach Metadata: Associate identified metadata with chat transcript",
                      "Index transcript: For Advanced search and analytics",
                  ]
              }
              logger.info(f"Insights generation successful: {structured_result}")
              return structured_result



          ### Step 7: Store Results in S3 ###
          def store_results(bucket, key, content):
              s3_client.put_object(
                  Bucket=bucket,
                  Key=key,
                  Body=content.encode("utf-8")
              )
              logger.info(f"Stored results at: s3://{bucket}/{key}")

          def get_output_filename(filename):
              # Use regex to remove any folder path and replace extension with .txt
              return re.sub(r'.*/|(\.[^.]+)$', '', filename) + '.txt'

          ### Step 8: Lambda Handler (Unified for All Scenarios) ###
          def lambda_handler(event, context):
              try:
                  logger.info(f"Received event: {json.dumps(event)}")

                  filename = extract_filename(event)
                  logger.info(f"Extracted file name:: {filename}")
                  request_id = context.aws_request_id
                  audio_summary = None
                  video_summary = None

                  # Download video and extract frames locally
                  video_path = f"/tmp/{request_id}/{filename}"
                  os.makedirs(f"/tmp/{request_id}/uploads", exist_ok=True)
                  s3_client.download_file(VIDEO_BUCKET, f"{filename}", video_path)

                  frames = extract_frames(video_path, request_id)
                  if frames:
                      video_summary = generate_video_summary(frames)
                      

                  # Transcribe audio
                  audio_transcript = transcribe_audio(filename)
                  if audio_transcript:
                      audio_summary = audio_transcript

                  # Generate insights from combined summaries
                  insights = generate_insights(audio_summary, video_summary)
                  insights["documentPath"] = filename  # Add document filename to response
                  
                  logger.info(f"Classification result in lambda_handler:: {insights}")

                  # Create the combined text file content
                  combined_text = ""
                  if audio_summary:
                      combined_text += f"Audio Transcript:\n{audio_summary}\n\n"
                  else:
                      combined_text += "Audio Transcript: Not Available\n\n"

                  if video_summary:
                      combined_text += f"Video Transcript:\n{video_summary}\n\n"
                  else:
                      combined_text += "Video Transcript: Not Available\n\n"

                  combined_text += f'Final Summary and Insights:\n{insights["extractedData"]["summary"]}\n\n'

                  # Store combined results in OUTPUT_BUCKET
                  output_key = get_output_filename(filename)
                  logger.info(f"Output file name for KB:: {output_key}")
                  store_results(OUTPUT_BUCKET, output_key, combined_text)

                  logger.info(f"Stored combined results in {OUTPUT_BUCKET}/{output_key}")


                  # Create response body (Matching Reference Structure)
                  response_body = {
                      "application/json": {
                          "body": json.dumps(insights)
                      }
                  }

                  return {
                      "messageVersion": "1.0",
                      "response": {
                          "actionGroup": event.get("actionGroup", ""),
                          "apiPath": event.get("apiPath", ""),
                          "httpMethod": event.get("httpMethod", ""),
                          "httpStatusCode": 200,
                          "responseBody": response_body
                      },
                      "sessionAttributes": event.get("sessionAttributes", {}),
                      "promptSessionAttributes": event.get("promptSessionAttributes", {}),
                  }

              except Exception as e:
                  logger.error(f"Error processing document: {e}", exc_info=True)

                  error_response = {
                      "application/json": {
                          "body": json.dumps({
                              "error": str(e),
                              "message": "An error occurred during media processing"
                          })
                      }
                  }

                  return {
                      "messageVersion": "1.0",
                      "response": {
                          "httpStatusCode": 500,
                          "responseBody": error_response
                      }
                  }


  TranscriptionExecutorLambdaPermissions:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt TranscriptionExecutorLambdaFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: "bedrock.amazonaws.com"
      SourceArn: !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/${TranscriptionClassificationAgent}"


  # Step X: Custom Lambda for Multi-Agent Collaboration
  MultiAgentCollabLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AmazonBedrockFullAccess
      # Policies:
      #   - PolicyName: BedrockActionsPolicy
      #     PolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: Allow
      #           Action:
      #             - "bedrock:*"
      #           Resource:
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent/*"
      #             - !Sub "arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:agent-alias/*"
      #         - Effect: Allow
      #           Action:
      #             - "iam:PassRole"
      #           Resource:
      #             - !GetAtt BedrockAgentExecutionRoleSupervisorAgent.Arn
      #             - !GetAtt BedrockAgentExecutionRoleClassfnCollab.Arn


  MultiAgentCollaborationLambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "MultiAgentCollaborationLambda"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt MultiAgentCollabLambdaExecutionRole.Arn
      Layers:
      # - arn:aws:lambda:us-west-2:477713232171:layer:bedrock-layer-13611:1 # For MAC we need boto3 versio 1.36.11
      # - arn:aws:lambda:us-west-2:477713232171:layer:new-mac-bedrock-layer-13611:1 # For MAC we need boto3 versio 1.36.11
      - !Ref MACLambdaLayer # For MAC we need boto3 versio 1.36.11
      Timeout: 300
      Code:
        ZipFile: |
          import boto3
          import json
          import os
          import cfnresponse
          import time

          bedrock_client = boto3.client("bedrock-agent")

          def lambda_handler(event, context):
              try:
                  print("Received event: " + json.dumps(event, indent=2))
                  print("Boto3 Version:", boto3.__version__)
                  response_data = {}
                  status = cfnresponse.SUCCESS  # Default status
                  request_type = event['RequestType']

                  if request_type in ["Create", "Update"]:
                      supervisor_agent_id = event["ResourceProperties"]["SUPERVISOR_AGENT_ID"]
                      classificationcollab_agent_id = event["ResourceProperties"]["CLASSIFICATIONCOLLAB_AGENT_ID"]
                      classificationcollab_agent_alias_id = event["ResourceProperties"]["CLASSIFICATIONCOLLAB_AGENT_ALIAS_ID"]  # New Alias ID
                      super_agent_resource_role_arn = event["ResourceProperties"]["SUPER_AGENT_EXECUTION_ROLE_ARN"]
                      agent_name = event["ResourceProperties"]["AGENT_NAME"]  # <-- Dynamically retrieved
                      foundation_model = event["ResourceProperties"]["FOUNDATION_MODEL"]  # <-- Dynamically retrieved
                      classificationcollab_agent_name = event["ResourceProperties"]["CLASSIFICATIONCOLLAB_AGENT_NAME"]  # <-- Dynamically retrieved
                      classfn_collab_agent_resource_role_arn = event["ResourceProperties"]["CLASSFN_COLLAB_AGENT_EXECUTION_ROLE_ARN"]
                      aws_account_id = event["ResourceProperties"]["AWS_ACCOUNT_ID"]
                      aws_region = event["ResourceProperties"]["AWS_REGION"]

                      # Construct the correct alias ARN for classificationcollab agent
                      classificationcollab_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{classificationcollab_agent_id}/{classificationcollab_agent_alias_id}"


                      print(f"Updating Supervisor Agent {supervisor_agent_id} for Collaboration...")
                      bedrock_client.update_agent(
                          agentId=supervisor_agent_id,
                          agentName=agent_name,
                          agentResourceRoleArn=super_agent_resource_role_arn,
                          instruction="""
          You are a document routing agent. 
          STRICT RULES:
          - Do not modify or summarize responses from collaborating agents. Forward their JSON responses EXACTLY.
          - Your ONLY job is to forward the request to the appropriate collaboration agent and relay back the response AS-IS.
          PROCESS:
          1. When a request arrives, forward it to the correct collaboration agent.
          2. DO NOT modify or interpret the collaborator's response.
          3. Return the response EXACTLY in its original JSON structure without any additional formatting or commentary.
                            """,
                          foundationModel=foundation_model,
                          agentCollaboration="SUPERVISOR_ROUTER",
                          orchestrationType="DEFAULT"
                      )

                      print(f"Updating Clasification Collaborator Agent {classificationcollab_agent_id} as Supervisor for Collaboration for Transcription, Conversion and Classification...")
                      bedrock_client.update_agent(
                          agentId=classificationcollab_agent_id,
                          agentName=classificationcollab_agent_name,
                          agentResourceRoleArn=classfn_collab_agent_resource_role_arn,
                          instruction="""
          You are a document conversion and classification agent whose goal is to ensure that all documents are properly converted to standard format like PDF  and accurately classified into relevant categories.
          Here are the steps you MUST FOLLOW in exact order when processing documents:
          Step 1. When a request arrives for classification, you MUST ALWAYS FIRST identify the file type of the data to decide if conversion must be applied before classification.
          Step 2. If the document is PDF, PNG, mp3, mp4 format you MUST directly classify the document into the correct category.
          Step 3. If the document is DOCX, PPT, XLSX, XLS, excel or any other non-standard format, you MUST first convert it into a standard format like PDF. After the document has been converted to PDF, you MUST always classify the document. 
          Step 4. You MUST NOT stop after conversion. Classification is always required for all documents before sending a final response back.
          STRICT RULES:
            * Return responses ONLY in JSON format. DO NOT add your own commentary around the JSON response before returning.
            * Include `"nextSteps"` for all types of  document categories.
            * Format all output with keys `"category"`, `"confidence"`, `"extractedData"`, `"nextSteps"` and  `"documentPath"`.
            * DO NOT return partial JSON or exclude fields. Always return all keys, even if some values are null.
            * If `"extractedData"` has additional metadata fields, include them in `"extractedData"` as-is. 
                            """,
                          foundationModel=foundation_model,
                          agentCollaboration="SUPERVISOR",
                          orchestrationType="DEFAULT"
                      )



                      # Add association for Conversion Agent  to the ClassificationCollaboratorAgent
                      document_conversion_agent_id = event["ResourceProperties"]["CONVERSION_AGENT_ID"]
                      document_conversion_agent_alias_id = event["ResourceProperties"]["CONVERSION_AGENT_ALIAS_ID"]
                      document_conversion_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{document_conversion_agent_id}/{document_conversion_agent_alias_id}"

                      print(f"Associating Conversion Agent {document_conversion_agent_id} as Collaborator to the ClassificationCollab Agent...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=classificationcollab_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": document_conversion_agent_alias_arn},
                          collaboratorName="DocumentConversionAgent",
                          collaborationInstruction="This agent converts documents of the following types -  word documents (extension of .doc,.docx), excel documents (extension is .xlsx, .xls, .excel) and power point documents ( extension is .ppt,.pptx) into standard format of  PDF",
                          relayConversationHistory="DISABLED"
                      )

                      # Add association for Classification Agent  to the ClassificationCollaboratorAgent
                      document_classification_agent_id = event["ResourceProperties"]["CLASSIFICATION_AGENT_ID"]
                      document_classification_agent_alias_id = event["ResourceProperties"]["CLASSIFICATION_AGENT_ALIAS_ID"]
                      document_classification_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{document_classification_agent_id}/{document_classification_agent_alias_id}"

                      print(f"Associating Classification Agent {document_classification_agent_id} as Collaborator to the ClassificationCollab Agent...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=classificationcollab_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": document_classification_agent_alias_arn},
                          collaboratorName="DocumentClassificationAgent",
                          collaborationInstruction="This agent classifies standard documents  like PDF, PNG, JPEG into categories such as Claims Settlement Demand Package, Insurance Policy Document, Standard Operating Procedure, Crash Videos, Chat Transcripts and returning the metadata associated with each file type.",
                          relayConversationHistory="DISABLED"
                      )

                      # Add association for Transcription Classification Agent  to the ClassificationCollaboratorAgent
                      transcription_classification_agent_id = event["ResourceProperties"]["TRANSCRIPTION_AGENT_ID"]
                      transcription_classification_agent_alias_id = event["ResourceProperties"]["TRANSCRIPTION_AGENT_ALIAS_ID"]
                      transcription_classification_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{transcription_classification_agent_id}/{transcription_classification_agent_alias_id}"

                      print(f"Associating Transcription Classification Agent {transcription_classification_agent_id} as Collaborator to the ClassificationCollab Agent...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=classificationcollab_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": transcription_classification_agent_alias_arn},
                          collaboratorName="TranscriptionClassificationAgent",
                          collaborationInstruction="This agent classifies audio transcripts in mp4,mp3 format into FNOL, Third-Party, Claim Adjuster conversatio categories and returns the metadata associated with each file type.",
                          relayConversationHistory="DISABLED"
                      )

                      print(f"Preparing Collaborator Supervisor Agent .....VERSION IS DRAFT")
                      bedrock_client.prepare_agent(
                          agentId=classificationcollab_agent_id
                      )

                      time.sleep(20)
                      print(f"Preparing Collaborator Supervisor Agent NEW VERSION BEING CREATED")
                      new_alias_response = bedrock_client.create_agent_alias(agentAliasName="Version2",
                        agentId=classificationcollab_agent_id)
                      print(f"New alias for collaborator agent:: {new_alias_response}")
                      time.sleep(20)
                      print(f"New agent alias ARN :: {new_alias_response['agentAlias']['agentAliasArn']} ")
                      print(f"New agent alias ID :: {new_alias_response['agentAlias']['agentAliasId']} ")
                     

                      bedrock_client.associate_agent_collaborator(
                          agentId=supervisor_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": new_alias_response['agentAlias']['agentAliasArn']},
                          collaboratorName="ClassificationCollaboratorAgent",
                          collaborationInstruction="This agent converts documents like docx,doc,ppt,xls,xlsx,excel into standard formats like PDF as well as classifies standardized documents like mp4,mp3,png,jpeg,pdf into categories such as Claims Documents Package, Insurance Policy Document, Standard Operating Procedure, Crash Videos, Chat Transcripts etc.",
                          relayConversationHistory="DISABLED"
                      )


                      # Add association for ClaimsDocumentsPacakage Agent to Supervisor agent
                      settlement_demand_agent_id = event["ResourceProperties"]["SETTLEMENTDEMAND_AGENT_ID"]
                      settlement_demand_agent_alias_id = event["ResourceProperties"]["SETTLEMENTDEMAND_AGENT_ALIAS_ID"]
                      settlement_demand_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{settlement_demand_agent_id}/{settlement_demand_agent_alias_id}"

                      print(f"Associating ClaimsDocumentsPackage Agent {settlement_demand_agent_id} as Collaborator...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=supervisor_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": settlement_demand_agent_alias_arn},
                          collaboratorName="ClaimsDocumentsAgent",
                          collaborationInstruction="This agent preprocesses Claims Documents Package Documents into structured Markup Text.",
                          relayConversationHistory="DISABLED"
                      )

                      # Add association for vehicle damage analysis agent to Supervisor agent
                      vehicle_damage_agent_id = event["ResourceProperties"]["VEHICLEDAMAGEPROCESS_AGENT_ID"]
                      vehicle_damage_agent_alias_id = event["ResourceProperties"]["VEHICLEDAMAGEPROCESS_AGENT_ALIAS_ID"]
                      vehicle_damage_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{vehicle_damage_agent_id}/{vehicle_damage_agent_alias_id}"

                      print(f"Associating VehicleDamageProcessing Agent {vehicle_damage_agent_id} as Collaborator...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=supervisor_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": vehicle_damage_agent_alias_arn},
                          collaboratorName="VehicleDamageProcessingAgent",
                          collaborationInstruction="This agent processes Vehicle Damage Images into Analysis Text.",
                          relayConversationHistory="DISABLED"
                      )

                      # Add association for audio video analysis agent to Supervisor agent
                      audio_video_agent_id = event["ResourceProperties"]["AUDIOVIDEO_PROCESS_AGENT_ID"]
                      audio_video_agent_alias_id = event["ResourceProperties"]["AUDIOVIDEO_PROCESS_AGENT_ALIAS_ID"]
                      audio_video_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{audio_video_agent_id}/{audio_video_agent_alias_id}"

                      print(f"Associating AudioVideoProcessing Agent {audio_video_agent_id} as Collaborator...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=supervisor_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": audio_video_agent_alias_arn},
                          collaboratorName="AudioVideoProcessingAgent",
                          collaborationInstruction="This agent processes Audio Video transcripts related to claims recordings.",
                          relayConversationHistory="DISABLED"
                      )

                      # Add association for repair estimate agent to Supervisor agent
                      repair_estimate_agent_id = event["ResourceProperties"]["REPAIR_ESTIMATE_AGENT_ID"]
                      repair_estimate_agent_alias_id = event["ResourceProperties"]["REPAIR_ESTIMATE_AGENT_ALIAS_ID"]
                      repair_estimate_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{repair_estimate_agent_id}/{repair_estimate_agent_alias_id}"

                      print(f"Associating Repair Estimate Agent {repair_estimate_agent_id} as Collaborator...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=supervisor_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": repair_estimate_agent_alias_arn},
                          collaboratorName="VehicleRepairEstimateProcessingAgent",
                          collaborationInstruction="This agent processes Vehicle Repair estimates.",
                          relayConversationHistory="DISABLED"
                      )

                      # Add association for policy agent to Supervisor agent
                      policy_agent_id = event["ResourceProperties"]["POLICY_AGENT_ID"]
                      policy_agent_alias_id = event["ResourceProperties"]["POLICY_AGENT_ALIAS_ID"]
                      policy_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{policy_agent_id}/{policy_agent_alias_id}"

                      print(f"Associating Policy  Agent {policy_agent_alias_id} as Collaborator...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=supervisor_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": policy_agent_alias_arn},
                          collaboratorName="InsurancePolicyProcessingAgent",
                          collaborationInstruction="This agent processes Insurance Policies",
                          relayConversationHistory="DISABLED"
                      )

                      # Add association for unclassified agent to Supervisor agent
                      unclassified_agent_id = event["ResourceProperties"]["UNCLASSIFIED_AGENT_ID"]
                      unclassified_agent_alias_id = event["ResourceProperties"]["UNCLASSIFIED_AGENT_ALIAS_ID"]
                      unclassified_agent_alias_arn = f"arn:aws:bedrock:{aws_region}:{aws_account_id}:agent-alias/{unclassified_agent_id}/{unclassified_agent_alias_id}"

                      print(f"Associating Unclassified  Agent {unclassified_agent_alias_id} as Collaborator...")
                      bedrock_client.associate_agent_collaborator(
                          agentId=supervisor_agent_id,
                          agentVersion="DRAFT",
                          agentDescriptor={"aliasArn": unclassified_agent_alias_arn},
                          collaboratorName="UnclassifiedDocumentProcessingAgent",
                          collaborationInstruction="This agent processes Miscellaneous Documents",
                          relayConversationHistory="DISABLED"
                      )

                      print(f"Preparing Main Supervisor Agent {supervisor_agent_id}...")
                      bedrock_client.prepare_agent(
                          agentId=supervisor_agent_id
                      )

                      response_data["Status"] = "Success"
                      print("Multi-Agent Collaboration setup complete.")

                  elif request_type == "Delete":
                      print("Skipping delete operation. No cleanup required.")
                      response_data["Status"] = "Skipped"

              except Exception as e:
                  print(f"Error: {str(e)}")
                  response_data["Error"] = str(e)
                  status = cfnresponse.FAILED  # Mark as failed explicitly

              finally:
                  print("Sending response to CloudFormation...")
                  cfnresponse.send(event, context, status, response_data)
                  print("Response sent successfully.")

# Step X+1: Custom Resource to Trigger Lambda
  MultiAgentCollaborationCustomResource:
    Type: Custom::MultiAgentCollaboration
    Properties:
      ServiceToken: !GetAtt MultiAgentCollaborationLambda.Arn
      ServiceTimeout: 400
      AWS_ACCOUNT_ID: !Ref AWS::AccountId  # Pass Account ID
      AWS_REGION: !Ref AWS::Region        # Pass Region
      SUPERVISOR_AGENT_ID: !Ref DocumentPreprocessingSupervisorAgent
      CLASSIFICATIONCOLLAB_AGENT_ID: !Ref ClassificationCollaboratorAgent
      CLASSIFICATIONCOLLAB_AGENT_ALIAS_ID: !GetAtt ClassificationCollaboratorAgentAlias.AgentAliasId  # New Alias ID
      SETTLEMENTDEMAND_AGENT_ID: !Ref ClaimsDocumentsPackageProcessingAgent
      SETTLEMENTDEMAND_AGENT_ALIAS_ID: !GetAtt ClaimsDocumentsPackageProcessingAgentAlias.AgentAliasId  # New Alias ID
      VEHICLEDAMAGEPROCESS_AGENT_ID: !Ref VehicleDamageAnalysisProcessingAgent
      VEHICLEDAMAGEPROCESS_AGENT_ALIAS_ID: !GetAtt VehicleDamageAnalysisAgentAlias.AgentAliasId  # New Alias ID
      AUDIOVIDEO_PROCESS_AGENT_ID: !Ref AudioVideoTranscriptProcessingAgent
      AUDIOVIDEO_PROCESS_AGENT_ALIAS_ID: !GetAtt AudioVideoTranscriptProcessingAgentAlias.AgentAliasId
      CONVERSION_AGENT_ID: !Ref DocumentConversionAgent  # New Conversion Agent
      CONVERSION_AGENT_ALIAS_ID: !GetAtt DocumentConversionAgentAlias.AgentAliasId  # New Conversion Agent Alias  
      SUPER_AGENT_EXECUTION_ROLE_ARN: !GetAtt BedrockAgentExecutionRoleSupervisorAgent.Arn
      CLASSFN_COLLAB_AGENT_EXECUTION_ROLE_ARN: !GetAtt BedrockAgentExecutionRoleClassfnCollab.Arn
      AGENT_NAME: "DocumentPreprocessingSupervisor"
      CLASSIFICATIONCOLLAB_AGENT_NAME: "ClassificationCollaborator"
      FOUNDATION_MODEL: "anthropic.claude-3-5-haiku-20241022-v1:0"
      CLASSIFICATION_AGENT_ID: !Ref DocumentClassificationAgent
      CLASSIFICATION_AGENT_ALIAS_ID: !GetAtt DocumentClassificationAgentAlias.AgentAliasId
      TRANSCRIPTION_AGENT_ID: !Ref TranscriptionClassificationAgent
      TRANSCRIPTION_AGENT_ALIAS_ID: !GetAtt TranscriptionClassificationAgentAlias.AgentAliasId
      REPAIR_ESTIMATE_AGENT_ID: !Ref VehicleRepairEstimateProcessingAgent
      REPAIR_ESTIMATE_AGENT_ALIAS_ID: !GetAtt VehicleRepairEstimateProcessingAgentAlias.AgentAliasId
      POLICY_AGENT_ID: !Ref InsurancePolicyDocumentProcessingAgent
      POLICY_AGENT_ALIAS_ID: !GetAtt InsurancePolicyDocumentProcessingAgentAlias.AgentAliasId
      UNCLASSIFIED_AGENT_ID: !Ref UnclassifiedDocumentProcessingAgent
      UNCLASSIFIED_AGENT_ALIAS_ID: !GetAtt UnclassifiedDocumentProcessingAgentAlias.AgentAliasId


### OpenSearch Serverless Encryption Security Policy
  OpenSearchServerlessEncryptionPolicy:
    Type: AWS::OpenSearchServerless::SecurityPolicy
    Properties:
      Name: "kb-os-encryption-policy"
      Type: "encryption"
      Description: "Encryption policy for the knowledge base collection"
      Policy:
        Fn::Sub: |
          {
            "Rules": [
              {
                "ResourceType": "collection",
                "Resource": ["collection/${OpenSearchCollectionName}"]
              }
            ],
            "AWSOwnedKey": true
          }

### OpenSearch Serverless Network Security Policy
  OpenSearchServerlessNetworkPolicy:
    Type: AWS::OpenSearchServerless::SecurityPolicy
    Properties:
      Name: "kb-os-network-policy"
      Type: "network"
      Description: "Network policy for the knowledge base collection"
      Policy:
        Fn::Sub: |
          [{
            "Rules": [
              {
                "ResourceType": "collection",
                "Resource": ["collection/${OpenSearchCollectionName}"]
              }
            ],
            "AllowFromPublic": true
          }]

### OpenSearch Serverless Data Access Policy
  OpenSearchServerlessDataAccessPolicy:
    Type: AWS::OpenSearchServerless::AccessPolicy
    Properties:
      Name: "kb-os-data-access-policy"
      Type: "data"
      Description: "Data access policy for the Bedrock Knowledge Base"
      Policy:
        Fn::Sub: |
          [{
            "Description": "Access policy for the Knowledge Base",
            "Rules": [
              {
                "ResourceType": "index",
                "Resource": ["index/*/*"],
                "Permission": ["aoss:*"]
              },
              {
                "ResourceType": "collection",
                "Resource": ["collection/${OpenSearchCollectionName}"],
                "Permission": ["aoss:*"]
              }
            ],
            "Principal": ["${BedrockKnowledgeBaseRole.Arn}", "${CreateOSIndexLambdaRole.Arn}"]
          }]

### OpenSearch Serverless Collection for vector search indexing
  OpenSearchServerlessCollection:
    Type: AWS::OpenSearchServerless::Collection
    Properties:
      Name: !Ref OpenSearchCollectionName
      Type: "VECTORSEARCH"
      Description: "Collection for the knowledge base"
    DependsOn:
      - OpenSearchServerlessEncryptionPolicy
      - OpenSearchServerlessNetworkPolicy

### IAM Role for Bedrock Knowledge Base to access S3 and OpenSearch
  BedrockKnowledgeBaseRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: bedrock.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: "KBAccessPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:ListBucket
                Resource:
                  - !GetAtt KBDataBucket.Arn
                  - !Sub "${KBDataBucket.Arn}/*"
              - Effect: Allow
                Action:
                  - aoss:APIAccessAll
                Resource:
                  - !GetAtt OpenSearchServerlessCollection.Arn
              - Effect: Allow
                Action:
                  - bedrock:InvokeModel
                Resource: 
                  - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0"
                  - !Sub "arn:aws:bedrock:${AWS::Region}::foundation-model/amazon.titan-embed-text-v2:0"

### Lambda Layer for OpenSearch and AWS4Auth dependencies
  CreateOSIndexLambdaLayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      LayerName: "CreateOSIndexLambdaLayer"
      Description: "Lambda layer for OpenSearch and AWS4Auth dependencies"
      Content: 
        S3Bucket: !Ref DestinationBucket
        S3Key: "lambda-layer-opensearch-python3.10-x86_64.zip"
      CompatibleRuntimes:
        - python3.10
    DependsOn: CustomCopyResource

### IAM Role for the index creation Lambda function
  CreateOSIndexLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: "CreateOSIndexPolicy"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - aoss:CreateIndex
                  - aoss:DeleteIndex
                  - aoss:DescribeIndex
                  - aoss:UpdateIndex
                Resource:
                  - !Sub "${OpenSearchServerlessCollection.Arn}/index/*"
              - Effect: Allow
                Action:
                  - aoss:APIAccessAll
                Resource:
                  - !GetAtt OpenSearchServerlessCollection.Arn
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "arn:aws:logs:*:*:*"

### Lambda function to create the index in the OpenSearch collection
  CreateOSIndexLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "CreateOSIndexFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      Timeout: 900
      Role: !GetAtt CreateOSIndexLambdaRole.Arn
      Layers:
        - !Ref CreateOSIndexLambdaLayer
      Code:
        ZipFile: |
          import boto3
          import time
          from opensearchpy import OpenSearch, RequestsHttpConnection
          from requests_aws4auth import AWS4Auth
          import json
          import os
          import cfnresponse

          def lambda_handler(event, context):
              print(event)
              print("Now sleep for 2 mins....")
              time.sleep(120)
              region = os.environ['AWS_REGION']
              service = 'aoss'
              credentials = boto3.Session().get_credentials()
              awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token)

              collection_arn = event['ResourceProperties']['OpenSearchCollectionArn']
              endpoint = f"{collection_arn.split('/')[-1]}.{region}.aoss.amazonaws.com"
              index_name = event['ResourceProperties']['IndexName']

              client = OpenSearch(
                  hosts=[{'host': endpoint, 'port': 443}],
                  http_auth=awsauth,
                  use_ssl=True,
                  verify_certs=True,
                  connection_class=RequestsHttpConnection
              )

              try:
                  if event['RequestType'] == 'Delete':
                      client.indices.delete(index=index_name)
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                      return
                  # Assume Create for simplicity
                  index_body = {
                      "settings": {
                          "index": {
                            "knn" : "true"
                          }
                      },
                      "mappings": {
                          "properties": {
                              "bedrock-knowledge-base-default-vector": {
                                  "type": "knn_vector",
                                  "dimension": 1024,
                                  "method": {
                                      "engine": "faiss",
                                      "name": "hnsw",
                                      "space_type": "l2",
                                      "parameters": {}
                                  }
                              },
                              "AMAZON_BEDROCK_METADATA": {
                                    "type": "text",
                                    "index": "false"
                              },
                              "AMAZON_BEDROCK_TEXT_CHUNK": {
                                    "type": "text"
                              },
                              "id": {
                                    "type": "text",
                                    "fields": {
                                        "keyword": {
                                            "type": "keyword",
                                            "ignore_above": 256
                                        }
                                    }
                              },
                              "policynumber": {
                                "type": "text",
                                "fields": {
                                  "keyword": {
                                    "type": "keyword",
                                    "ignore_above": 256
                                  }
                                }
                              },
                              "type": {
                                "type": "text",
                                "fields": {
                                  "keyword": {
                                    "type": "keyword",
                                    "ignore_above": 256
                                  }
                                }
                              },
                              "x-amz-bedrock-kb-source-uri": {
                                "type": "text",
                                "fields": {
                                  "keyword": {
                                    "type": "keyword",
                                    "ignore_above": 256
                                  }
                                }
                              }
                          }
                      }
                  }
                  response = client.indices.create(index=index_name, body=index_body)
                  time.sleep(60)
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': response})
              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})

### Custom Resource to trigger the index creation Lambda
  CreateOSIndexCustomResource:
    Type: Custom::CreateOSIndex
    Properties:
      ServiceToken: !GetAtt CreateOSIndexLambdaFunction.Arn
      ServiceTimeout: 900
      OpenSearchCollectionArn: !GetAtt OpenSearchServerlessCollection.Arn
      IndexName: !Ref IndexName
    DependsOn:
      - OpenSearchServerlessCollection
      - OpenSearchServerlessEncryptionPolicy
      - OpenSearchServerlessNetworkPolicy
      - OpenSearchServerlessDataAccessPolicy

### Bedrock Knowledge Base (uses the OpenSearch collection for vector storage)
  BedrockKnowledgeBase:
    Type: AWS::Bedrock::KnowledgeBase
    DependsOn: CreateOSIndexCustomResource
    Properties:
      Name: !Ref KnowledgeBaseName
      Description: !Ref KnowledgeBaseDescription
      RoleArn: !GetAtt BedrockKnowledgeBaseRole.Arn
      KnowledgeBaseConfiguration:
        Type: "VECTOR"
        VectorKnowledgeBaseConfiguration:
          EmbeddingModelArn: !Sub "arn:${AWS::Partition}:bedrock:${AWS::Region}::foundation-model/amazon.titan-embed-text-v2:0"
      StorageConfiguration:
        Type: "OPENSEARCH_SERVERLESS"
        OpensearchServerlessConfiguration:
          CollectionArn: !GetAtt OpenSearchServerlessCollection.Arn
          VectorIndexName: !Ref IndexName
          FieldMapping:
            VectorField: "bedrock-knowledge-base-default-vector"
            TextField: "AMAZON_BEDROCK_TEXT_CHUNK"
            MetadataField: "AMAZON_BEDROCK_METADATA"

### Bedrock Data Source that points to the KBDataBucket
  BedrockDataSource:
    Type: AWS::Bedrock::DataSource
    Properties:
      KnowledgeBaseId: !Ref BedrockKnowledgeBase
      Name: "InsuranceDocumentsDataSource"
      Description: "Data source for processed insurance documents in S3"
      DataSourceConfiguration:
        Type: "S3"
        S3Configuration:
          BucketArn: !GetAtt KBDataBucket.Arn
      VectorIngestionConfiguration:
        ChunkingConfiguration:
          ChunkingStrategy: "NONE"
      
# Step : Queue status to know what is being processed

  CheckQueueStatusLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: CheckQueueStatusLambdaRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole

      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

      Policies:
        - PolicyName: CheckQueueStatusSQSPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - sqs:SendMessage
                  - sqs:ReceiveMessage
                  - sqs:DeleteMessage
                  - sqs:GetQueueAttributes
                  - sqs:GetQueueUrl
                Resource:
                  - !GetAtt ProcessingQueue.Arn


  CheckQueueStatusLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "CheckQueueStatusLambdaFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      # Role: !GetAtt LambdaExecutionRole.Arn
      Role: !GetAtt CheckQueueStatusLambdaExecutionRole.Arn
      Environment:
        Variables:
          PROCESSING_QUEUE_URL: !Ref ProcessingQueue
      Code:
        ZipFile: |
          import json
          import boto3
          import os

          # Initialize SQS client
          sqs_client = boto3.client('sqs')
          PROCESSING_QUEUE_URL = os.environ['PROCESSING_QUEUE_URL']

          def lambda_handler(event, context):
              try:
                  # Receive messages from the SQS queue
                  response = sqs_client.receive_message(
                      QueueUrl=PROCESSING_QUEUE_URL,
                      MaxNumberOfMessages=10,  # Fetch up to 10 messages at once
                      WaitTimeSeconds=2
                  )
                  
                  messages = response.get('Messages', [])
                  queue_status = []

                  for message in messages:
                      message_body = json.loads(message['Body'])
                      queue_status.append({
                          'fileName': message_body.get('fileName', 'Unknown'),
                          'status': message_body.get('status', 'Unknown')
                      })

                  return {
                      'statusCode': 200,
                      'headers': {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'},
                      'body': json.dumps({'queue': queue_status})
                  }

              except Exception as e:
                  return {
                      'statusCode': 500,
                      'body': json.dumps({'error': str(e)})
                  }

### Lambda function role to retrieve relevant data and call Bedrock RetrieveAndGenerate API
  PolicyClaimRetrievalLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: PolicyRetrievalLambdaPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "arn:aws:logs:*:*:*"
              - Effect: Allow
                Action:
                  - bedrock:Retrieve
                  - bedrock:RetrieveAndGenerate
                Resource: !Sub arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:knowledge-base/${BedrockKnowledgeBase}
              - Effect: Allow
                Action:
                  - bedrock:InvokeModel
                Resource: !Sub arn:aws:bedrock:${AWS::Region}::foundation-model/anthropic.claude-3-haiku-20240307-v1:0

### Lambda function to retrieve relevant data and call Bedrock RetrieveAndGenerate API
  RetrievalLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: "RetrieveAndGenerateFunction"
      Handler: index.lambda_handler
      Runtime: python3.10
      Timeout: 180  # Timeout set for 3 minutes
      Role: !GetAtt PolicyClaimRetrievalLambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import json
          import os

          bedrock_agent_runtime = boto3.client(
              'bedrock-agent-runtime', 
              region_name=os.environ.get("AWS_REGION")
          )

          def lambda_handler(event, context):
              # Parse the POST body
              body = json.loads(event.get('body', '{}'))
              
              policy_number = body.get('policy_number', '')
              claim_number = body.get('claim_number', '')
              question = body.get('question', '')
              
              # Build the query from available inputs.
              query_parts = []
              if policy_number:
                  query_parts.append(f"policy number {policy_number}")
              if claim_number:
                  query_parts.append(f"claim number {claim_number}")
              if question:
                  query_parts.append(question)
              query_text = " ".join(query_parts) if query_parts else "Retrieve relevant information."
              
              # Build filter conditions if provided
              filter_conditions = []
              if policy_number:
                  filter_conditions.append({"equals": {"key": "policyNumber", "value": policy_number}})
              if claim_number:
                  filter_conditions.append({"equals": {"key": "claimNumber", "value": claim_number}})
              
              # Build the vector search configuration conditionally.
              vector_search_configuration = {"numberOfResults": 100}
              if filter_conditions:
                  if len(filter_conditions) == 1:
                      filter_expr = filter_conditions[0]
                  else:
                      filter_expr = {"and": filter_conditions}
                  vector_search_configuration["filter"] = filter_expr

              response = bedrock_agent_runtime.retrieve_and_generate(
                  input={"text": query_text},
                  retrieveAndGenerateConfiguration={
                      "type": "KNOWLEDGE_BASE",
                      "knowledgeBaseConfiguration": {
                          "knowledgeBaseId": os.environ["KB_ID"],
                          "modelArn": f"arn:aws:bedrock:{os.environ['AWS_REGION']}::foundation-model/anthropic.claude-3-haiku-20240307-v1:0",
                          "retrievalConfiguration": {
                              "vectorSearchConfiguration": vector_search_configuration
                          },
                          "generationConfiguration": {
                              "inferenceConfig": {
                                  "textInferenceConfig": {
                                      "maxTokens": 8192,
                                      "temperature": 0,
                                      "topP": 0.54
                                  }
                              }
                          }
                      }
                  }
              )
              
              generated_text = response['output']['text']
              print(f"Generated response: {generated_text}")
              
              return {
                  "statusCode": 200,
                  "body": json.dumps({"response": generated_text})
              }


      Environment:
        Variables:
          KB_ID: !Ref BedrockKnowledgeBase

# Step 5c: API Gateway Definition
  DocumentProcessingApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: "DocumentProcessingAPI"
      Description: "API Gateway to handle UI, document uploads, and supervisor invocation"
      EndpointConfiguration:
        Types:
          - REGIONAL

# Step 6: Define API Gateway Resources & Methods
  UIResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ParentId: !GetAtt DocumentProcessingApi.RootResourceId
      PathPart: "ui"

  UIMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref UIResource
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UserInterfaceLambdaFunction.Arn}/invocations"

# Step 6: Define API Gateway Resources & Methods
  UploadResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ParentId: !GetAtt DocumentProcessingApi.RootResourceId
      PathPart: "upload"

  UploadMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref UploadResource
      HttpMethod: POST
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${UploadLambdaFunction.Arn}/invocations"
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true

  UploadOptions:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref UploadResource
      HttpMethod: OPTIONS
      AuthorizationType: NONE
      Integration:
        Type: MOCK
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"
              method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type'"
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true

  InvokeSupervisorResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ParentId: !GetAtt DocumentProcessingApi.RootResourceId
      PathPart: "invoke-supervisor"

  InvokeSupervisorMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref InvokeSupervisorResource
      HttpMethod: POST
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        TimeoutInMillis: 180000  # Set timeout to 2 minutes (120,000 milliseconds)
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${InvokeSupervisorLambdaFunction.Arn}/invocations"
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true

  InvokeSupervisorOptions:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref InvokeSupervisorResource
      HttpMethod: OPTIONS
      AuthorizationType: NONE
      Integration:
        Type: MOCK
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"
              method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type'"
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true

# Check queue for in progress documents
  CheckQueueResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ParentId: !GetAtt DocumentProcessingApi.RootResourceId
      PathPart: "check-queue"

  CheckQueueMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref CheckQueueResource
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CheckQueueStatusLambdaFunction.Arn}/invocations"
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true

# Add Permission for API Gateway to Invoke Check Queue Lambda
  ApiGatewayLambdaPermissionCheckQueue:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !Ref CheckQueueStatusLambdaFunction
      Principal: "apigateway.amazonaws.com"

############## Query KB START ##############
# Add Permission for API Gateway to Query KB Lambda function
  ApiGatewayLambdaPermissionCheckKB:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !Ref RetrievalLambdaFunction
      Principal: "apigateway.amazonaws.com"
# Query KB
  CheckKBResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ParentId: !GetAtt DocumentProcessingApi.RootResourceId
      PathPart: "ask-questions"
# Query KB
  CheckKBMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref CheckKBResource
      HttpMethod: POST
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RetrievalLambdaFunction.Arn}/invocations"
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true

############## Query KB END ##############

############SYNC KB START##################
# Add Permission for API Gateway for SYNC KB Lambda function            
  ApiGatewayLambdaPermissionSyncKB:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref SyncKnowledgeBaseFunction
      Action: "lambda:InvokeFunction"
      Principal: "apigateway.amazonaws.com"

  SyncKBResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ParentId: !GetAtt DocumentProcessingApi.RootResourceId
      PathPart: "sync-kb"

  SyncKBMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      ResourceId: !Ref SyncKBResource
      HttpMethod: POST
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SyncKnowledgeBaseFunction.Arn}/invocations"
      MethodResponses:
        - StatusCode: 200
          ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true
############SYNC KB END##################

# Step 7: Deploy API Gateway
  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn:
      - UIMethod
      - UploadMethod
      - UploadOptions
      - InvokeSupervisorMethod
      - InvokeSupervisorOptions
      - CheckQueueMethod  # Add this to the dependencies
      - CheckKBMethod
      - SyncKBMethod
    Properties:
      RestApiId: !Ref DocumentProcessingApi
      StageName: prod

# Step 8: Grant API Gateway Permission to Invoke Lambdas
  ApiGatewayLambdaPermissionUI:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !Ref UserInterfaceLambdaFunction
      Principal: "apigateway.amazonaws.com"

  ApiGatewayLambdaPermissionUpload:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !Ref UploadLambdaFunction
      Principal: "apigateway.amazonaws.com"

# Step 8b: API Gateway Permission for Invoke Supervisor Lambda
  ApiGatewayLambdaPermissionInvokeSupervisor:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !Ref InvokeSupervisorLambdaFunction
      Principal: "apigateway.amazonaws.com"


# Step 9: Outputs (Now includes Invoke Supervisor API)
Outputs:
  ApiGatewayInvokeURL:
    Description: "Invoke URL for API Gateway"
    Value: !Sub "https://${DocumentProcessingApi}.execute-api.${AWS::Region}.amazonaws.com/prod/ui"

  S3BucketName:
    Description: "S3 bucket for storing uploaded documents"
    Value: !Ref DocumentUploadBucket

  KnowledgeBaseDataBucket:
    Description: "S3 bucket storing the preprocessed documents that serve as the data source for the Bedrock Knowledge Base"
    Value: !Ref KBDataBucket

  SampleDataBucketName:
    Description: "S3 Bucket for storing sample test data"
    Value: !Ref SampleDataBucket

  InvokeSupervisorAPI:
    Description: "Invoke URL for calling the Supervisor Agent"
    Value: !Sub "https://${DocumentProcessingApi}.execute-api.${AWS::Region}.amazonaws.com/prod/invoke-supervisor"

  DocumentPreprocessingSupervisorAgentId:
    Description: "Bedrock Agent ID for Document Preprocessing"
    Value: !Ref DocumentPreprocessingSupervisorAgent

  ClaimsDocumentsPackageProcessingAgentId:
    Description: "Bedrock Agent ID for ClaimsDocumentsPackageProcessing Agent"
    Value: !Ref ClaimsDocumentsPackageProcessingAgent

  ClaimsDocumentsPackageProcessingAgentAliasId:
    Description: "Alias ID for ClaimsDocumentsPackageProcessing Agent"
    Value: !GetAtt ClaimsDocumentsPackageProcessingAgentAlias.AgentAliasId

  ClassificationCollaboratorAgentId:
    Description: "Bedrock Agent ID for Classification Agent"
    Value: !Ref ClassificationCollaboratorAgent

  DocumentConversionAgentId:
    Description: "Bedrock Agent ID for Document conversion  Agent"
    Value: !Ref DocumentConversionAgent

  DocumentClassificationAgentId:
    Description: "Bedrock Agent ID for Document classification   Agent"
    Value: !Ref DocumentClassificationAgent

  StandardizationToPDFECRRepositoryUri:
    Description: URI of the ECR repository for PDF standardization
    Value: !GetAtt StandardizationToPDFECRRepository.RepositoryUri

  StandardizationToPDFLambdaFunctionArn:
    Description: ARN of the deployed Lambda function for document standardization to PDF
    Value: !GetAtt StandardizationToPDFLambdaFunction.Arn

  KnowledgeBaseId:
    Description: "The ID of the created Bedrock Knowledge Base"
    Value: !Ref BedrockKnowledgeBase




