# The CarRental bot consists of the following:
# 1. IAM role that is used by the bot at runtime
# 2. Inline Bot
# 3. Bot Version
# 4. Alias
Parameters:
  ConnectInstanceArn:
    Type: String
    Description: Enter Connect Instance ARN, it starts with arn and end with 36 characters including 4 -s.
Resources:
  #1. Car rental master flow for an Amazon Connect instance
  CarRentalMainFlow:
    Type: 'AWS::Connect::ContactFlow'
    DependsOn:
      - CallerIntentIntegrationAssociation
      - CallerInfoIntegrationAssociation
      - CarRentalWhisperFlow
    Properties:
      Name: 'CarRentalMainFlow'
      Description: 'Car rental main flow and dependent on Lex bots and rental car wisper flow'
      InstanceArn: 
        Ref: ConnectInstanceArn
      Type: 'CONTACT_FLOW'
      Content: !Join
                - ''
                - - '{"Version":"2019-10-30","StartAction":"2570a42b-b1a2-4169-8170-d4ac4030502a","Metadata":{"entryPointPosition":{"x":40,"y":40},"ActionMetadata":{"Enable rental knowledge base":{"position":{"x":259.2,"y":25.6},"isFriendlyName":true,"children":["Enable rental knowledge base-us5Di4UZZt"],"parameters":{"WisdomAssistantArn":{"displayName":'
                  - '"'
                  - !GetAtt WisdomAssistant.AssistantArn
                  - '"'
                  - '}},"fragments":{"SetContactData":"Enable rental knowledge base-us5Di4UZZt"}},"Enable rental knowledge base-us5Di4UZZt":{"position":{"x":259.2,"y":25.6},"isFriendlyName":true,"dynamicParams":[]},"2570a42b-b1a2-4169-8170-d4ac4030502a":{"position":{"x":56.8,"y":240.8}},"b88c1097-6aca-4776-b599-bfb1cd2a6c70":{"position":{"x":522.4,"y":431.2}},"c3b52218-63fa-456b-b30d-cecefd09cdd7":{"position":{"x":2216.8,"y":-4}},"ef6ad9e9-50e8-446f-90b1-c11986138b31":{"position":{"x":1992,"y":-4}},"fba5678c-6252-4000-882f-179f43e7de30":{"position":{"x":2522.4,"y":114.4}},"bb15027a-7a85-4ed6-80de-3d5489720b6c":{"position":{"x":2013.6,"y":224}},"f90456a0-4f7e-4306-9a47-67ed1e47cb3f":{"position":{"x":1994.4,"y":-187.2}},"ee11fa94-39c0-4527-a86d-4e607d427058":{"position":{"x":1744,"y":259.2}},"64c76090-2e61-4b72-a276-44dd76238ddf":{"position":{"x":1741.6,"y":66.4}},"Check negative sentiment score":{"position":{"x":1740.8,"y":-159.2},"isFriendlyName":true,"conditions":[],"conditionMetadata":[{"id":"752f2a4e-b39b-45b4-bdb6-d01bf88bec7a","operator":{"name":"Is greater than","value":"GreaterThan","shortDisplay":">"},"value":"0.70"},{"id":"95c86fdb-08e8-454d-9c84-cb99955d0bbd","operator":{"name":"Is less or equal","value":"LessThanOrEqualTo","shortDisplay":"<="},"value":"0.70"}]},"Set contact attributes for contact name and memID":{"position":{"x":2205.6,"y":-185.6},"isFriendlyName":true,"dynamicParams":[]},"716197c9-6301-4c18-8a9c-91c2505085a9":{"position":{"x":258.4,"y":242.4}},"5599f63e-c2fd-4a0f-af5e-76d19f382d19":{"position":{"x":266.4,"y":402.4}},"fdfd857d-b2b7-4a52-a668-de441a063fa4":{"position":{"x":2919.2,"y":-188.8}},"03aeee17-8495-4e1f-b060-17fb3ebe46b7":{"position":{"x":3166.4,"y":-145.6}},"8c944950-3ec1-4d7e-a2ad-6b1b8a089d7b":{"position":{"x":2429.6,"y":-191.2},"parameters":{"EventHooks":{"AgentWhisper":{"displayName":"CarRentalWhisperFlow"}}},"contactFlow":{"text":"CarRentalWhisperFlow","id":'
                  - '"'
                  - !GetAtt CarRentalWhisperFlow.ContactFlowArn
                  - '"'
                  - '},"customerOrAgent":false},"dc5a09a3-7ec4-4e31-8e13-0ea59241cdec":{"position":{"x":2672,"y":-190.4},"parameters":{"QueueId":{"displayName":"BasicQueue"}},"queue":{"text":"BasicQueue"}},"5b278d14-5ad1-4174-82a2-3e5b366b765b":{"position":{"x":1079.2,"y":259.2}},"Check if contact is via voice or chat":{"position":{"x":498.4,"y":27.2},"isFriendlyName":true,"conditions":[],"conditionMetadata":[{"id":"0b268137-a509-4443-a4b4-76da55858511","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"CHAT"},{"id":"8ed5c641-5b78-450d-babb-f9aab7edf755","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"VOICE"}]},"f5477ba9-6fc6-4860-80ec-423819ff945a":{"position":{"x":752,"y":27.2},"parameters":{"LexV2Bot":{"AliasArn":{"useLexBotDropdown":false}}},"lexV2BotName":"","conditionMetadata":[{"id":"6e9acc45-81e4-464a-ac71-15a08ec9ed29","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"GetCallerInfo"}]},"c2243812-5b37-4f59-9110-35646528f416":{"position":{"x":1450.4,"y":301.6}},"5857584b-86f9-4d0f-8f94-b438cef5d615":{"position":{"x":1456.8,"y":-12},"conditions":[],"conditionMetadata":[{"id":"c30c0b9f-3f87-4ea0-90d6-f98fe7a187b0","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"NEGATIVE"},{"id":"a4ae6fad-7974-473e-8e4b-797e23d20552","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"NEUTRAL"},{"id":"15f5320a-908e-47ef-a4d8-9f5e65ae1895","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"POSITIVE"}]},"14c15a7f-8bc9-442a-8d99-a17ace8c772c":{"position":{"x":988,"y":28.8},"dynamicParams":[]},"3f298372-232b-44f7-85a1-e87e318cf411":{"position":{"x":1234.4,"y":-5.6},"parameters":{"LexV2Bot":{"AliasArn":{"useLexBotDropdown":false}}},"lexV2BotName":"","conditionMetadata":[{"id":"c9dfa4c2-0039-49a8-90d2-e5f9d43c5b9a","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"AskForSupport"}]}},"Annotations":[],"name":"CarRentalMainFlow","description":"","type":"contactFlow","status":"published","hash":{}},"Actions":[{"Parameters":{"WisdomAssistantArn":'
                  - '"'
                  - !GetAtt WisdomAssistant.AssistantArn
                  - '"'
                  - '},"Identifier":"Enable rental knowledge base","Type":"CreateWisdomSession","Transitions":{"NextAction":"Enable rental knowledge base-us5Di4UZZt","Errors":[{"NextAction":"5599f63e-c2fd-4a0f-af5e-76d19f382d19","ErrorType":"NoMatchingError"}]}},{"Parameters":{"WisdomSessionArn":"$.Wisdom.SessionArn"},"Identifier":"Enable rental knowledge base-us5Di4UZZt","Type":"UpdateContactData","Transitions":{"NextAction":"Check if contact is via voice or chat","Errors":[{"NextAction":"5599f63e-c2fd-4a0f-af5e-76d19f382d19","ErrorType":"NoMatchingError"}]}},{"Parameters":{"FlowLoggingBehavior":"Enabled"},"Identifier":"2570a42b-b1a2-4169-8170-d4ac4030502a","Type":"UpdateFlowLoggingBehavior","Transitions":{"NextAction":"716197c9-6301-4c18-8a9c-91c2505085a9"}},{"Parameters":{},"Identifier":"b88c1097-6aca-4776-b599-bfb1cd2a6c70","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"Text":"Thank you $.Attributes.FirstName $.Attributes.LastName. We apologize for your inconvenience. Since you are a loyal customer, we have applied a 25% discount on your next car rental. Thank you for being a loyal customer and please do not hesitate to contact us if this resolution does not meet with your expectations."},"Identifier":"c3b52218-63fa-456b-b30d-cecefd09cdd7","Type":"MessageParticipant","Transitions":{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","Errors":[{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Negative sentiment score was below or equal to 0.70"},"Identifier":"ef6ad9e9-50e8-446f-90b1-c11986138b31","Type":"MessageParticipant","Transitions":{"NextAction":"c3b52218-63fa-456b-b30d-cecefd09cdd7","Errors":[{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","ErrorType":"NoMatchingError"}]}},{"Parameters":{},"Identifier":"fba5678c-6252-4000-882f-179f43e7de30","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"Text":"Could not determine sentiment score"},"Identifier":"bb15027a-7a85-4ed6-80de-3d5489720b6c","Type":"MessageParticipant","Transitions":{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","Errors":[{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Hello $.Attributes.FirstName $.Attributes.LastName"},"Identifier":"f90456a0-4f7e-4306-9a47-67ed1e47cb3f","Type":"MessageParticipant","Transitions":{"NextAction":"Set contact attributes for contact name and memID","Errors":[{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Sentiment was postive. Try chat session again with a more negative sentiment."},"Identifier":"ee11fa94-39c0-4527-a86d-4e607d427058","Type":"MessageParticipant","Transitions":{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","Errors":[{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Sentiment was neutral. Try chat session again with a more negative sentiment."},"Identifier":"64c76090-2e61-4b72-a276-44dd76238ddf","Type":"MessageParticipant","Transitions":{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","Errors":[{"NextAction":"fba5678c-6252-4000-882f-179f43e7de30","ErrorType":"NoMatchingError"}]}},{"Parameters":{"ComparisonValue":"$.Lex.SentimentResponse.Scores.Negative"},"Identifier":"Check negative sentiment score","Type":"Compare","Transitions":{"NextAction":"bb15027a-7a85-4ed6-80de-3d5489720b6c","Conditions":[{"NextAction":"f90456a0-4f7e-4306-9a47-67ed1e47cb3f","Condition":{"Operator":"NumberGreaterThan","Operands":["0.70"]}},{"NextAction":"ef6ad9e9-50e8-446f-90b1-c11986138b31","Condition":{"Operator":"NumberLessOrEqualTo","Operands":["0.70"]}}],"Errors":[{"NextAction":"bb15027a-7a85-4ed6-80de-3d5489720b6c","ErrorType":"NoMatchingCondition"}]}},{"Parameters":{"Attributes":{"AgentWhisper":"Customer is unhappy and is a platinum member. Customer details are $.Attributes.FirstName $.Attributes.LastName, MemberID: $.Attributes.MemberID"},"TargetContact":"Current"},"Identifier":"Set contact attributes for contact name and memID","Type":"UpdateContactAttributes","Transitions":{"NextAction":"8c944950-3ec1-4d7e-a2ad-6b1b8a089d7b","Errors":[{"NextAction":"03aeee17-8495-4e1f-b060-17fb3ebe46b7","ErrorType":"NoMatchingError"}]}},{"Parameters":{"RecordingBehavior":{"RecordedParticipants":[]}},"Identifier":"716197c9-6301-4c18-8a9c-91c2505085a9","Type":"UpdateContactRecordingBehavior","Transitions":{"NextAction":"Enable rental knowledge base"}},{"Parameters":{"Text":"Sorry we encountered an error."},"Identifier":"5599f63e-c2fd-4a0f-af5e-76d19f382d19","Type":"MessageParticipant","Transitions":{"NextAction":"b88c1097-6aca-4776-b599-bfb1cd2a6c70","Errors":[{"NextAction":"b88c1097-6aca-4776-b599-bfb1cd2a6c70","ErrorType":"NoMatchingError"}]}},{"Parameters":{},"Identifier":"fdfd857d-b2b7-4a52-a668-de441a063fa4","Type":"TransferContactToQueue","Transitions":{"NextAction":"03aeee17-8495-4e1f-b060-17fb3ebe46b7","Errors":[{"NextAction":"03aeee17-8495-4e1f-b060-17fb3ebe46b7","ErrorType":"QueueAtCapacity"},{"NextAction":"03aeee17-8495-4e1f-b060-17fb3ebe46b7","ErrorType":"NoMatchingError"}]}},{"Parameters":{},"Identifier":"03aeee17-8495-4e1f-b060-17fb3ebe46b7","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"EventHooks":{"AgentWhisper":'
                  - '"'
                  - !GetAtt CarRentalWhisperFlow.ContactFlowArn
                  - '"'
                  - '}},"Identifier":"8c944950-3ec1-4d7e-a2ad-6b1b8a089d7b","Type":"UpdateContactEventHooks","Transitions":{"NextAction":"dc5a09a3-7ec4-4e31-8e13-0ea59241cdec","Errors":[{"NextAction":"03aeee17-8495-4e1f-b060-17fb3ebe46b7","ErrorType":"NoMatchingError"}]}},{"Parameters":{"QueueId":'
                  - '"'
                  - !GetAtt InstanceDataCustomResource.Queue
                  - '"'
                  - '},"Identifier":"dc5a09a3-7ec4-4e31-8e13-0ea59241cdec","Type":"UpdateContactTargetQueue","Transitions":{"NextAction":"fdfd857d-b2b7-4a52-a668-de441a063fa4","Errors":[{"NextAction":"03aeee17-8495-4e1f-b060-17fb3ebe46b7","ErrorType":"NoMatchingError"}]}},{"Parameters":{},"Identifier":"5b278d14-5ad1-4174-82a2-3e5b366b765b","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"ComparisonValue":"$.Channel"},"Identifier":"Check if contact is via voice or chat","Type":"Compare","Transitions":{"NextAction":"5599f63e-c2fd-4a0f-af5e-76d19f382d19","Conditions":[{"NextAction":"f5477ba9-6fc6-4860-80ec-423819ff945a","Condition":{"Operator":"Equals","Operands":["CHAT"]}},{"NextAction":"b88c1097-6aca-4776-b599-bfb1cd2a6c70","Condition":{"Operator":"Equals","Operands":["VOICE"]}}],"Errors":[{"NextAction":"5599f63e-c2fd-4a0f-af5e-76d19f382d19","ErrorType":"NoMatchingCondition"}]}},{"Parameters":{"Text":"Thanks for contacting ABC car rental. How can I help you today?","LexV2Bot":{"AliasArn":'
                  - '"'
                  - !GetAtt CarRentalBotGetCallerInfoAlias.Arn
                  - '"'
                  - '}},"Identifier":"f5477ba9-6fc6-4860-80ec-423819ff945a","Type":"ConnectParticipantWithLexBot","Transitions":{"NextAction":"5b278d14-5ad1-4174-82a2-3e5b366b765b","Conditions":[{"NextAction":"14c15a7f-8bc9-442a-8d99-a17ace8c772c","Condition":{"Operator":"Equals","Operands":["GetCallerInfo"]}}],"Errors":[{"NextAction":"5b278d14-5ad1-4174-82a2-3e5b366b765b","ErrorType":"NoMatchingCondition"},{"NextAction":"5b278d14-5ad1-4174-82a2-3e5b366b765b","ErrorType":"NoMatchingError"}]}},{"Parameters":{},"Identifier":"c2243812-5b37-4f59-9110-35646528f416","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"ComparisonValue":"$.Lex.SentimentResponse.Label"},"Identifier":"5857584b-86f9-4d0f-8f94-b438cef5d615","Type":"Compare","Transitions":{"NextAction":"c2243812-5b37-4f59-9110-35646528f416","Conditions":[{"NextAction":"Check negative sentiment score","Condition":{"Operator":"Equals","Operands":["NEGATIVE"]}},{"NextAction":"64c76090-2e61-4b72-a276-44dd76238ddf","Condition":{"Operator":"Equals","Operands":["NEUTRAL"]}},{"NextAction":"ee11fa94-39c0-4527-a86d-4e607d427058","Condition":{"Operator":"Equals","Operands":["POSITIVE"]}}],"Errors":[{"NextAction":"c2243812-5b37-4f59-9110-35646528f416","ErrorType":"NoMatchingCondition"}]}},{"Parameters":{"Attributes":{"FirstName":"$.Lex.Slots.FirstName ","LastName":"$.Lex.Slots.LastName ", "MemberID":"$.Lex.Slots.MemberID "},"TargetContact":"Current"},"Identifier":"14c15a7f-8bc9-442a-8d99-a17ace8c772c","Type":"UpdateContactAttributes","Transitions":{"NextAction":"3f298372-232b-44f7-85a1-e87e318cf411","Errors":[{"NextAction":"5b278d14-5ad1-4174-82a2-3e5b366b765b","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Thanks for the details $.Attributes.FirstName $.Attributes.LastName. How can we help you?","LexV2Bot":{"AliasArn":'
                  - '"'
                  - !GetAtt CarRentalBotGetCallerIntentAlias.Arn
                  - '"'
                  - '}},"Identifier":"3f298372-232b-44f7-85a1-e87e318cf411","Type":"ConnectParticipantWithLexBot","Transitions":{"NextAction":"c2243812-5b37-4f59-9110-35646528f416","Conditions":[{"NextAction":"5857584b-86f9-4d0f-8f94-b438cef5d615","Condition":{"Operator":"Equals","Operands":["AskForSupport"]}}],"Errors":[{"NextAction":"c2243812-5b37-4f59-9110-35646528f416","ErrorType":"NoMatchingCondition"},{"NextAction":"c2243812-5b37-4f59-9110-35646528f416","ErrorType":"NoMatchingError"}]}}]}'
      Tags:
        - Key: instance
          Value: Connect instance  
  #2. Car rental wisperflow flow for an Amazon Connect instance
  CarRentalWhisperFlow:
    Type: 'AWS::Connect::ContactFlow'
    Properties:
      Name: 'CarRentalWhisperFlow'
      Description: 'Car Rental whisper flow'
      InstanceArn: 
        Ref: ConnectInstanceArn
      Type: 'AGENT_WHISPER'
      Content: '{"Version":"2019-10-30","StartAction":"222caecc-c107-4553-87fc-85a74c34bb06","Metadata":{"entryPointPosition":{"x":74.4,"y":20},"ActionMetadata":{"95dc2179-0f18-4646-8e15-15377c9cbb29":{"position":{"x":492,"y":138.4}},"222caecc-c107-4553-87fc-85a74c34bb06":{"position":{"x":230.4,"y":96},"parameters":{"Text":{"useDynamic":true}},"useDynamic":true}},"Annotations":[],"name":"CarRentalAgentWhisper","description":"Whisper played to the agent when connect for an outbound call on the cusomer issuie","type":"agentWhisper","status":"published","hash":{}},"Actions":[{"Parameters":{},"Identifier":"95dc2179-0f18-4646-8e15-15377c9cbb29","Type":"EndFlowExecution","Transitions":{}},{"Parameters":{"Text":"$.Attributes.AgentWhisper"},"Identifier":"222caecc-c107-4553-87fc-85a74c34bb06","Type":"MessageParticipant","Transitions":{"NextAction":"95dc2179-0f18-4646-8e15-15377c9cbb29"}}]}'
      Tags:
        - Key: instance
          Value: Connect instance  
  # 3. IAM Role used by the Lex service to make runtime calls
  BotRuntimeBlogRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lexv2.amazonaws.com
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: LexRuntimeRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 
                 - "polly:SynthesizeSpeech"
                 - "comprehend:DetectSentiment"
                Resource: "*"       
  # 4. IAM Role used by the Amazon Q to use s3 content as input                
  AmazonQBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3bucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:GetObject'
              - 's3:GetBucketLocation'
              - 's3:ListBucket'
            Effect: Allow
            Resource: 
              - !Join
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref S3bucket
                  - /*              
              - !Join
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref S3bucket                  
            Principal: 
              Service: 'app-integrations.amazonaws.com'
  # 5. IAM Role used by lambda to get the basic queue
  GetInstanceDataResourceRole:
   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: ConnectPolicy
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Effect: Allow
              Action:
                  - "connect:ListQueues"
              Resource: "*"     

  # 6. IAM Role used by lambda to create knowledge base.
  ConnectWisdomIntegrationRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    UpdateReplacePolicy: Retain
    Properties:
      RoleName: !Sub
        - 'ConnectWisdomIntegrationRole-${UUID}'
        - UUID: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      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: Connect_Wisdom_Integration_Policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              # IAM Policy to allow Lambda to update Service Linked Roles
              - Effect: Allow
                Action:
                  - iam:PutRolePolicy
                  - iam:DeleteRolePolicy
                  - iam:AttachRolePolicy
                Resource:
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:role/aws-service-role/connect.amazonaws.com/*'
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:role/aws-service-role/wisdom.amazonaws.com/*'
              - Effect: Allow
                Action:
                  - connect:DescribeInstance
                  - connect:CreateIntegrationAssociation
                  - connect:ListIntegrationAssociations
                  - connect:DeleteIntegrationAssociation
                  - connect:ListTagsForResource
                  - connect:TagResource
                  - connect:UntagResource
                Resource:
                  - !Sub '${ConnectInstanceArn}'
                  - !Sub '${ConnectInstanceArn}/integration-association/*'
              - Effect: Allow
                Action:
                  - wisdom:ListAssistantAssociations
                  - wisdom:GetAssistant
                  - wisdom:GetKnowledgeBase
                  - wisdom:CreateAssistantAssociation
                  - wisdom:DeleteAssistant
                  - wisdom:DeleteKnowledgeBase
                  - wisdom:DeleteAssistantAssociation
                Resource:
                  - !Sub 'arn:aws:wisdom:${AWS::Region}:${AWS::AccountId}:assistant/*'
                  - !Sub 'arn:aws:wisdom:${AWS::Region}:${AWS::AccountId}:knowledge-base/*'
                  - !Sub 'arn:aws:wisdom:${AWS::Region}:${AWS::AccountId}:association/*/*' 
              - Effect: Allow
                Action:
                  - ds:DescribeDirectories # Required for connect:DescribeInstance, connect:ListIntegrationAssociations
                  - wisdom:ListAssistants
                  - wisdom:ListKnowledgeBases
                  - wisdom:CreateAssistant
                  - wisdom:CreateKnowledgeBase
                  - wisdom:ListTagsForResource
                  - wisdom:TagResource
                  - wisdom:UntagResource
                Resource:
                  - "*" # required for these APIs, as they have to take effect on all resources.   

  # 7. Inline bot definition which depends on the IAM role
  # The bot definition consists of combining all the child resources into one CFN resource.
  # This includes Locales, Intents, Slots, and SlotTypes.
  CarRentalBotGetCallerInfo:
    DependsOn: BotRuntimeBlogRole
    Type: AWS::Lex::Bot
    Properties:
      Name: "CarRentalBotGetCaller"
      RoleArn: !GetAtt BotRuntimeBlogRole.Arn
      DataPrivacy:
        ChildDirected: false
      IdleSessionTTLInSeconds: 300
      Description: "Rental Car bot"
      # Provide a setting that allows you to either auto build the locales provided.
      # Locale builds are also kicked off if you attempt to create a bot version 
      # that depends on an unbuilt locale.
      AutoBuildBotLocales: false
      BotLocales:
        - LocaleId: "en_US"
          Description: "Rental Car bot Locale"
          NluConfidenceThreshold: 0.40
          VoiceSettings:
            VoiceId: "Ivy"
          Intents:
            - Name: "GetCallerInfo"
              Description: "Get caller information."
              SampleUtterances:
                - Utterance: "I need to talk to an agent"
                - Utterance: "I want to speak to an agent"
                - Utterance: "I want some help"
              SlotPriorities:
                - Priority: 1
                  SlotName: FirstName
                - Priority: 2
                  SlotName: LastName 
                - Priority: 3
                  SlotName: MemberID
              Slots:
                - Name: "FirstName"
                  Description: "FirstName"
                  SlotTypeName: "AMAZON.FirstName"
                  ValueElicitationSetting:
                    SlotConstraint: "Required"
                    PromptSpecification:
                      MessageGroupsList:
                        - Message:
                            PlainTextMessage:
                              Value: "Can I have your first name?"
                      MaxRetries: 3
                      AllowInterrupt: false
                - Name: "LastName"
                  Description: "LastName"
                  SlotTypeName: "AMAZON.LastName"
                  ValueElicitationSetting:
                    SlotConstraint: "Required"
                    PromptSpecification:
                      MessageGroupsList:
                        - Message:
                            PlainTextMessage:
                              Value: "Can I have your last name?"
                      MaxRetries: 3
                      AllowInterrupt: false
                - Name: "MemberID"
                  Description: "MemberID"
                  SlotTypeName: "AMAZON.Number"
                  ValueElicitationSetting:
                    SlotConstraint: "Required"
                    PromptSpecification:
                      MessageGroupsList:
                        - Message:
                            PlainTextMessage:
                              Value: "Can you also provide me with your membership ID?"
                      MaxRetries: 3
                      AllowInterrupt: false
            - Name: "FallbackIntent"
              Description: "Default intent when no other intent matches"
              ParentIntentSignature: "AMAZON.FallbackIntent"
  # 8. Inline bot definition which depends on the IAM role
  # The bot definition consists of combining all the child resources into one CFN resource.
  # This includes Locales, Intents, Slots, and SlotTypes.
  CarRentalBotGetCallerIntent:
    DependsOn: BotRuntimeBlogRole
    Type: AWS::Lex::Bot
    Properties:
      Name: "CarRentalGetCallerIntent"
      RoleArn: !GetAtt BotRuntimeBlogRole.Arn
      DataPrivacy:
        ChildDirected: false
      IdleSessionTTLInSeconds: 300
      Description: "Rental Car bot intent"
      # Provide a setting that allows you to either auto build the locales provided.
      # Locale builds are also kicked off if you attempt to create a bot version 
      # that depends on an unbuilt locale.
      AutoBuildBotLocales: false
      BotLocales:
        - LocaleId: "en_US"
          Description: "Rental Car bot Locale"
          NluConfidenceThreshold: 0.40
          VoiceSettings:
            VoiceId: "Ivy"
          Intents:
            - Name: "AskForSupport"
              Description: "Get caller Intent."
              SampleUtterances:
                - Utterance: "I want to file a complaint"
                - Utterance: "I was overcharged"
                - Utterance: "I did not get the car I wanted"
                - Utterance: "My vacation was ruined"
                - Utterance: "The car had mechanical issues"
                - Utterance: "I encountered poor customer service at the rental counter"
                - Utterance: "I was overcharged for my last car rental"
                - Utterance: "I was overcharged charged for gas even though I returned the car with a full tank"
                - Utterance: "My chosen car was not available"
                - Utterance: "I was overcharged for the trip"
                - Utterance: "Long wait times at rental location"
            - Name: "FallbackIntent"
              Description: "Default intent when no other intent matches"
              ParentIntentSignature: "AMAZON.FallbackIntent"              
  # 9. Define a bot version which depends on the DRAFT version of the Lex Bot
  CarRentalBotGetCallerInfoVersion:
    DependsOn: CarRentalBotGetCallerInfo
    Type: AWS::Lex::BotVersion
    Properties:
      BotId: !Ref CarRentalBotGetCallerInfo
      BotVersionLocaleSpecification:
        - LocaleId: en_US
          BotVersionLocaleDetails:
            SourceBotVersion: DRAFT
      Description: BookTrip Version 
  # 10. We define the alias by providing the bot version created by the AWS::Lex::BotVersion resource above
  CarRentalBotGetCallerInfoAlias:
    DependsOn: CarRentalBotGetCallerInfoVersion
    Type: AWS::Lex::BotAlias
    Properties:
      BotAliasLocaleSettings:        
        - BotAliasLocaleSetting: 
            Enabled: true
          LocaleId: en_US         
      BotId: !Ref CarRentalBotGetCallerInfo
      BotAliasName: "CarRentalBotGetCallerInfoVersionAlias"
      BotVersion: !GetAtt CarRentalBotGetCallerInfoVersion.BotVersion
      SentimentAnalysisSettings:
        DetectSentiment: true
  # 11. Define a bot version which depends on the DRAFT version of the Lex Bot
  CarRentalBotGetCallerIntentVersion:
    DependsOn: CarRentalBotGetCallerIntent
    Type: AWS::Lex::BotVersion
    Properties:
      BotId: !Ref CarRentalBotGetCallerIntent
      BotVersionLocaleSpecification:
        - LocaleId: en_US
          BotVersionLocaleDetails:
            SourceBotVersion: DRAFT
      Description: BookTrip Version 
  # 12. We define the alias by providing the bot version created by the AWS::Lex::BotVersion resource above
  CarRentalBotGetCallerIntentAlias:
    DependsOn: CarRentalBotGetCallerIntentVersion
    Type: AWS::Lex::BotAlias
    Properties:
      BotAliasLocaleSettings:        
        - BotAliasLocaleSetting: 
            Enabled: true
          LocaleId: en_US         
      BotId: !Ref CarRentalBotGetCallerIntent
      BotAliasName: "CarRentalBotGetCallerIntentVersionAlias"
      BotVersion: !GetAtt CarRentalBotGetCallerIntentVersion.BotVersion
      SentimentAnalysisSettings:
        DetectSentiment: true      
  # 13. CarRentalBotGetCallerInfo Lex Bot integration for an Amazon Connect instance
  CallerInfoIntegrationAssociation:
    Type: AWS::Connect::IntegrationAssociation
    DependsOn: CarRentalBotGetCallerInfoAlias
    Properties:
      InstanceId: 
        Ref: ConnectInstanceArn
      IntegrationType: LEX_BOT
      IntegrationArn: !GetAtt CarRentalBotGetCallerInfoAlias.Arn
  # 14. CarRentalBotGetCallerIntent Lex Bot integration for an Amazon Connect instance
  CallerIntentIntegrationAssociation:
    DependsOn: CarRentalBotGetCallerIntentAlias
    Type: AWS::Connect::IntegrationAssociation
    Properties:
      InstanceId: 
        Ref: ConnectInstanceArn
      IntegrationType: LEX_BOT
      IntegrationArn: !GetAtt CarRentalBotGetCallerIntentAlias.Arn
  # 15. CarRentalBotGetCallerIntent Lex Bot integration for an Amazon Connect instance
  InstanceDataCustomResource:
    Type: Custom::InstanceDataCustomResource
    Properties:
      ServiceToken: !GetAtt ConnectInstanceDataLambda.Arn
      InstanceArn: !Ref ConnectInstanceArn
  # 16. CarRentalBotGetCallerIntent Lex Bot integration for an Amazon Connect instance          
  ConnectInstanceDataLambda:            
    Type: AWS::Lambda::Function 
    Properties:      
      Description: Get Basic Queue from Connect instance
      Handler: index.lambda_handler
      FunctionName: ConnectInstanceDataLambda
      Role: !GetAtt GetInstanceDataResourceRole.Arn
      Runtime: python3.12      
      Code: 
        ZipFile: |
          import boto3
          import cfnresponse
          connect = boto3.client('connect')
          def lambda_handler(event, context):
            try:
              if (event['RequestType']=='Delete'):
                cfnresponse.send(event, context, cfnresponse.SUCCESS)
              else:
                print(event)
                responseData = {}
                connect_arn = event['ResourceProperties']['InstanceArn']
                QueueSummary = connect.list_queues(InstanceId=connect_arn,QueueTypes=['STANDARD',],MaxResults=100)
                for queue in QueueSummary['QueueSummaryList']:
                  if queue['Name'] == 'BasicQueue':
                    responseData['Queue'] = queue['Arn']
                cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
            except Exception as error:
              print(error)
              cfnresponse.send(event, context, cfnresponse.FAILED)                          
  # 17. Generate KMS key for knowledge base integration
  KMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: 'Symmetric encryption KMS key for Cloudformation'
      EnableKeyRotation: true
      KeyPolicy: 
        Version: 2012-10-17
        Id: default-key
        Statement:
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Join 
                   - ''
                   - - 'arn:aws:iam::'
                     - !Ref 'AWS::AccountId'
                     - ':root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow administration of the key
            Effect: Allow
            Principal:
              AWS: 
                - !Join 
                  - ''
                  - - 'arn:aws:iam::'
                    - !Ref 'AWS::AccountId'
                    - ':role/admin'
                - !Join 
                  - ''
                  - - 'arn:aws:iam::'
                    - !Ref 'AWS::AccountId'
                    - ':role/AccessAnalyzerTrustedService'                    
            Resource: '*'
          - Sid: Allow use of the key
            Effect: Allow
            Principal:
              AWS: 
                - !Join 
                  - ''
                  - - 'arn:aws:iam::'
                    - !Ref 'AWS::AccountId'
                    - ':role/admin'
                - !Join 
                  - ''
                  - - 'arn:aws:iam::'
                    - !Ref 'AWS::AccountId'
                    - ':role/AccessAnalyzerTrustedService'                    
            Action: 
              - 'kms:Create*'
              - 'kms:Describe*'
              - 'kms:Enable*'
              - 'kms:List*'
              - 'kms:Put*'
              - 'kms:Update*'
              - 'kms:Revoke*'
              - 'kms:Disable*'
              - 'kms:Get*'
              - 'kms:Delete*'
              - 'kms:TagResource'
              - 'kms:UntagResource'
              - 'kms:ScheduleKeyDeletion'
              - 'kms:CancelKeyDeletion'
            Resource: '*'
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              Service: connect.amazonaws.com
            Action: 
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey*'
              - 'kms:DescribeKey'
            Resource: '*'
  # 18. Create wisdom Assistant to pass to lambda function
  WisdomAssistant: #passed to the lambda
    Type: AWS::Wisdom::Assistant
    Properties:
      Description: 'Wisdom Assistant'
      Name: 'WisdomAssistant'
      Type: 'AGENT'

  # 19. Create S3 bucket to setup knowledge base. 
  # It is used as parameter to knowledgebase AppIntegrationsDataIntegration
  S3bucket: 
    Type: AWS::S3::Bucket    

  # 20. Create App integration.
  # It is used s3 bucket a parameter
  AppIntegrationsDataIntegration: # parameter to knowledgebase
    Type: AWS::AppIntegrations::DataIntegration
    Properties:
      Description: 'App Integrations Data Integration'
      KmsKey: !GetAtt KMSKey.Arn
      Name: 'KnowledgeBase'
      SourceURI: !Join 
                  - ''
                  - - 's3://'
                    - !Ref S3bucket

  # 21. Create external knowledgebase.
  KnowledgeBase: #passed to the lambda
    Type: AWS::Wisdom::KnowledgeBase
    Properties:
      Description: 'Knowledge Base'
      KnowledgeBaseType: 'EXTERNAL'
      Name: 'KnowledgeBase'
      SourceConfiguration: 
        AppIntegrations: 
          AppIntegrationArn: !GetAtt AppIntegrationsDataIntegration.DataIntegrationArn
  # 22. Create Assistant Association 
  AssistantAssociation: 
    Type: AWS::Wisdom::AssistantAssociation
    Properties:
      AssistantId: !GetAtt WisdomAssistant.AssistantId
      Association: 
        KnowledgeBaseId: !GetAtt KnowledgeBase.KnowledgeBaseId
      AssociationType: 'KNOWLEDGE_BASE'

  # 23. Create custom resource to create multiple integration
  InvokeConnectWisdomIntegration:
    Type: "Custom::InvokeConnectWisdomIntegration"
    Properties:
      ServiceToken: !GetAtt ConnectWisdomIntegration.Arn
      INSTANCE_ARN: !Ref ConnectInstanceArn
      WISDOM_ASSISTANT_ARN: !GetAtt WisdomAssistant.AssistantArn
      WISDOM_KNOWLEDGE_BASE_ARN: !GetAtt KnowledgeBase.KnowledgeBaseArn

  # 24. Lambda function create Connect Integration Association with Wisdom Resources 
  ConnectWisdomIntegration:
    Type: 'AWS::Lambda::Function'
    DeletionPolicy: Delete
    UpdateReplacePolicy: Retain
    Properties:
      Description: 'ConnectWisdomIntegration: Creates Connect Integration Association with Wisdom Resources'
      FunctionName: !Sub
        - 'ConnectWisdomIntegration-${UUID}'
        - UUID: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      Handler: index.lambda_handler # use index.lambda_handler for inline functions
      Runtime: python3.12
      RuntimeManagementConfig:
        UpdateRuntimeOn: "Auto"
      Role: !GetAtt ConnectWisdomIntegrationRole.Arn
      MemorySize: 256
      Timeout: 60
      Environment:
        Variables:
          STACK_UUID: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      Code:
        ZipFile: |
          import os
          import json
          import urllib3
          http = urllib3.PoolManager()

          # AWS SDK Imports
          import boto3
          from botocore.exceptions import ClientError                  

          CONNECT_CLIENT = boto3.client('connect')
          WISDOM_CLIENT = boto3.client('wisdom')

          # STACK_UUID: Substring of CloudFormation StackID. Used to identify and tag resources
          STACK_UUID = os.environ["STACK_UUID"] 

          def lambda_handler(event, context):
              print("Event Recieved: ", json.dumps(event))
              print("Request Type:", event['RequestType'])
              print("Resource Properties: ", json.dumps(event["ResourceProperties"]))

              # Define variables from Custom Resource Event Data
              PHYSICAL_RESOURCE_ID = event["ResourceProperties"]["ServiceToken"]
              INSTANCE_ARN = event["ResourceProperties"]["INSTANCE_ARN"] # To get InstanceId: INSTANCE_ARN.split("/")[1]
              WISDOM_ASSISTANT_ARN = event["ResourceProperties"]["WISDOM_ASSISTANT_ARN"] 
              WISDOM_KNOWLEDGE_BASE_ARN = event["ResourceProperties"]["WISDOM_KNOWLEDGE_BASE_ARN"]

              # Check for existing Integrations: List Integration Association - Wisdom Assistant / Knowledge Base
              # Full Assistant/KnowledgeBase Integration Association Object: [{'IntegrationAssociationId': 'string', 'IntegrationAssociationArn': 'string', 'InstanceId': 'string', 'IntegrationType': 'WISDOM_ASSISTANT', 'IntegrationArn': 'string'}]
              connectWisdomAssistantIntegration = listIntegrationAssociations(instanceId=INSTANCE_ARN, integrationType="WISDOM_ASSISTANT")
              connectWisdomKnowledgeBaseIntegration = listIntegrationAssociations(instanceId=INSTANCE_ARN, integrationType="WISDOM_KNOWLEDGE_BASE")
              print("Connect Integration - Wisdom Assistant ", json.dumps(connectWisdomAssistantIntegration))    
              print("Connect Integration - Wisdom Knowledgebase ", json.dumps(connectWisdomKnowledgeBaseIntegration))

              # Define shared ResponseData Expected by CloudFormation Response:
              responseData = {
                  "Connect_WisdomAssistant_IntegrationAssociationARN": "", # Connect Integration Association - Wisdom Assistant
                  "Connect_WisdomKnowledgeBase_IntegrationAssociationARN": "", # Connect Integration Association - Wisdom KnowledgeBase
                  "Wisdom_Assistant_ARN": "", # Amazon Connect Integration Association - Wisdom Assistant
                  "Wisdom_KnowledgeBase_ARN": "", # Amazon Connect Integration Association - Wisdom KnowledgeBase
              }

              # Case 1: CloudFormation Stack sends Create or Update Event.
              if event["RequestType"] == "Create" or event["RequestType"] == "Update":
                  # Case 1: If the Connect Instance has existing Wisdom Integrations (both Assistant and KnowledgeBase) return those values.
                  if len(connectWisdomAssistantIntegration) and len(connectWisdomKnowledgeBaseIntegration):
                      # Roadmap: Delete Existing Integration Associations, create new Integration Associations with resources.
                      print("Connect Instance: ", INSTANCE_ARN, " has existing Wisdom integrations.")

                      # Return previous Integration Association Data
                      responseData["Previous_Connect_WisdomAssistant_IntegrationAssociation"] = str(connectWisdomAssistantIntegration[0])
                      responseData["Previous_Wisdom_Assistant_ARN"] = str(connectWisdomAssistantIntegration[0]['IntegrationArn'])
                      responseData["Previous_Connect_WisdomKnowledgeBase_IntegrationAssociation"] = str(connectWisdomKnowledgeBaseIntegration[0])
                      responseData["Previous_Wisdom_KnowledgeBase_ARN"] = str(connectWisdomKnowledgeBaseIntegration[0]['IntegrationArn'])
                  
                      deleteConnectWisdomAssistantIntegration = deleteIntegrationAssociation(instanceId=INSTANCE_ARN, integrationAssociationId=connectWisdomAssistantIntegration[0]['IntegrationAssociationId'])
                      print("Delete Complete: Amazon Connect Integration Association - Wisdom Assistant: ", connectWisdomAssistantIntegration[0]['IntegrationAssociationId'])

                      deleteConnectWisdomKnowledgeBaseIntegration = deleteIntegrationAssociation(instanceId=INSTANCE_ARN, integrationAssociationId=connectWisdomKnowledgeBaseIntegration[0]['IntegrationAssociationId'])
                      print("Delete Complete: Amazon Connect Integration Association - Wisdom KnowledgeBase: ", connectWisdomKnowledgeBaseIntegration[0]['IntegrationAssociationId'])
                  else:
                      print("Connect Instance: ", INSTANCE_ARN, " has no existing Wisdom integrations.")
                  
                  # Step 2: Create new Integration Associations with provided Wisdom Assistant and Knowledge Base
                  # At this point, Connect Instance has no existing Wisdom Integrations.
                  if len(WISDOM_ASSISTANT_ARN) > 0:
                      connectWisdomAssistantIntegration = createIntegrationAssociation(INSTANCE_ARN, WISDOM_ASSISTANT_ARN, 'WISDOM_ASSISTANT')
                      print("Connect Integration - Wisdom Assistant. Create Integration Association Response: ", connectWisdomAssistantIntegration)
                      if connectWisdomAssistantIntegration['status'] == "SUCCESS":
                          responseData["Connect_WisdomAssistant_IntegrationAssociationARN"] = connectWisdomAssistantIntegration['IntegrationAssociationArn']
                          responseData["Wisdom_Assistant_ARN"] = WISDOM_ASSISTANT_ARN
                  else:
                      print("Wisdom Assistant ARN not provided. Skipping Integration Association Creation.")

                  if len(WISDOM_KNOWLEDGE_BASE_ARN) > 0:
                      connectWisdomKnowledgeBaseIntegration = createIntegrationAssociation(INSTANCE_ARN, WISDOM_KNOWLEDGE_BASE_ARN, 'WISDOM_KNOWLEDGE_BASE')
                      print("Connect Integration - Wisdom KnowledgeBase. Create Integration Association Response: ", connectWisdomKnowledgeBaseIntegration)
                      if connectWisdomKnowledgeBaseIntegration['status'] == "SUCCESS":
                          responseData["Connect_WisdomKnowledgeBase_IntegrationAssociationARN"] = connectWisdomKnowledgeBaseIntegration['IntegrationAssociationArn']
                          responseData["Wisdom_KnowledgeBase_ARN"] = WISDOM_KNOWLEDGE_BASE_ARN
                  else:
                      print("Wisdom KnowledgeBase ARN not provided. Skipping Integration Association Creation.")

                  # Send CFN Response
                  print("Create/Update - Wisdom Integration Handler Response: ", json.dumps(responseData))
                  send(event, context, "SUCCESS", responseData, PHYSICAL_RESOURCE_ID)
                  return responseData
              
              # CloudFormation Stack sends DELETE signal - Delete Connect Integration Associations.
              # DELETE - ORDER OF OPERATIONS: 1) Connect-Assistant Integration, 2) Connect-Knowledgebase Integration, 3) Wisdom Assistant-Knowledgebase Association, 4) Wisdom Assistant, 5) Wisdom Knowledgebase
              if event["RequestType"] == "Delete":
                  # Case 1: If the Connect Instance has Wisdom Integration Associations (WISDOM_ASSISTANT, WISDOM_KNOWLEDGE_BASE), delete resources.
                  if len(connectWisdomAssistantIntegration):
                      response = deleteIntegrationAssociation(INSTANCE_ARN, connectWisdomAssistantIntegration[0]["IntegrationAssociationId"])
                      print("Connect Integration - Wisdom Assistant. Delete Integration Association Response: ", str(response))

                  if len(connectWisdomKnowledgeBaseIntegration):
                      response = deleteIntegrationAssociation(INSTANCE_ARN, connectWisdomKnowledgeBaseIntegration[0]["IntegrationAssociationId"])
                      print("Connect Integration - Wisdom KnowledgeBase. Delete Integration Association Response: ", str(response))
                  
                  # Send CFN Response
                  print("Delete - Wisdom Integration Handler Response: ", json.dumps(responseData))
                  send(event, context, "SUCCESS", responseData, PHYSICAL_RESOURCE_ID)
                  return responseData

          # List Amazon Connect Instance Integration Associations (Accepts either Instance ID or ARN)
          # https://docs.aws.amazon.com/connect/latest/APIReference/API_ListIntegrationAssociations.html
          def listIntegrationAssociations(instanceId, integrationType):
              CONNECT_CLIENT = boto3.client('connect')
              try:
                  response = CONNECT_CLIENT.list_integration_associations(
                      InstanceId=instanceId,
                      IntegrationType=integrationType
                  )
                  return response["IntegrationAssociationSummaryList"]
              except ClientError as e:
                  print(e)
                  return {'status': "CLIENT_ERROR", 'Message': str(e)}
              except Exception as ex:
                  print(ex)
                  return {'status': "EXCEPTION", 'Message': str(ex)}

          # Integrates a WISDOM_KNOWLEDGE_BASE or WISDOM_ASSISTANT with an Amazon Connect Instance
          # https://docs.aws.amazon.com/connect/latest/APIReference/API_CreateIntegrationAssociation.html
          def createIntegrationAssociation(instanceId, integrationArn, integrationType):
              print("Creating Integration Association between Connect Instance: ", instanceId, " and the Wisdom Resource: ", integrationArn, " with Integration Type: ", integrationType, " and UUID Tag: ", STACK_UUID)
              try:
                  response = CONNECT_CLIENT.create_integration_association(
                      InstanceId = instanceId,
                      IntegrationArn = integrationArn,
                      IntegrationType = integrationType,
                      Tags={ "UUID": STACK_UUID }
                  )
                  response["status"] = "SUCCESS"
                  return response
              except ClientError as e:
                  return {'status': "CLIENT_ERROR", 'Message': str(e)}
              except Exception as ex:
                  return {'status': "EXCEPTION", 'Message': str(ex)}

          # Delete a Connect - Wisdom Integration Association (Assistant or KnowledgeBase)
          # https://docs.aws.amazon.com/connect/latest/APIReference/API_DeleteIntegrationAssociation.html
          def deleteIntegrationAssociation(instanceId, integrationAssociationId):
              print("Deleting Integration Association between Connect Instance: ", instanceId, " and the Wisdom Resource: ", integrationAssociationId)
              try:
                  CONNECT_CLIENT.delete_integration_association(InstanceId=instanceId, IntegrationAssociationId=integrationAssociationId)
                  return {'status': "SUCCESS", 'Message': str("Integration Association, " + integrationAssociationId + ", deleted successfully")}
              except ClientError as e:
                  return {'status': "CLIENT_ERROR", 'Message': str(e)}
              except Exception as ex:
                  return {'status': "EXCEPTION", 'Message': str(ex)}

          # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
          # SPDX-License-Identifier: MIT-0
          # CloudFormation Response Helper Function: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html
          def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False, reason=None):
              responseUrl = event['ResponseURL']
              print(responseUrl)
              
              responseBody = {
                  'Status' : responseStatus,
                  'Reason' : reason or "See the details in CloudWatch Log Stream: {}".format(context.log_stream_name),
                  'PhysicalResourceId' : physicalResourceId or context.log_stream_name,
                  'StackId' : event['StackId'],
                  'RequestId' : event['RequestId'],
                  'LogicalResourceId' : event['LogicalResourceId'],
                  'NoEcho' : noEcho,
                  'Data' : responseData
              }
              json_responseBody = json.dumps(responseBody)
              print("Response body: ", json_responseBody)

              headers = {
                  'content-type' : '',
                  'content-length' : str(len(json_responseBody))
              }

              try:
                  response = http.request('PUT', responseUrl, headers=headers, body=json_responseBody)
                  print("Status code:", response.status)
              except Exception as e:
                  print("send(..) failed executing http.request(..):", e)              
Outputs:
  S3bucketURL:
    Description: S3 Bucket URL
    Value: !GetAtt S3bucket.Arn
