AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Argentic — private photo backup in YOUR AWS account. One stack = everything
  the iOS app needs: a dedicated photos bucket, semantic-search vector index,
  Sign in with Apple identity pool with per-user isolation, and an AI cost
  alarm. Deploy, then copy the ArgenticSetupCode output into the app.
  Storage cost: roughly $0.01/GB/month less than photo subscriptions —
  your photos, your cloud.

Parameters:
  AlarmEmail:
    Type: String
    Default: ''
    Description: >
      (Optional) Email for the AI cost alarm (fires if the in-app assistant
      exceeds 1M input tokens in an hour). Leave empty to skip.
  AppBundleId:
    Type: String
    Default: com.aaagius.argentic
    Description: >
      iOS bundle ID allowed to sign in (leave the default unless you build
      the app yourself from source).

Conditions:
  HasAlarmEmail: !Not [!Equals [!Ref AlarmEmail, '']]

Mappings:
  # Bedrock cross-region inference profile geo prefix per deployment region,
  # for Claude Haiku 4.5 (verified 2026-10-06, list-inference-profiles):
  # there is no apac. Haiku profile; Tokyo has jp., Sydney au., Singapore
  # only global. (worldwide routing). Same table as
  # PhotoAgent.inferenceGeography in the app.
  RegionGeo:
    us-east-1: { Geo: us }
    us-east-2: { Geo: us }
    us-west-2: { Geo: us }
    eu-west-1: { Geo: eu }
    eu-west-3: { Geo: eu }
    eu-central-1: { Geo: eu }
    eu-north-1: { Geo: eu }
    ap-northeast-1: { Geo: jp }
    ap-southeast-1: { Geo: global }
    ap-southeast-2: { Geo: au }

Resources:
  # --- Photos bucket (dedicated, retained on stack deletion: user data) ---
  PhotosBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub argentic-photos-${AWS::AccountId}
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      # Safety net: the bucket is the ONLY copy of deleted photos. Versioning
      # keeps an overwritten/deleted object for 30 days so an accidental
      # in-app delete (or a bad cleanup) is recoverable, then it's purged.
      # Photos are written once (unique keys) so this adds ~zero storage in
      # normal use — cost is proportional to what you actually delete.
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: abort-incomplete-multipart
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
          - Id: expire-deleted-objects-30d
            Status: Enabled
            NoncurrentVersionExpiration:
              NoncurrentDays: 30
          # Once the old version has expired, its delete marker restores
          # nothing: remove it so the Trash listing stays short.
          - Id: remove-expired-delete-markers
            Status: Enabled
            ExpiredObjectDeleteMarker: true

  # --- Semantic search: S3 Vectors (512-dim/cosine, MobileCLIP-S0) ---
  VectorBucket:
    Type: AWS::S3Vectors::VectorBucket
    Properties:
      VectorBucketName: !Sub argentic-vectors-${AWS::AccountId}

  VectorIndex:
    Type: AWS::S3Vectors::Index
    DependsOn: VectorBucket
    Properties:
      VectorBucketName: !Sub argentic-vectors-${AWS::AccountId}
      IndexName: photos-mobileclip-s0
      DataType: float32
      Dimension: 512
      DistanceMetric: cosine
      MetadataConfiguration:
        NonFilterableMetadataKeys: [caption, ocr]

  # --- Sign in with Apple → scoped, per-user-isolated credentials ---
  IdentityPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      IdentityPoolName: ArgenticIdentityPool
      AllowUnauthenticatedIdentities: false
      SupportedLoginProviders:
        appleid.apple.com: !Ref AppBundleId

  AuthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      Description: Argentic per-tenant scoped S3/Bedrock access for signed-in users.
      MaxSessionDuration: 3600
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: { Federated: cognito-identity.amazonaws.com }
            Action: sts:AssumeRoleWithWebIdentity
            Condition:
              StringEquals:
                cognito-identity.amazonaws.com:aud: !Ref IdentityPool
              ForAnyValue:StringLike:
                cognito-identity.amazonaws.com:amr: authenticated
      Policies:
        - PolicyName: argentic-tenant-scoped
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              # Every signed-in user is confined to their own prefix — family
              # members share the stack without seeing each other's photos.
              - Sid: TenantObjects
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:AbortMultipartUpload
                  - s3:ListMultipartUploadParts
                  - s3:DeleteObject
                Resource:
                  - !Sub ${PhotosBucket.Arn}/photos/${!cognito-identity.amazonaws.com:sub}/*
                  - !Sub ${PhotosBucket.Arn}/thumbs/${!cognito-identity.amazonaws.com:sub}/*
              - Sid: TenantEntitlementsRead
                Effect: Allow
                Action: s3:GetObject
                Resource: !Sub ${PhotosBucket.Arn}/config/${!cognito-identity.amazonaws.com:sub}/*
              # Trash (app 1.6): read the old versions the bucket keeps 30
              # days after a delete, to copy them back. Read only, on the
              # member's own photos/ and thumbs/: no role can delete an old
              # version, so nobody can empty the safety net.
              - Sid: TenantObjectVersionsRead
                Effect: Allow
                Action: s3:GetObjectVersion
                Resource:
                  - !Sub ${PhotosBucket.Arn}/photos/${!cognito-identity.amazonaws.com:sub}/*
                  - !Sub ${PhotosBucket.Arn}/thumbs/${!cognito-identity.amazonaws.com:sub}/*
              - Sid: TenantList
                Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:ListBucketVersions
                Resource: !GetAtt PhotosBucket.Arn
                Condition:
                  StringLike:
                    s3:prefix:
                      - photos/${cognito-identity.amazonaws.com:sub}/*
                      - thumbs/${cognito-identity.amazonaws.com:sub}/*
              - Sid: VectorIndexAccess
                Effect: Allow
                Action:
                  - s3vectors:PutVectors
                  - s3vectors:QueryVectors
                  - s3vectors:GetVectors
                  - s3vectors:ListVectors
                  - s3vectors:DeleteVectors
                Resource: !GetAtt VectorIndex.IndexArn
              # AI assistant + monthly memories: ONE model, region-partition
              # inference profile.
              - Sid: AgentModelInvoke
                Effect: Allow
                Action:
                  # Converse + ConverseStream (assistant streaming), same
                  # single-model resource scope.
                  - bedrock:InvokeModel
                  - bedrock:InvokeModelWithResponseStream
                Resource:
                  - !Sub
                    - arn:aws:bedrock:${AWS::Region}:${AWS::AccountId}:inference-profile/${Geo}.anthropic.claude-haiku-4-5-20251001-v1:0
                    - Geo: !FindInMap [RegionGeo, !Ref 'AWS::Region', Geo]
                  - arn:aws:bedrock:*::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0

  IdentityPoolRoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId: !Ref IdentityPool
      Roles:
        authenticated: !GetAtt AuthenticatedRole.Arn

  # --- AI cost guardrail (optional email) ---
  AlarmTopic:
    Type: AWS::SNS::Topic

  AlarmSubscription:
    Type: AWS::SNS::Subscription
    Condition: HasAlarmEmail
    Properties:
      TopicArn: !Ref AlarmTopic
      Protocol: email
      Endpoint: !Ref AlarmEmail

  BedrockUsageAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: >
        Argentic assistant exceeded 1M Bedrock input tokens in one hour —
        normal conversations use a few thousand.
      Namespace: AWS/Bedrock
      MetricName: InputTokenCount
      Statistic: Sum
      Period: 3600
      EvaluationPeriods: 1
      Threshold: 1000000
      ComparisonOperator: GreaterThanThreshold
      TreatMissingData: notBreaching
      AlarmActions:
        - !Ref AlarmTopic

Outputs:
  ArgenticSetupCode:
    Description: >
      COPY THIS into the Argentic app (Settings > Connect your AWS).
    Value: !Sub argentic1|${AWS::Region}|${IdentityPool}|${PhotosBucket}|argentic-vectors-${AWS::AccountId}|photos-mobileclip-s0
  PhotosBucketName:
    Value: !Ref PhotosBucket
  IdentityPoolId:
    Value: !Ref IdentityPool
