Description: >
    Creates a Q Business Application
    
Parameters:
    IdcInstanceArn:
      Type: String
      Description: "ARN of the Identity Center (IDC) Instance"    
      Default: "arn:aws:sso:::instance/ssoins-1111111111111111"
    
    S3BucketName:
      Type: String
      Description: "S3 Bucket to be used as a datasource"
      Default: "yourbucket"
    
    SESSourceEmail:
      Type: String
      Description: "Email address that the chatbot users will be sending inquires to (customer service/ support)"
      Default: "your-verified-email@example.com"
  
    CognitoUserEmail:
      Type: String
      Description: "Email for the initial Cognito user"
      Default: "user@example.com"
    
    CognitoUserPassword:
      Type: String
      Description: "Password for the initial Cognito user"
      NoEcho: true
      MinLength: 8
      Default: "ChangeMe123!" 

Resources:
    QBusinessApp:
      Type: AWS::QBusiness::Application
      Properties:
        DisplayName: qbusiness-example-app
        Description: qbusiness demo app
        IdentityCenterInstanceArn: !Ref IdcInstanceArn
        RoleArn: !GetAtt QBusinessServiceRole.Arn

    QBusinessServiceRole:
      # checkov:skip=CKV_AWS_111: Inline policies are not required for POC purposes
      # checkov:skip=CKV_AWS_107: Wildcard resources required for Q Business service functionality
      # checkov:skip=CKV_AWS_356: Complex IAM policy required for service functionality
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Principal:
                Service: qbusiness.amazonaws.com
              Action: sts:AssumeRole
        Path: "/"
        Policies:
          - PolicyName: QBusinessServicePolicy
            PolicyDocument:
            # checkov:skip=CKV_AWS_111: Wildcard resources required for Q Business service role functionality
              Version: "2012-10-17"
              Statement:
                - Effect: Allow
                  Action:
                    - "qbusiness:CreateApplication"
                    - "qbusiness:DeleteApplication"
                    - "qbusiness:GetApplication"
                    - "qbusiness:UpdateApplication"
                    - "qbusiness:ListApplications"
                    - "qbusiness:CreateIndex"
                    - "qbusiness:DeleteIndex"
                    - "qbusiness:UpdateIndex"
                    - "qbusiness:GetIndex"
                    - "qbusiness:CreateDataSource"
                    - "qbusiness:DeleteDataSource"
                    - "qbusiness:UpdateDataSource"
                    - "qbusiness:GetDataSource"
                    - "qbusiness:CreateWebExperience"
                    - "qbusiness:DeleteWebExperience"
                    - "qbusiness:UpdateWebExperience"
                    - "qbusiness:GetWebExperience"
                    - "identitystore:DescribeUser"
                    - "identitystore:ListUsers"
                    - "logs:DescribeLogGroups"
                    - "logs:CreateLogGroup"
                    - "logs:CreateLogStream"
                    - "logs:PutLogEvents"
                  Resource: "*"

    QBusinessIndex:
      Type: AWS::QBusiness::Index
      DependsOn: QBusinessApp
      Properties:
        ApplicationId: !Ref QBusinessApp
        CapacityConfiguration: 
          Units: 1
        DisplayName: QBusinessIndexExample
        Type: ENTERPRISE
    
    QBusinessRetriever:
      Type: AWS::QBusiness::Retriever
      DependsOn: QBusinessApp
      Properties:
        ApplicationId: !Ref QBusinessApp
        Configuration: 
          NativeIndexConfiguration: 
            IndexId: !GetAtt QBusinessIndex.IndexId
        DisplayName: QBusinessExampleRetriever
        Type: NATIVE_INDEX

    QBusinessWebExperience:
      Type: AWS::QBusiness::WebExperience
      DependsOn: 
        - QBusinessApp
        - QBizWebexperienceRole
      Properties:
        ApplicationId: !Ref QBusinessApp
        RoleArn: !GetAtt QBizWebexperienceRole.Arn

    QBizDataSourceRole:
      # checkov:skip=CKV_AWS_111: Inline policies are not required for POC purposes
      # checkov:skip=CKV_AWS_107: Wildcard resources required for Q Business data source functionality
        Type: 'AWS::IAM::Role'
        Properties:
            AssumeRolePolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Sid: 'AllowsAmazonQToAssumeRoleForServicePrincipal'
                    Effect: Allow
                    Principal:
                      Service: 
                          - qbusiness.amazonaws.com
                    Action: 'sts:AssumeRole'
            Policies:
              - PolicyName: "QbusinessDatasourcepolicy"
                PolicyDocument:
                  Version: "2012-10-17"
                  Statement:
                    - Sid: "AllowsAmazonQToDecryptSecret"
                      Effect: "Allow"
                      Action:
                        - "kms:Decrypt"
                      Resource:
                        - !Sub "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*"
                      Condition:
                        StringLike:
                          "kms:ViaService": !Sub "secretsmanager.${AWS::Region}.amazonaws.com"

                    - Sid: "AllowsAmazonQToGetObjectfromS3"
                      Effect: "Allow"
                      Action:
                        - "s3:GetObject"
                        - "s3:ListBucket"
                      Resource:
                        - !Sub "arn:aws:s3:::${S3BucketName}/*"
                        - !Sub "arn:aws:s3:::${S3BucketName}/OpenAPISchema.yaml"
                      Condition:
                        StringEquals:
                          "aws:ResourceAccount": !Sub "${AWS::AccountId}"

                    - Sid: "AllowsAmazonQToListS3Buckets"
                      Effect: "Allow"
                      Action:
                        - "s3:ListBucket"
                      Resource:
                        - !Sub "arn:aws:s3:::${S3BucketName}"
                      Condition:
                        StringEquals:
                          "aws:ResourceAccount": !Sub "${AWS::AccountId}"

                    - Sid: "AllowsAmazonQToIngestDocuments"
                      Effect: "Allow"
                      Action:
                        - "qbusiness:BatchPutDocument"
                        - "qbusiness:BatchDeleteDocument"
                      Resource:
                        - !Sub "arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:*"

                    - Sid: "AllowsAmazonQToCallPrincipalMappingAPIs"
                      Effect: "Allow"
                      Action:
                        - "qbusiness:PutGroup"
                        - "qbusiness:CreateUser"
                        - "qbusiness:DeleteGroup"
                        - "qbusiness:UpdateUser"
                        - "qbusiness:ListGroups"
                      Resource:
                        - !Sub "arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*"

                    - Sid: "AllowsAmazonQToCreateAndDeleteENI"
                      Effect: "Allow"
                      Action:
                        - "ec2:CreateNetworkInterface"
                        - "ec2:DeleteNetworkInterface"
                      Resource:
                        - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:subnet/*"
                        - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/*"
                        - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:network-interface/*"
                      Condition:
                        StringLike:
                          "aws:RequestTag/AMAZON_Q": !Sub "qbusiness_${AWS::AccountId}_*"
                        ForAllValues:StringEquals:
                          "aws:TagKeys":
                            - "AMAZON_Q"

                    - Sid: "AllowsAmazonQToCreateTags"
                      Effect: "Allow"
                      Action:
                        - "ec2:CreateTags"
                      Resource:
                        - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:network-interface/*"
                      Condition:
                        StringEquals:
                          "ec2:CreateAction": "CreateNetworkInterface"

                    - Sid: "AllowsAmazonQToCreateNetworkInterfacePermission"
                      Effect: "Allow"
                      Action:
                        - "ec2:CreateNetworkInterfacePermission"
                      Resource:
                        - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:network-interface/*"
                      Condition:
                        StringLike:
                          "aws:ResourceTag/AMAZON_Q": !Sub "qbusiness_${AWS::AccountId}_*"

                    - Sid: "AllowsAmazonQToConnectToVPC"
                      Effect: "Allow"
                      Action:
                        - "ec2:DescribeNetworkInterfaces"
                        - "ec2:DescribeAvailabilityZones"
                        - "ec2:DescribeNetworkInterfaceAttribute"
                        - "ec2:DescribeVpcs"
                        - "ec2:DescribeRegions"
                        - "ec2:DescribeSecurityGroups"
                        - "ec2:DescribeNetworkInterfacePermissions"
                        - "ec2:DescribeSubnets"
                      Resource: "*"

    QBizWebexperienceRole:
      # checkov:skip=CKV_AWS_111: Inline policies are not required for POC purposes
      Type: 'AWS::IAM::Role'
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Sid: 'QBusinessTrustPolicy'
              Effect: 'Allow'
              Principal:
                Service:
                  - 'application.qbusiness.amazonaws.com'
              Action:
                - 'sts:AssumeRole'
                - 'sts:SetContext'
              Condition:
                StringEquals:
                  'aws:SourceAccount': !Sub '${AWS::AccountId}'
                ArnEquals:
                  'aws:SourceArn': !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*'
        Policies:
          - PolicyName: "QbusinessWebpolicy"
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Sid: QBusinessConversationPermission
                  Effect: Allow
                  Action:
                    - qbusiness:Chat
                    - qbusiness:ChatSync
                    - qbusiness:ListMessages
                    - qbusiness:ListConversations
                    - qbusiness:DeleteConversation
                    - qbusiness:PutFeedback
                    - qbusiness:GetWebExperience
                    - qbusiness:GetApplication
                    - qbusiness:ListPlugins
                    - qbusiness:GetPlugin
                    - qbusiness:ListPluginActions
                    - qbusiness:GetChatControlsConfiguration
                    - qbusiness:ListIndices
                    - qbusiness:ListDataSources
                  Resource: !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*'

                - Sid: QBusinessPluginDiscoveryPermissions
                  Effect: Allow
                  Action:
                    - qbusiness:ListPluginTypeMetadata
                    - qbusiness:ListPluginTypeActions
                  Resource: !Sub 'arn:aws:qbusiness:${AWS::Region}:${AWS::AccountId}:application/*'

                - Sid: QBusinessKMSDecryptPermissions
                  Effect: Allow
                  Action:
                    - kms:Decrypt
                  Resource:
                    - !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*'
                  Condition:
                    StringLike:
                      'kms:ViaService': !Sub 'qbusiness.${AWS::Region}.amazonaws.com'

                - Sid: QBusinessSetContextPermissions
                  Effect: Allow
                  Action:
                    - sts:SetContext
                  Resource:
                    - 'arn:aws:sts::*:self'
                  Condition:
                    StringLike:
                      'aws:CalledViaLast': 'qbusiness.amazonaws.com'

    DataSourceS3:
        Type: "AWS::QBusiness::DataSource"
        DependsOn: QBusinessApp
        Properties:
          ApplicationId: !Ref QBusinessApp
          IndexId: !GetAtt QBusinessIndex.IndexId
          DisplayName: MyS3DataSource
          RoleArn: !GetAtt 'QBizDataSourceRole.Arn'
          Configuration:
            type: S3
            syncMode: FULL_CRAWL
            connectionConfiguration:
              repositoryEndpointMetadata:
                BucketName: !Sub "${S3BucketName}"
            repositoryConfigurations:
              document:
                fieldMappings:
                  - dataSourceFieldName: content
                    indexFieldName: document_content
                    indexFieldType: STRING

    LambdaKmsKey:
      Type: AWS::KMS::Key
      Properties:
        Description: KMS key for encrypting email sender Lambda function
        KeyUsage: ENCRYPT_DECRYPT # Specify key usage
        EnableKeyRotation: true # enables key rotation
        KeyPolicy:
          Version: '2012-10-17'
          Id: key-default-1
          Statement:
            - Sid: Allow access for Key Administrators
              Effect: Allow
              Principal:
                # AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root' # Replace with specific users/roles if needed
                AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
              Action:
                - kms:*
              Resource: '*'
            - Sid: Allow services to use the key
              Effect: Allow
              Principal:
                Service: 
                  - lambda.amazonaws.com
                  - logs.amazonaws.com
                  - sqs.amazonaws.com
              Action:
                - kms:Encrypt
                - kms:Decrypt
                - kms:ReEncrypt*
                - kms:GenerateDataKey*
                - kms:DescribeKey
              Resource: '*'
            # Remove direct reference to EmailSenderLambdaRole to avoid circular dependency
            - Sid: Allow AWS Account to use the key
              Effect: Allow
              Principal:
                AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
              Action:
                - kms:Encrypt
                - kms:Decrypt
                - kms:GenerateDataKey
                - kms:DescribeKey
              Resource: '*'
           
    # Create DLQ before Lambda to resolve circular dependency
    EmailDLQ:
      Type: AWS::SQS::Queue
      DependsOn: LambdaKmsKey  # Ensure KMS key exists first
      Properties:
        QueueName: email-sender-dlq
        MessageRetentionPeriod: 1209600  # 14 days in seconds
        VisibilityTimeout: 300  # 5 minutes
        KmsMasterKeyId: !GetAtt LambdaKmsKey.Arn

    EmailSenderLambdaRole:
      # checkov:skip=CKV_AWS_111: Inline policies are not required for POC purposes
      Type: AWS::IAM::Role
      DependsOn: 
        - LambdaKmsKey
        - EmailDLQ
      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/AmazonSESFullAccess
        Policies:
          - PolicyName: SQSAccess
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - sqs:SendMessage
                    - sqs:GetQueueAttributes
                    - sqs:GetQueueUrl
                  Resource: !GetAtt EmailDLQ.Arn
          - PolicyName: CloudWatchLogsEncryption
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - kms:Encrypt
                    - kms:Decrypt
                    - kms:GenerateDataKey*
                  Resource: !GetAtt LambdaKmsKey.Arn
    EmailSenderResource:
      Type: AWS::ApiGateway::Resource
      Properties: 
        PathPart: email-sender
        RestApiId: !Ref EmailSenderApi
        ParentId: !GetAtt EmailSenderApi.RootResourceId

    EmailSenderMethod:
      Type: AWS::ApiGateway::Method
      DependsOn: 
        - CognitoAuthorizer
        - CognitoResourceServer
        - CognitoUserPoolClient
      Properties:
        RestApiId: !Ref EmailSenderApi
        ResourceId: !Ref EmailSenderResource
        HttpMethod: POST
        AuthorizationType: COGNITO_USER_POOLS
        AuthorizerId: !Ref CognitoAuthorizer
        AuthorizationScopes:
          - "email-api/send-email"  # Match the scope defined above
        Integration:
          Type: AWS_PROXY
          IntegrationHttpMethod: POST
          Uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EmailSenderFunction.Arn}/invocations
          Credentials: !GetAtt ApiGatewayExecutionRole.Arn

    ApiGatewayExecutionRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service: apigateway.amazonaws.com
              Action: sts:AssumeRole
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
        Policies:
          - PolicyName: ApiGatewayLambdaInvoke
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - lambda:InvokeFunction
                  Resource: !GetAtt EmailSenderFunction.Arn
    EmailSenderDeployment:
      Type: AWS::ApiGateway::Deployment
      DependsOn: EmailSenderMethod
      Properties:
        RestApiId: !Ref EmailSenderApi
        Description: Production deployment for email sender API

    ApiGatewayLogGroup:
      Type: AWS::Logs::LogGroup
      DependsOn: LambdaKmsKey
      Properties:
        LogGroupName: !Join
          - '-'
          - - !Ref EmailSenderApi
            - access-logs 
        KmsKeyId: !GetAtt LambdaKmsKey.Arn  # Use the same KMS key for log encryption
        RetentionInDays: 90  # Set an appropriate retention period 

    EmailSenderFunction:
      # checkov:skip=CKV_AWS_117: VPC access not required for this email sending Lambda function
      # checkov:skip=CKV_AWS_116: DLQ already configured for this Lambda function
      Type: AWS::Lambda::Function
      DependsOn:
        - EmailSenderLambdaRole
        - EmailDLQ
        - LambdaKmsKey
      Properties:
        FunctionName: email-sender-function
        Handler: index.lambda_handler
        Role: !GetAtt EmailSenderLambdaRole.Arn
        ReservedConcurrentExecutions: 100
        DeadLetterConfig:
          TargetArn: !GetAtt EmailDLQ.Arn
        KmsKeyArn: !GetAtt LambdaKmsKey.Arn
        Code:
          ZipFile: |
            import json
            import boto3
            import os
            import logging
            # Set up logging
            logger = logging.getLogger()
            logger.setLevel(logging.INFO)
            ses_client = boto3.client('ses')
            def lambda_handler(event, context):
                logger.info("Function started")
                logger.info(f"Received event: {json.dumps(event)}")
                try:
                    # Parse the body from API Gateway event
                    body = json.loads(event['body']) if isinstance(event.get('body'), str) else event.get('body', {})
                    to_email = body.get('to')
                    subject = body.get('subject')
                    message_body = body.get('body')
                    logger.info(f"Parsed email details: To: {to_email}, Subject: {subject}")
                    if not to_email or not subject or not message_body:
                        logger.error("Missing required fields")
                        return {
                            'statusCode': 400,
                            'headers': {
                                'Content-Type': 'application/json',
                                'Access-Control-Allow-Origin': '*'
                            },
                            'body': json.dumps({'error': 'Missing required fields: to, subject, or body'})
                        }
                    source_email = os.environ.get('SES_SOURCE_EMAIL')
                    logger.info(f"Using source email: {source_email}")
                    if not source_email:
                        logger.error("SES_SOURCE_EMAIL environment variable is not set")
                        return {
                            'statusCode': 500,
                            'headers': {
                                'Content-Type': 'application/json',
                                'Access-Control-Allow-Origin': '*'
                            },
                            'body': json.dumps({'error': 'Server configuration error'})
                        }
                    response = ses_client.send_email(
                        Source=source_email,
                        Destination={
                            'ToAddresses': [to_email],
                        },
                        Message={
                            'Subject': {
                                'Data': subject,
                                'Charset': 'UTF-8'
                            },
                            'Body': {
                                'Text': {
                                    'Data': message_body,
                                    'Charset': 'UTF-8'
                                }
                            }
                        }
                    )
                    logger.info(f"SES response: {response}")
                    return {
                        'statusCode': 200,
                        'headers': {
                            'Content-Type': 'application/json',
                            'Access-Control-Allow-Origin': '*'
                        },
                        'body': json.dumps({'message': 'Email sent successfully'})
                    }
                except Exception as e:
                    logger.error(f"Error: {str(e)}", exc_info=True)
                    return {
                        'statusCode': 500,
                        'headers': {
                            'Content-Type': 'application/json',
                            'Access-Control-Allow-Origin': '*'
                        },
                        'body': json.dumps({'error': f'Failed to send email due to an internal error: {str(e)}'})
                    }

        Runtime: python3.8
        Timeout: 10
        Environment:
          Variables:
            SES_SOURCE_EMAIL: !Ref SESSourceEmail


    EmailSenderApi:
          Type: AWS::ApiGateway::RestApi
          Properties:
            Name: EmailSenderAPI
            Description: API for sending emails via SES
            EndpointConfiguration:
              Types:
                - REGIONAL

    ApiGatewayAccount:
      Type: AWS::ApiGateway::Account
      DependsOn: ApiGatewayCloudWatchRole
      Properties:
        CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchRole.Arn

    ApiGatewayCloudWatchRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service: apigateway.amazonaws.com
              Action: sts:AssumeRole
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs

    EmailSenderStage:
      Type: AWS::ApiGateway::Stage
      DependsOn: 
        - EmailSenderDeployment
        - ApiGatewayAccount
      Properties:
        DeploymentId: !Ref EmailSenderDeployment
        RestApiId: !Ref EmailSenderApi
        StageName: 'dev'
        MethodSettings:
          - ResourcePath: /*
            HttpMethod: '*'
            MetricsEnabled: true
            DataTraceEnabled: true
            LoggingLevel: INFO

    LambdaApiGatewayPermission:
      Type: AWS::Lambda::Permission
      Properties:
        FunctionName: !Ref EmailSenderFunction
        Action: lambda:InvokeFunction
        Principal: apigateway.amazonaws.com
        SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${EmailSenderApi}/*/POST/email-sender
        

    CognitoResourceServer:
      Type: AWS::Cognito::UserPoolResourceServer
      Properties:
        Identifier: "email-api"
        Name: "Email API Resource Server"
        UserPoolId: !Ref CognitoUserPool
        Scopes:
          - ScopeName: "send-email"
            ScopeDescription: "Permission to send emails"

    CognitoUserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: !Sub "${AWS::StackName}-EmailSenderUserPool"
        AdminCreateUserConfig:
          AllowAdminCreateUserOnly: true
        AutoVerifiedAttributes:
          - email
        UsernameAttributes:
          - email
        UsernameConfiguration:
          CaseSensitive: false
        VerificationMessageTemplate:
          DefaultEmailOption: CONFIRM_WITH_CODE
        EmailConfiguration:
          EmailSendingAccount: COGNITO_DEFAULT
        Policies:
          PasswordPolicy:
            MinimumLength: 8
            RequireLowercase: true
            RequireNumbers: true
            RequireSymbols: true
            RequireUppercase: true
        Schema:
          - Name: email
            AttributeDataType: String
            DeveloperOnlyAttribute: false
            Mutable: true
            Required: true
            StringAttributeConstraints:
              MinLength: "0"
              MaxLength: "2048"

    CognitoUserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      DependsOn: QBusinessWebExperience
      Properties:
        UserPoolId: !Ref CognitoUserPool
        ClientName: EmailSenderUserPoolClient
        CallbackURLs: [https://example.chat.qbusiness.us-east-1.on.aws/oauth/callback]
        SupportedIdentityProviders: 
          - COGNITO
        AllowedOAuthFlowsUserPoolClient: true
        AllowedOAuthFlows: 
          - code    
        AllowedOAuthScopes:
          - openid    
          - email     
          - profile   
          - email-api/send-email  
        GenerateSecret: true
        PreventUserExistenceErrors: "ENABLED"
        ExplicitAuthFlows:
          - ALLOW_REFRESH_TOKEN_AUTH
          - ALLOW_USER_PASSWORD_AUTH
        TokenValidityUnits:
          AccessToken: "minutes"
          IdToken: "minutes"
          RefreshToken: "days"
        AccessTokenValidity: 60
        IdTokenValidity: 60
        RefreshTokenValidity: 30
        EnableTokenRevocation: true

    CognitoAuthorizer:
      Type: AWS::ApiGateway::Authorizer
      DependsOn: CognitoUserPool
      Properties:
        Name: CognitoAuthorizer
        Type: COGNITO_USER_POOLS
        IdentitySource: method.request.header.Authorization
        ProviderARNs:
          - !GetAtt CognitoUserPool.Arn
        RestApiId: !Ref EmailSenderApi  

    CognitoDomain:
      Type: AWS::Cognito::UserPoolDomain
      Properties:
        Domain: !Sub "email-${AWS::AccountId}"  
        UserPoolId: !Ref CognitoUserPool

    CreateCognitoUserFunction:
      Type: AWS::Lambda::Function
      DependsOn: CognitoUserPool
      Properties:
        Handler: index.handler
        Role: !GetAtt CreateCognitoUserRole.Arn
        Code:
          ZipFile: |
            import boto3
            import cfnresponse
            import logging

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

            def handler(event, context):
                try:
                    if event['RequestType'] in ['Create', 'Update']:
                        client = boto3.client('cognito-idp')
                        
                        # Get parameters
                        user_pool_id = event['ResourceProperties']['UserPoolId']
                        email = event['ResourceProperties']['Email']
                        password = event['ResourceProperties']['Password']
                        
                        # Check if user exists
                        try:
                            client.admin_get_user(
                                UserPoolId=user_pool_id,
                                Username=email
                            )
                            logger.info(f"User {email} already exists")
                        except client.exceptions.UserNotFoundException:
                            # Create user
                            logger.info(f"Creating user {email}")
                            client.admin_create_user(
                                UserPoolId=user_pool_id,
                                Username=email,
                                UserAttributes=[
                                    {
                                        'Name': 'email',
                                        'Value': email
                                    },
                                    {
                                        'Name': 'email_verified',
                                        'Value': 'true'
                                    }
                                ],
                                MessageAction='SUPPRESS'
                            )
                            
                            # Set password
                            client.admin_set_user_password(
                                UserPoolId=user_pool_id,
                                Username=email,
                                Password=password,
                                Permanent=True
                            )
                        
                        cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                            'Message': f'User {email} processed successfully'
                        })
                    elif event['RequestType'] == 'Delete':
                        # Optionally delete the user on stack deletion
                        cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                            'Message': 'Nothing to do on delete'
                        })
                except Exception as e:
                    logger.error(f"Error: {str(e)}")
                    cfnresponse.send(event, context, cfnresponse.FAILED, {
                        'Error': str(e)
                    })
        Runtime: python3.8
        Timeout: 300
        Environment:
          Variables:
            LOG_LEVEL: INFO

    # IAM role for the Lambda function
    CreateCognitoUserRole:
      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: CognitoAccess
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - cognito-idp:AdminCreateUser
                    - cognito-idp:AdminSetUserPassword
                    - cognito-idp:AdminGetUser
                  Resource: !GetAtt CognitoUserPool.Arn

    # Custom resource to create the user
    CreateCognitoUser:
      Type: Custom::CreateCognitoUser
      DependsOn: 
        - CognitoUserPool
        - CreateCognitoUserFunction
        - EmailDLQ
      Properties:
        ServiceToken: !GetAtt CreateCognitoUserFunction.Arn
        UserPoolId: !Ref CognitoUserPool
        Email: !Ref CognitoUserEmail
        Password: !Ref CognitoUserPassword

Outputs:
    QBizDataSourceRoleARN:
        Value: !GetAtt 'QBizDataSourceRole.Arn'
    QBizWebexperienceRoleARN:
        Value: !GetAtt 'QBizWebexperienceRole.Arn'
    ApiEndpoint:
        Value: !Sub "https://${EmailSenderApi}.execute-api.${AWS::Region}.amazonaws.com/dev"
    ApplicationId:
        Description: "The Q Business Application ID"
        Value: !Ref QBusinessApp
    CognitoUserPoolId:
        Description: The ID of the Cognito User Pool
        Value: !Ref CognitoUserPool
        Export:
          Name: CognitoUserPoolId
    CognitoUserPoolClientId:
        Description: The ID of the Cognito User Pool Client
        Value: !Ref CognitoUserPoolClient
        Export:
          Name: CognitoUserPoolClientId
    CognitoAuthorizationUrl:
      Description: "Cognito Authorization URL"
      Value: !Sub "https://email-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com/oauth2/authorize"
    CognitoTokenUrl:
      Description: "Cognito Token URL"
      Value: !Sub "https://email-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com/oauth2/token"
    FullCognitoDomainUrl:
      Description: "Full Cognito Domain URL"
      Value: !Sub "https://email-${AWS::AccountId}.auth.${AWS::Region}.amazoncognito.com"
    