AWSTemplateFormatVersion: '2010-09-09'
Description: Deploy GenericSuite application domain for the EC2 instances (SSL Certificate and Route53 domain)

Parameters:
  DomainName:
    Description: Domain name for the ALB
    Type: String
  AppName:
    Description: application name
    Type: String
  AppStage:
    Description: application stage (qa, prod, staging, demo, dev)
    Type: String
  HostedZoneId:
    Description: The ID of the hosted zone where the domain is managed
    Type: AWS::Route53::HostedZone::Id

Resources:
  Route53RecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref HostedZoneId
      Name: !Ref DomainName
      Type: A
      # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-route53.html
      TTL: '60'
      ResourceRecords:
      - 192.168.0.1
      - 192.168.0.2

  AppSSLCertificate:
    Type: AWS::CertificateManager::Certificate
    Properties: 
      DomainName: !Ref DomainName
      ValidationMethod: DNS
      Tags:
        - Key: App
          Value: !Ref AppName
        - Key: Stage
          Value: !Ref AppStage
        - Key: Name
          Value: !Sub "${AppName}-${AppStage}-ssl-certificate"

  GetCertificateValidationResource:
    Type: Custom::GetCertificateValidation
    Properties:
      ServiceToken: !GetAtt GetCertificateValidationFunction.Arn
      CertificateArn: !Ref AppSSLCertificate

  GetCertificateValidationFunction:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.11
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          
          def handler(event, context):
              if event['RequestType'] == 'Delete':
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                  return
              
              certificate_arn = event['ResourceProperties']['CertificateArn']
              acm_client = boto3.client('acm')
              
              try:
                  response = acm_client.describe_certificate(CertificateArn=certificate_arn)
                  validation_options = response['Certificate']['DomainValidationOptions']
                  
                  for option in validation_options:
                      if 'ResourceRecord' in option:
                          record = option['ResourceRecord']
                          cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                              'Name': record['Name'],
                              'Type': record['Type'],
                              'Value': record['Value']
                          })
                          return
                  
                  cfnresponse.send(event, context, cfnresponse.FAILED, {
                      'Reason': 'No validation records found'
                  })
              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {
                      'Reason': str(e)
                  })

  CreateCNAMERecordResource:
    Type: Custom::CreateCNAMERecord
    Properties:
      ServiceToken: !GetAtt CreateCNAMERecordFunction.Arn
      HostedZoneId: !Ref HostedZoneId
      RecordName: !GetAtt GetCertificateValidationResource.Name
      RecordType: !GetAtt GetCertificateValidationResource.Type
      RecordValue: !GetAtt GetCertificateValidationResource.Value

  CreateCNAMERecordFunction:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.11
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          
          def handler(event, context):
              if event['RequestType'] == 'Delete':
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                  return
              
              hosted_zone_id = event['ResourceProperties']['HostedZoneId']
              record_name = event['ResourceProperties']['RecordName']
              record_type = event['ResourceProperties']['RecordType']
              record_value = event['ResourceProperties']['RecordValue']
              
              route53_client = boto3.client('route53')
              
              try:
                  response = route53_client.change_resource_record_sets(
                      HostedZoneId=hosted_zone_id,
                      ChangeBatch={
                          'Changes': [
                              {
                                  'Action': 'UPSERT',
                                  'ResourceRecordSet': {
                                      'Name': record_name,
                                      'Type': record_type,
                                      'TTL': 300,
                                      'ResourceRecords': [{'Value': record_value}]
                                  }
                              }
                          ]
                      }
                  )
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                      'Message': 'CNAME record created successfully'
                  })
              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {
                      'Reason': str(e)
                  })

  LambdaExecutionRole:
    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: ACMandRoute53Access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - acm:DescribeCertificate
                  - route53:ChangeResourceRecordSets
                Resource: '*'

Outputs:
  CertificateArn:
    Description: ARN of the created SSL Certificate
    Value: !Ref AppSSLCertificate
    Export:
      Name: !Sub "${AWS::StackName}-CertificateArn"
  Route53RecordSetId:
    Description: ID of the created Route53 Record Set
    Value: !Ref Route53RecordSet
    Export:
      Name: !Sub "${AWS::StackName}-Route53RecordSetId"
  DomainName:
    Description: Domain name for the ALB
    Value: !Ref DomainName
    Export:
      Name: !Sub "${AWS::StackName}-DomainName"
