AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'Rekognition

  Sample SAM Template for Rekognition

  '
Parameters:
  StackName:
    Type: String
    Default: rekognition
    Description: Base name for the stack resources (must be lowercase)
    AllowedPattern: ^[a-z0-9-]*$
    ConstraintDescription: Stack name can only contain lowercase letters, numbers,
      and hyphens
  Environment:
    Type: String
    Default: dev
    AllowedValues:
    - dev
    - staging
    - prod
    Description: Environment name for the stack (defaults to dev)
  ProjectName:
    Type: String
    Default: datafy-rekognition
    Description: Name of the Rekognition Custom Labels project
  ModelVersion:
    Type: String
    Default: v1
    Description: Version of the Rekognition Custom Labels model
  DatastoreManifestBucket:
    Type: String
    Default: datafy-rekognition-training-data-dev
    Description: The S3 bucket containing manifest files
Globals:
  Function:
    Timeout: 900
    MemorySize: 1024
    Handler: app.lambda_handler
    Runtime: python3.10
    Architectures:
    - x86_64
    Tracing: Active
    LoggingConfig:
      LogFormat: JSON
Resources:
  SourceDataBucket:
    Type: AWS::S3::Bucket
    DependsOn: CreateManifestFunctionPermission
    Properties:
      BucketName:
        Fn::Sub: datafy-${StackName}-source-data-${Environment}
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      NotificationConfiguration:
        LambdaConfigurations:
        - Event: s3:ObjectCreated:*
          Filter:
            S3Key:
              Rules:
              - Name: suffix
                Value: .xml
          Function:
            Fn::GetAtt:
            - CreateManifestFunction
            - Arn
    Metadata:
      SamResourceId: SourceDataBucket
  TrainingDataBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName:
        Fn::Sub: datafy-${StackName}-training-data-${Environment}
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      LifecycleConfiguration:
        Rules:
        - Id: DeleteOldManifests
          Status: Enabled
          ExpirationInDays: 30
          Prefix: manifests/
    Metadata:
      SamResourceId: TrainingDataBucket
  CreateManifestFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/create_manifest/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 300
      MemorySize: 1024
      Policies:
      - AmazonS3FullAccess
      - Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - logs:CreateLogGroup
          - logs:CreateLogStream
          - logs:PutLogEvents
          Resource:
          - Fn::Sub: arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/Rekognition-CreateManifestFunction*
        - Effect: Allow
          Action:
          - ssm:GetParameter
          Resource:
          - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/datafy-rekognition-stack/*
    Metadata:
      SamResourceId: CreateManifestFunction
  CreateManifestFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName:
        Ref: CreateManifestFunction
      Principal: s3.amazonaws.com
      SourceAccount:
        Ref: AWS::AccountId
      SourceArn:
        Fn::Sub: arn:aws:s3:::datafy-${StackName}-source-data-${Environment}
    Metadata:
      SamResourceId: CreateManifestFunctionPermission
  SourceBucketParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name:
        Fn::Sub: /datafy-rekognition-stack/source-bucket
      Type: String
      Value:
        Ref: SourceDataBucket
      Description: Source data bucket name for XML and JPG files
    Metadata:
      SamResourceId: SourceBucketParameter
  TrainingBucketParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name:
        Fn::Sub: /datafy-rekognition-stack/training-bucket
      Type: String
      Value:
        Ref: TrainingDataBucket
      Description: Training data bucket name
    Metadata:
      SamResourceId: TrainingBucketParameter
  TrainingPrefixParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name:
        Fn::Sub: /datafy-rekognition-stack/training-prefix
      Type: String
      Value: manifests
      Description: Prefix for training data in the bucket
    Metadata:
      SamResourceId: TrainingPrefixParameter
  StartModelFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/start_model/
      Policies:
      - AmazonRekognitionFullAccess
      - AmazonS3FullAccess
      - Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - ssm:GetParameter
          - ssm:GetParameters
          Resource:
          - arn:aws:ssm:eu-west-1:587594388832:parameter/*
    Metadata:
      SamResourceId: StartModelFunction
  AnalyzeImageFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/analyze_image/
      Handler: app.lambda_handler
      Runtime: python3.9      
      Timeout: 180
      MemorySize: 512
      Policies:
        - AmazonRekognitionFullAccess
        - AmazonSSMReadOnlyAccess
    Metadata:
      SamResourceId: AnalyzeImageFunction
  StopModelFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/stop_model/
      Policies:
      - AmazonRekognitionFullAccess
      - AmazonS3FullAccess
      - Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - ssm:GetParameter
          - ssm:GetParameters
          Resource:
          - arn:aws:ssm:eu-west-1:587594388832:parameter/*
    Metadata:
      SamResourceId: StopModelFunction
  CheckPendingImagesFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/check_pending_images/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 60
      Policies:
        - AmazonRekognitionFullAccess
        - AmazonSSMReadOnlyAccess
        - Statement:
            - Effect: Allow
              Action:
                - s3:GetObject
              Resource: !Sub 'arn:aws:s3:::${DatastoreManifestBucket}/*'
  CheckModelAvailabilityFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/check_model_availability/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 60
      Policies:
        - AmazonRekognitionFullAccess
        - AmazonSSMReadOnlyAccess
  StartTrainingFunction:
    Type: AWS::Serverless::Function
    Properties:
      Description: Function to manually start training a Rekognition Custom Labels
        model (DO NOT trigger with S3 events)
      CodeUri: functions/start_training/
      Timeout: 900
      MemorySize: 2048
      Environment:
        Variables:
          TRAINING_BUCKET:
            Ref: TrainingDataBucket
          PROJECT_ARN:
            Fn::Sub: arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/Datafy
      Policies:
      - AmazonRekognitionFullAccess
      - AmazonS3FullAccess
      - Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - ssm:GetParameter
          - ssm:GetParameters
          Resource:
          - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*
    Metadata:
      SamResourceId: StartTrainingFunction
  CreateDatasetFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 
        Fn::Sub: '${ProjectName}-create-dataset'
      CodeUri: functions/create_dataset/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 300
      MemorySize: 1024
      Environment:
        Variables:
          PROJECT_ARN:
            Fn::Sub: arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/Datafy
          TRAINING_BUCKET:
            Ref: TrainingDataBucket
          PROJECT_NAME: 
            Ref: ProjectName
      Policies:
        - AmazonRekognitionFullAccess
        - AmazonS3FullAccess
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:GetParameter
                - ssm:GetParameters
                - lambda:InvokeFunction
              Resource:
                - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*
                - Fn::Sub: arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ProjectName}-delete-datasets

  CheckDatasetStatusFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 
        Fn::Sub: '${ProjectName}-check-dataset-status'
      CodeUri: functions/check_dataset_status/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 60
      MemorySize: 512
      Environment:
        Variables:
          PROJECT_ARN:
            Fn::Sub: arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/Datafy
          PROJECT_NAME: 
            Ref: ProjectName
      Policies:
        - AmazonRekognitionFullAccess
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:GetParameter
                - ssm:GetParameters
              Resource:
                - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*
  ProjectNameParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name:
        Fn::Sub: /datafy-rekognition-stack/project-name
      Type: String
      Value: Datafy
      Description: Rekognition Custom Labels Project Name
    Metadata:
      SamResourceId: ProjectNameParameter
  RekognitionStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Name: 
        Fn::Sub: ${StackName}-state-machine-${Environment}
      DefinitionUri: statemachine/rekognition.asl.json
      DefinitionSubstitutions:
        CheckPendingImagesFunctionArn: !GetAtt CheckPendingImagesFunction.Arn
        StartModelFunctionArn: !GetAtt StartModelFunction.Arn
        CheckModelAvailabilityFunctionArn: !GetAtt CheckModelAvailabilityFunction.Arn
        AnalyzeImageFunctionArn: !GetAtt AnalyzeImageFunction.Arn
        StopModelFunctionArn: !GetAtt StopModelFunction.Arn
      Policies:
        - LambdaInvokePolicy:
            FunctionName: !Ref CheckPendingImagesFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref StartModelFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref CheckModelAvailabilityFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref AnalyzeImageFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref StopModelFunction
    Metadata:
      SamResourceId: RekognitionStateMachine
  ApplicationResourceGroup:
    Type: AWS::ResourceGroups::Group
    Properties:
      Name:
        Fn::Sub: ApplicationInsights-SAM-${AWS::StackName}
      ResourceQuery:
        Type: CLOUDFORMATION_STACK_1_0
    Metadata:
      SamResourceId: ApplicationResourceGroup
  ApplicationInsightsMonitoring:
    Type: AWS::ApplicationInsights::Application
    Properties:
      ResourceGroupName:
        Ref: ApplicationResourceGroup
      AutoConfigurationEnabled: 'true'
    Metadata:
      SamResourceId: ApplicationInsightsMonitoring
  RekognitionServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: rekognition.amazonaws.com
          Action: sts:AssumeRole
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonRekognitionFullAccess
      Policies:
      - PolicyName: RekognitionS3Access
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - s3:GetObject
            - s3:ListBucket
            - s3:PutObject
            Resource:
            - Fn::Sub: arn:aws:s3:::${TrainingDataBucket}
            - Fn::Sub: arn:aws:s3:::${TrainingDataBucket}/*
            - Fn::Sub: arn:aws:s3:::${SourceDataBucket}
            - Fn::Sub: arn:aws:s3:::${SourceDataBucket}/*
    Metadata:
      SamResourceId: RekognitionServiceRole
  DeleteDatasetsFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 
        Fn::Sub: '${ProjectName}-delete-datasets'
      CodeUri: functions/delete_datasets/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 300
      MemorySize: 1024
      Environment:
        Variables:
          PROJECT_ARN:
            Fn::Sub: arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/Datafy
      Policies:
        - AmazonRekognitionFullAccess
        - AmazonS3FullAccess
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:GetParameter
                - ssm:GetParameters
              Resource:
                - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*

  SplitManifestFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 
        Fn::Sub: '${ProjectName}-split-manifest'
      CodeUri: functions/split_manifest/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 300
      MemorySize: 1024
      Environment:
        Variables:
          TRAINING_BUCKET:
            Ref: TrainingDataBucket
          PROJECT_NAME: 
            Ref: ProjectName
      Policies:
        - AmazonS3FullAccess
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:GetParameter
                - ssm:GetParameters
              Resource:
                - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*

  CheckTrainingStatusFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 
        Fn::Sub: '${ProjectName}-check-training-status'
      CodeUri: functions/check_training_status/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 300
      MemorySize: 1024
      Environment:
        Variables:
          PROJECT_ARN:
            Fn::Sub: arn:aws:rekognition:${AWS::Region}:${AWS::AccountId}:project/Datafy
          PROJECT_NAME: 
            Ref: ProjectName
      Policies:
        - AmazonRekognitionFullAccess
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:GetParameter
                - ssm:GetParameters
              Resource:
                - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*

  # SSM Parameters for state machines
  RekognitionStateMachineParam:
    Type: AWS::SSM::Parameter
    Properties:
      Name:
        Fn::Sub: /datafy-rekognition-stack/rekognition-state-machine-arn
      Type: String
      Value:
        Ref: RekognitionStateMachine
  
  RekognitionStateMachineRoleParam:
    Type: AWS::SSM::Parameter
    Properties:
      Name:
        Fn::Sub: /datafy-rekognition-stack/rekognition-state-machine-role-arn
      Type: String
      Value:
        Fn::GetAtt:
        - RekognitionStateMachineRole
        - Arn
        
  # Training workflow state machine
  RekognitionTrainingStateMachine:
    Type: AWS::Serverless::StateMachine
    Properties:
      Name: 
        Fn::Sub: ${StackName}-training-workflow-${Environment}
      DefinitionUri: statemachine/rekognition_training.asl.json
      DefinitionSubstitutions:
        CheckManifestFreshnessFunctionArn:
          Fn::GetAtt:
          - CheckManifestFreshnessFunction
          - Arn
        DeleteDatasetsFunctionArn:
          Fn::GetAtt:
          - DeleteDatasetsFunction
          - Arn
        SplitManifestFunctionArn:
          Fn::GetAtt:
          - SplitManifestFunction
          - Arn
        CreateDatasetFunctionArn:
          Fn::GetAtt:
          - CreateDatasetFunction
          - Arn
        StartTrainingFunctionArn:
          Fn::GetAtt:
          - StartTrainingFunction
          - Arn
        CheckTrainingStatusFunctionArn:
          Fn::GetAtt:
          - CheckTrainingStatusFunction
          - Arn
        CheckDatasetStatusFunctionArn:
          Fn::GetAtt:
          - CheckDatasetStatusFunction
          - Arn
      Policies:
        - AWSLambdaRole
        - AmazonRekognitionFullAccess
      Tracing:
        Enabled: true
      # Events:
      #   StartTrainingApi:
      #     Type: Api
      #     Properties:
      #       Path: /start-training
      #       Method: POST
    Metadata:
      SamResourceId: RekognitionTrainingStateMachine
      
  RekognitionTrainingStateMachineParam:
    Type: AWS::SSM::Parameter
    Properties:
      Name:
        Fn::Sub: /datafy-rekognition-stack/rekognition-training-state-machine-arn
      Type: String
      Value:
        Ref: RekognitionTrainingStateMachine

  # Scheduled triggers for state machines
  # RekognitionStateMachineScheduledRule:
  #   Type: AWS::Events::Rule
  #   Properties:
  #     Name:
  #       Fn::Sub: ${StackName}-daily-processing-${Environment}
  #     Description: "Trigger Rekognition state machine daily to process images"
  #     ScheduleExpression: "cron(0 2 * * ? *)" # Run daily at 2:00 AM UTC
  #     State: "ENABLED"
  #     Targets:
  #       - Id: "RekognitionStateMachineTarget"
  #         Arn: !Ref RekognitionStateMachine
  #         RoleArn: !GetAtt EventBridgeToStateMachineRole.Arn

  # RekognitionTrainingStateMachineScheduledRule:
  #   Type: AWS::Events::Rule
  #   Properties:
  #     Name:
  #       Fn::Sub: ${StackName}-weekly-training-${Environment}
  #     Description: "Trigger Rekognition training state machine weekly to update model"
  #     ScheduleExpression: "cron(0 1 ? * SUN *)" # Run weekly on Sundays at 1:00 AM UTC
  #     State: "ENABLED"
  #     Targets:
  #       - Id: "RekognitionTrainingStateMachineTarget"
  #         Arn: !Ref RekognitionTrainingStateMachine
  #         RoleArn: !GetAtt EventBridgeToStateMachineRole.Arn

  # IAM role for EventBridge to invoke step functions
  EventBridgeToStateMachineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: InvokeStepFunctions
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: states:StartExecution
                Resource: 
                  - Fn::Sub: arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${StackName}-state-machine-${Environment}
                  - Fn::Sub: arn:aws:states:${AWS::Region}:${AWS::AccountId}:stateMachine:${StackName}-training-workflow-${Environment}

  # Add the Check Manifest Freshness function
  CheckManifestFreshnessFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 
        Fn::Sub: '${ProjectName}-check-manifest-freshness'
      CodeUri: functions/check_manifest_freshness/
      Handler: app.lambda_handler
      Runtime: python3.9
      Timeout: 60
      MemorySize: 512
      Environment:
        Variables:
          TRAINING_BUCKET:
            Ref: TrainingDataBucket
          MANIFEST_KEY: manifest.jsonl
          MAX_AGE_HOURS: 168
      Policies:
        - AmazonS3ReadOnlyAccess
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:GetParameter
                - ssm:GetParameters
              Resource:
                - Fn::Sub: arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*

Outputs:
  RekognitionStateMachineArn:
    Description: Rekognition State machine ARN
    Value:
      Ref: RekognitionStateMachine
  RekognitionStateMachineRoleArn:
    Description: IAM Role created for Rekognition State machine based on the specified
      SAM Policy Templates
    Value:
      Fn::GetAtt:
      - RekognitionStateMachineRole
      - Arn
  SourceDataBucketName:
    Description: Name of the created source data bucket
    Value:
      Ref: SourceDataBucket
  SourceDataBucketArn:
    Description: ARN of the created source data bucket
    Value:
      Fn::GetAtt:
      - SourceDataBucket
      - Arn
  TrainingDataBucketName:
    Description: Name of the created training data bucket
    Value:
      Ref: TrainingDataBucket
  TrainingDataBucketArn:
    Description: ARN of the created training data bucket
    Value:
      Fn::GetAtt:
      - TrainingDataBucket
      - Arn
  RekognitionTrainingStateMachineArn:
    Description: Rekognition Training State machine ARN
    Value:
      Ref: RekognitionTrainingStateMachine
