AWSTemplateFormatVersion: '2010-09-09'
Description: 'KANU Platform - Complete Deployment with Web App and AI Generation Infrastructure'

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VPC ID where the application will be deployed

  PublicSubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Description: Public subnet IDs for the ALB and ECS tasks (must be in at least 2 AZs)

  EnvironmentName:
    Type: String
    Default: production
    AllowedValues:
      - development
      - staging
      - production
    Description: Environment name

  DesiredTaskCount:
    Type: Number
    Default: 1
    MinValue: 1
    MaxValue: 10
    Description: Number of ECS tasks to run

  ContainerImage:
    Type: String
    Default: 709825985650.dkr.ecr.us-east-1.amazonaws.com/kanu-ai/kanu-ai-cloud-engineer:webapp-v1.17.0
    Description: Docker image URI for webapp from Marketplace ECR

  PythonOrchestratorImage:
    Type: String
    Default: 709825985650.dkr.ecr.us-east-1.amazonaws.com/kanu-ai/kanu-ai-cloud-engineer:orchestrator-v169
    Description: Docker image URI for Python orchestrator

Resources:
  # ==============================================
  # DATA LAYER - DynamoDB Tables
  # ==============================================

  AppsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-apps-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
        - AttributeName: user_id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      GlobalSecondaryIndexes:
        - IndexName: user-id-index
          KeySchema:
            - AttributeName: user_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-apps-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  TasksTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-tasks-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: task_id
          AttributeType: S
        - AttributeName: user_id
          AttributeType: S
        - AttributeName: company_id
          AttributeType: S
      KeySchema:
        - AttributeName: task_id
          KeyType: HASH
      GlobalSecondaryIndexes:
        - IndexName: user-index
          KeySchema:
            - AttributeName: user_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: company-index
          KeySchema:
            - AttributeName: company_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-tasks-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  TaskDecisionsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-task-decisions-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: decision_id
          AttributeType: S
        - AttributeName: task_id
          AttributeType: S
      KeySchema:
        - AttributeName: decision_id
          KeyType: HASH
      GlobalSecondaryIndexes:
        - IndexName: task-index
          KeySchema:
            - AttributeName: task_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-task-decisions-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  TaskActivityLogsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-task-activity-logs-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: task_id
          AttributeType: S
        - AttributeName: timestamp
          AttributeType: S
      KeySchema:
        - AttributeName: task_id
          KeyType: HASH
        - AttributeName: timestamp
          KeyType: RANGE
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-task-activity-logs-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  ChatMessagesTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-chat-messages-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: pk
          AttributeType: S
        - AttributeName: sent_at
          AttributeType: S
      KeySchema:
        - AttributeName: pk
          KeyType: HASH
        - AttributeName: sent_at
          KeyType: RANGE
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-chat-messages-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  DeploymentsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-deployments-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: deployment_id
          AttributeType: S
        - AttributeName: site_id
          AttributeType: S
        - AttributeName: subdomain
          AttributeType: S
        - AttributeName: custom_domain
          AttributeType: S
        - AttributeName: user_id
          AttributeType: S
      KeySchema:
        - AttributeName: deployment_id
          KeyType: HASH
      GlobalSecondaryIndexes:
        - IndexName: SiteIndex
          KeySchema:
            - AttributeName: site_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: SubdomainIndex
          KeySchema:
            - AttributeName: subdomain
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: CustomDomainIndex
          KeySchema:
            - AttributeName: custom_domain
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: UserIndex
          KeySchema:
            - AttributeName: user_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-deployments-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  AppSubscriptionsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-app-subscriptions-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: app_id
          AttributeType: S
        - AttributeName: user_id
          AttributeType: S
        - AttributeName: stripe_customer_id
          AttributeType: S
        - AttributeName: stripe_subscription_id
          AttributeType: S
        - AttributeName: stripe_checkout_session_id
          AttributeType: S
        - AttributeName: status
          AttributeType: S
        - AttributeName: cancelled_at
          AttributeType: S
      KeySchema:
        - AttributeName: app_id
          KeyType: HASH
      GlobalSecondaryIndexes:
        - IndexName: user-id-index
          KeySchema:
            - AttributeName: user_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: stripe-customer-id-index
          KeySchema:
            - AttributeName: stripe_customer_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: stripe-subscription-id-index
          KeySchema:
            - AttributeName: stripe_subscription_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: stripe-checkout-session-id-index
          KeySchema:
            - AttributeName: stripe_checkout_session_id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: status-cancelled_at-index
          KeySchema:
            - AttributeName: status
              KeyType: HASH
            - AttributeName: cancelled_at
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-app-subscriptions-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  EmailSignupsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-email-signups-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: email
          AttributeType: S
      KeySchema:
        - AttributeName: email
          KeyType: HASH
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-email-signups-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  GitLabTokensTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'kanu-gitlab-tokens-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: user_id
          AttributeType: S
      KeySchema:
        - AttributeName: user_id
          KeyType: HASH
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'kanu-gitlab-tokens-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  UserCreditsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub 'user-credits-${AWS::StackName}'
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: email
          AttributeType: S
      KeySchema:
        - AttributeName: email
          KeyType: HASH
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true
      Tags:
        - Key: Name
          Value: !Sub 'user-credits-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  # ==============================================
  # STORAGE - S3 Buckets
  # ==============================================

  CodeStorageBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'kanu-code-storage-${AWS::AccountId}-${AWS::StackName}'
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: DeleteOldVersions
            Status: Enabled
            NoncurrentVersionExpirationInDays: 30
      Tags:
        - Key: Name
          Value: !Sub 'kanu-code-storage-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  FrontendAssetsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'kanu-frontends-${AWS::AccountId}-${AWS::StackName}'
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      Tags:
        - Key: Name
          Value: !Sub 'kanu-frontends-${EnvironmentName}'
        - Key: Environment
          Value: !Ref EnvironmentName

  # ==============================================
  # AUTHENTICATION - Cognito User Pool
  # ==============================================

  # Cognito User Pool
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: !Sub '${AWS::StackName}-users'
      AutoVerifiedAttributes:
        - email
      UsernameAttributes:
        - email
      UsernameConfiguration:
        CaseSensitive: false
      Policies:
        PasswordPolicy:
          MinimumLength: 8
          RequireLowercase: true
          RequireNumbers: true
          RequireSymbols: true
          RequireUppercase: true
      Schema:
        - Name: email
          AttributeDataType: String
          Required: true
          Mutable: false
      UserAttributeUpdateSettings:
        AttributesRequireVerificationBeforeUpdate:
          - email
      AccountRecoverySetting:
        RecoveryMechanisms:
          - Name: verified_email
            Priority: 1

  # Cognito User Pool Client
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      ClientName: !Sub '${AWS::StackName}-client'
      UserPoolId: !Ref UserPool
      GenerateSecret: true
      ExplicitAuthFlows:
        - ALLOW_USER_PASSWORD_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH
        - ALLOW_USER_SRP_AUTH
      PreventUserExistenceErrors: ENABLED
      RefreshTokenValidity: 30
      AccessTokenValidity: 60
      IdTokenValidity: 60
      TokenValidityUnits:
        RefreshToken: days
        AccessToken: minutes
        IdToken: minutes
      ReadAttributes:
        - email
        - email_verified
        - name
      WriteAttributes:
        - email
        - name

  # ==============================================
  # MESSAGING - SNS Topics and SQS Queues
  # ==============================================

  GeneratorStatusTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub '${AWS::StackName}-generator-status'
      DisplayName: 'KANU Generator Status Updates'
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Component
          Value: 'messaging'

  DecisionResponseTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub '${AWS::StackName}-decision-response'
      DisplayName: 'KANU Decision Responses'
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Component
          Value: 'messaging'

  GeneratorStatusQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub '${AWS::StackName}-generator-status'
      VisibilityTimeout: 30
      MessageRetentionPeriod: 1209600  # 14 days
      ReceiveMessageWaitTimeSeconds: 20  # Long polling
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Component
          Value: 'messaging'

  GeneratorStatusQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref GeneratorStatusQueue
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: 'sqs:SendMessage'
            Resource: !GetAtt GeneratorStatusQueue.Arn
            Condition:
              ArnEquals:
                'aws:SourceArn': !Ref GeneratorStatusTopic

  GeneratorStatusSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn: !Ref GeneratorStatusTopic
      Endpoint: !GetAtt GeneratorStatusQueue.Arn
      RawMessageDelivery: true

  DecisionResponseQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub '${AWS::StackName}-decision-response'
      VisibilityTimeout: 30
      MessageRetentionPeriod: 1209600  # 14 days
      ReceiveMessageWaitTimeSeconds: 20  # Long polling
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Component
          Value: 'messaging'

  DecisionResponseQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - !Ref DecisionResponseQueue
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Action: 'sqs:SendMessage'
            Resource: !GetAtt DecisionResponseQueue.Arn
            Condition:
              ArnEquals:
                'aws:SourceArn': !Ref DecisionResponseTopic

  DecisionResponseSubscription:
    Type: AWS::SNS::Subscription
    Properties:
      Protocol: sqs
      TopicArn: !Ref DecisionResponseTopic
      Endpoint: !GetAtt DecisionResponseQueue.Arn
      RawMessageDelivery: true

  # ==============================================
  # COMPUTE - ECS Clusters
  # ==============================================

  # Web Application Cluster
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub '${AWS::StackName}-cluster'
      CapacityProviders:
        - FARGATE
        - FARGATE_SPOT
      DefaultCapacityProviderStrategy:
        - CapacityProvider: FARGATE
          Weight: 1

  # Generation/Orchestration Cluster
  GenerationCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub '${AWS::StackName}-generation'
      ClusterSettings:
        - Name: containerInsights
          Value: enabled
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-generation'
        - Key: Environment
          Value: !Ref EnvironmentName

  # ==============================================
  # NETWORKING - Security Groups
  # ==============================================

  # Security Group for ALB
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP traffic to ALB
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: Allow HTTP from internet
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
          Description: Allow all outbound
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-alb-sg'

  # Security Group for ECS Tasks
  ECSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow traffic from ALB to ECS tasks
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3000
          ToPort: 3000
          SourceSecurityGroupId: !Ref ALBSecurityGroup
          Description: Allow traffic from ALB
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
          Description: Allow all outbound
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-ecs-sg'

  # Security Group for Generation Tasks
  GenerationSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for AI generation tasks
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
          Description: Allow all outbound for Bedrock, ECR, S3, DynamoDB
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-generation-sg'
        - Key: Environment
          Value: !Ref EnvironmentName

  # ==============================================
  # LOAD BALANCING - ALB
  # ==============================================

  # Application Load Balancer
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub '${AWS::StackName}-alb'
      Type: application
      Scheme: internet-facing
      IpAddressType: ipv4
      Subnets: !Ref PublicSubnetIds
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-alb'

  # Target Group
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub '${AWS::StackName}-tg'
      Port: 3000
      Protocol: HTTP
      TargetType: ip
      VpcId: !Ref VpcId
      HealthCheckEnabled: true
      HealthCheckProtocol: HTTP
      HealthCheckPath: /enterprise
      HealthCheckIntervalSeconds: 30
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
      Matcher:
        HttpCode: 200-399
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-tg'

  # ALB Listener
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup

  # Lambda Execution Role for Cognito Secret Retrieval
  CognitoSecretLambdaRole:
    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: CognitoAndSecretsAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cognito-idp:DescribeUserPoolClient
                  - cognito-idp:AdminCreateUser
                  - cognito-idp:AdminSetUserPassword
                Resource: !GetAtt UserPool.Arn
              - Effect: Allow
                Action:
                  - secretsmanager:CreateSecret
                  - secretsmanager:PutSecretValue
                  - secretsmanager:UpdateSecret
                Resource: !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${AWS::StackName}-cognito-secret*'

  # Lambda Function to Store Cognito Client Secret
  CognitoSecretFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub '${AWS::StackName}-cognito-secret'
      Runtime: python3.12
      Handler: index.handler
      Role: !GetAtt CognitoSecretLambdaRole.Arn
      Timeout: 60
      Code:
        ZipFile: |
          import json
          import boto3
          import cfnresponse

          def handler(event, context):
              try:
                  if event['RequestType'] == 'Delete':
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                      return

                  user_pool_id = event['ResourceProperties']['UserPoolId']
                  client_id = event['ResourceProperties']['ClientId']
                  secret_name = event['ResourceProperties']['SecretName']

                  cognito = boto3.client('cognito-idp')
                  secrets = boto3.client('secretsmanager')

                  # Get client secret from Cognito
                  response = cognito.describe_user_pool_client(
                      UserPoolId=user_pool_id,
                      ClientId=client_id
                  )
                  client_secret = response['UserPoolClient']['ClientSecret']

                  # Store in Secrets Manager
                  try:
                      create_response = secrets.create_secret(
                          Name=secret_name,
                          SecretString=client_secret
                      )
                      secret_arn = create_response['ARN']
                  except secrets.exceptions.ResourceExistsException:
                      secrets.put_secret_value(
                          SecretId=secret_name,
                          SecretString=client_secret
                      )
                      # Get the full ARN after update
                      desc_response = secrets.describe_secret(SecretId=secret_name)
                      secret_arn = desc_response['ARN']

                  # Create default admin user
                  admin_email = 'admin@getkanu.com'
                  admin_password = 'Test1234!'

                  try:
                      cognito.admin_create_user(
                          UserPoolId=user_pool_id,
                          Username=admin_email,
                          TemporaryPassword=admin_password,
                          MessageAction='SUPPRESS',
                          UserAttributes=[
                              {'Name': 'email', 'Value': admin_email},
                              {'Name': 'email_verified', 'Value': 'true'}
                          ]
                      )
                      # Set permanent password
                      cognito.admin_set_user_password(
                          UserPoolId=user_pool_id,
                          Username=admin_email,
                          Password=admin_password,
                          Permanent=True
                      )
                      print(f'Created admin user: {admin_email}')
                  except Exception as user_error:
                      print(f'Admin user creation error (may already exist): {str(user_error)}')

                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {
                      'SecretArn': secret_arn
                  })
              except Exception as e:
                  print(f'Error: {str(e)}')
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)})

  # Custom Resource to Invoke Lambda
  CognitoSecretStorage:
    Type: Custom::CognitoSecret
    Properties:
      ServiceToken: !GetAtt CognitoSecretFunction.Arn
      UserPoolId: !Ref UserPool
      ClientId: !Ref UserPoolClient
      SecretName: !Sub '${AWS::StackName}-cognito-secret'

  # IAM Role for ECS Task (Application Permissions)
  ECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${AWS::StackName}-task-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess
      Policies:
        - PolicyName: LaunchGenerationTasks
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ecs:RunTask
                  - ecs:DescribeTasks
                  - ecs:ListTasks
                Resource: '*'
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource:
                  - !GetAtt GenerationTaskRole.Arn
                  - !GetAtt ECSTaskExecutionRole.Arn
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-task-role'

  # IAM Role for ECS Task Execution
  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    DependsOn: CognitoSecretStorage
    Properties:
      RoleName: !Sub '${AWS::StackName}-execution-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
        - arn:aws:iam::aws:policy/SecretsManagerReadWrite
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-execution-role'

  # IAM Role for Generation Tasks
  GenerationTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${AWS::StackName}-generation-task'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: GenerationDynamoDBAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:GetItem
                  - dynamodb:PutItem
                  - dynamodb:UpdateItem
                  - dynamodb:Query
                Resource:
                  - !GetAtt AppsTable.Arn
                  - !GetAtt TasksTable.Arn
                  - !GetAtt TaskDecisionsTable.Arn
                  - !GetAtt TaskActivityLogsTable.Arn
                  - !GetAtt ChatMessagesTable.Arn
                  - !GetAtt DeploymentsTable.Arn
                  - !GetAtt AppSubscriptionsTable.Arn
                  - !GetAtt EmailSignupsTable.Arn
                  - !GetAtt GitLabTokensTable.Arn
                  - !GetAtt UserCreditsTable.Arn
                  - !Sub '${AppsTable.Arn}/index/*'
                  - !Sub '${TasksTable.Arn}/index/*'
                  - !Sub '${TaskDecisionsTable.Arn}/index/*'
                  - !Sub '${ChatMessagesTable.Arn}/index/*'
                  - !Sub '${DeploymentsTable.Arn}/index/*'
                  - !Sub '${AppSubscriptionsTable.Arn}/index/*'
        - PolicyName: GenerationS3Access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:ListBucket
                Resource:
                  - !GetAtt CodeStorageBucket.Arn
                  - !Sub '${CodeStorageBucket.Arn}/*'
                  - !GetAtt FrontendAssetsBucket.Arn
                  - !Sub '${FrontendAssetsBucket.Arn}/*'
        - PolicyName: GenerationBedrockAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - bedrock:InvokeModel
                  - bedrock:InvokeModelWithResponseStream
                Resource: '*'
        - PolicyName: GenerationCloudFormationAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:*
                Resource: !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/*'
        - PolicyName: GenerationCDKAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:*
                  - ssm:GetParameter
                  - ssm:PutParameter
                Resource:
                  - 'arn:aws:s3:::cdk-*'
                  - 'arn:aws:s3:::cdk-*/*'
                  - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/cdk-bootstrap/*'
              - Effect: Allow
                Action:
                  - ecr:*
                Resource: '*'
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                  - iam:CreateRole
                  - iam:PutRolePolicy
                  - iam:AttachRolePolicy
                  - iam:GetRole
                  - iam:PassRole
                  - iam:TagRole
                Resource:
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:role/cdk-*'
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:role/app-*'
        - PolicyName: GenerationLambdaAPIGatewayAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - lambda:*
                  - apigateway:*
                  - dynamodb:CreateTable
                  - dynamodb:DeleteTable
                  - dynamodb:DescribeTable
                  - dynamodb:UpdateTable
                Resource: '*'
        - PolicyName: GenerationMessagingAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - sns:Publish
                Resource:
                  - !Ref GeneratorStatusTopic
              - Effect: Allow
                Action:
                  - sqs:ReceiveMessage
                  - sqs:DeleteMessage
                  - sqs:GetQueueAttributes
                Resource:
                  - !GetAtt DecisionResponseQueue.Arn
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-generation-task'
        - Key: Environment
          Value: !Ref EnvironmentName

  # ==============================================
  # LOGGING - CloudWatch Log Groups
  # ==============================================

  # CloudWatch Log Group for Web App
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/ecs/${AWS::StackName}'
      RetentionInDays: 7

  # CloudWatch Log Group for Generation Tasks
  GenerationLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/ecs/${AWS::StackName}-generation'
      RetentionInDays: 7

  # ==============================================
  # TASK DEFINITIONS
  # ==============================================

  # ECS Task Definition
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    DependsOn: CognitoSecretStorage
    Properties:
      Family: !Sub '${AWS::StackName}-webapp'
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: '4096'
      Memory: '8192'
      TaskRoleArn: !GetAtt ECSTaskRole.Arn
      ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
      ContainerDefinitions:
        - Name: kanu-webapp
          Image: !Ref ContainerImage
          Essential: true
          PortMappings:
            - ContainerPort: 3000
              Protocol: tcp
          Environment:
            - Name: NODE_ENV
              Value: production
            - Name: PORT
              Value: '3000'
            - Name: FORCE_INSECURE_COOKIES
              Value: 'true'
            - Name: APP_URL
              Value: !Sub 'http://${ApplicationLoadBalancer.DNSName}'
            - Name: NEXT_PUBLIC_APP_URL
              Value: !Sub 'http://${ApplicationLoadBalancer.DNSName}'
            - Name: NEXT_PUBLIC_ROOT_DOMAIN
              Value: !GetAtt ApplicationLoadBalancer.DNSName
            - Name: COGNITO_USER_POOL_ID
              Value: !Ref UserPool
            - Name: COGNITO_USER_POOL_CLIENT_ID
              Value: !Ref UserPoolClient
            - Name: NEXT_PUBLIC_COGNITO_USER_POOL_ID
              Value: !Ref UserPool
            - Name: NEXT_PUBLIC_COGNITO_USER_POOL_CLIENT_ID
              Value: !Ref UserPoolClient

            # Generation Infrastructure
            - Name: USE_ENTERPRISE_DEPLOYMENT
              Value: 'true'
            - Name: ECS_CLUSTER_NAME
              Value: !Ref GenerationCluster
            - Name: ECS_TASK_DEFINITION
              Value: !Ref GenerationTaskDefinition
            - Name: ECS_PYTHON_TASK_DEFINITION
              Value: !Ref GenerationTaskDefinition
            - Name: ECS_SUBNET_IDS
              Value: !Join [',', !Ref PublicSubnetIds]
            - Name: ECS_SECURITY_GROUP_IDS
              Value: !Ref GenerationSecurityGroup

            # AWS Configuration
            - Name: AWS_REGION
              Value: !Ref AWS::Region
            - Name: AWS_ACCOUNT_ID
              Value: !Ref AWS::AccountId
            - Name: AWS_BEDROCK_REGION
              Value: !Ref AWS::Region
            - Name: USE_BEDROCK
              Value: 'true'

            # Storage
            - Name: KANU_CODE_BUCKET
              Value: !Ref CodeStorageBucket
            - Name: KANU_FRONTEND_BUCKET
              Value: !Ref FrontendAssetsBucket

            # DynamoDB Tables
            - Name: DYNAMODB_APPS_TABLE_NAME
              Value: !Ref AppsTable
            - Name: TASKS_TABLE_NAME
              Value: !Ref TasksTable
            - Name: TASK_DECISIONS_TABLE_NAME
              Value: !Ref TaskDecisionsTable
            - Name: TASK_ACTIVITY_LOGS_TABLE_NAME
              Value: !Ref TaskActivityLogsTable
            - Name: CHAT_MESSAGES_TABLE_NAME
              Value: !Ref ChatMessagesTable
            - Name: DEPLOYMENTS_TABLE_NAME
              Value: !Ref DeploymentsTable
            - Name: APP_SUBSCRIPTIONS_TABLE_NAME
              Value: !Ref AppSubscriptionsTable
            - Name: EMAIL_SIGNUPS_TABLE_NAME
              Value: !Ref EmailSignupsTable
            - Name: DYNAMODB_GITLAB_TOKENS_TABLE
              Value: !Ref GitLabTokensTable
            - Name: USER_CREDITS_TABLE_NAME
              Value: !Ref UserCreditsTable

            # Messaging
            - Name: GENERATOR_STATUS_TOPIC_ARN
              Value: !Ref GeneratorStatusTopic
            - Name: GENERATOR_STATUS_QUEUE_URL
              Value: !Ref GeneratorStatusQueue
            - Name: DECISION_RESPONSE_TOPIC_ARN
              Value: !Ref DecisionResponseTopic
            - Name: DECISION_RESPONSE_QUEUE_URL
              Value: !Ref DecisionResponseQueue

            # HITL Configuration
            - Name: MAX_CONCURRENT_TASKS_PER_USER
              Value: '100'
            - Name: ENABLE_HUMAN_IN_THE_LOOP
              Value: 'true'
          Secrets:
            - Name: COGNITO_USER_POOL_CLIENT_SECRET
              ValueFrom: !GetAtt CognitoSecretStorage.SecretArn
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: webapp
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-webapp'

  # Generation Task Definition
  GenerationTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Sub '${AWS::StackName}-generation'
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: '8192'
      Memory: '16384'
      ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
      TaskRoleArn: !GetAtt GenerationTaskRole.Arn
      ContainerDefinitions:
        - Name: orchestrator
          Image: !Ref PythonOrchestratorImage
          Essential: true
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref GenerationLogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: generation
          Environment:
            # Core AWS Configuration
            - Name: AWS_REGION
              Value: !Ref AWS::Region
            - Name: AWS_ACCOUNT_ID
              Value: !Ref AWS::AccountId

            # DynamoDB Tables
            - Name: DYNAMODB_APPS_TABLE_NAME
              Value: !Ref AppsTable
            - Name: DYNAMODB_TABLE
              Value: !Ref AppsTable

            # Task System Tables
            - Name: TASKS_TABLE_NAME
              Value: !Ref TasksTable
            - Name: TASK_DECISIONS_TABLE_NAME
              Value: !Ref TaskDecisionsTable
            - Name: TASK_ACTIVITY_LOGS_TABLE_NAME
              Value: !Ref TaskActivityLogsTable
            - Name: CHAT_MESSAGES_TABLE_NAME
              Value: !Ref ChatMessagesTable

            # SNS/SQS for Real-time Communication
            - Name: GENERATOR_STATUS_TOPIC_ARN
              Value: !Ref GeneratorStatusTopic
            - Name: DECISION_RESPONSE_QUEUE_URL
              Value: !Ref DecisionResponseQueue

            # S3 Buckets
            - Name: KANU_CODE_BUCKET
              Value: !Ref CodeStorageBucket
            - Name: KANU_FRONTEND_BUCKET
              Value: !Ref FrontendAssetsBucket

            # Generation Configuration
            - Name: USE_BEDROCK
              Value: 'true'
            - Name: CLAUDE_CODE_USE_BEDROCK
              Value: '1'
            - Name: ENABLE_HUMAN_IN_LOOP
              Value: 'true'
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-generation'
        - Key: Environment
          Value: !Ref EnvironmentName

  # ==============================================
  # SERVICES
  # ==============================================

  # ECS Service
  ECSService:
    Type: AWS::ECS::Service
    DependsOn:
      - ALBListener
      - CognitoSecretStorage
    Properties:
      ServiceName: !Sub '${AWS::StackName}-webapp-service'
      Cluster: !Ref ECSCluster
      TaskDefinition: !Ref TaskDefinition
      DesiredCount: !Ref DesiredTaskCount
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          Subnets: !Ref PublicSubnetIds
          SecurityGroups:
            - !Ref ECSSecurityGroup
      LoadBalancers:
        - ContainerName: kanu-webapp
          ContainerPort: 3000
          TargetGroupArn: !Ref TargetGroup
      HealthCheckGracePeriodSeconds: 60
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-webapp-service'

Outputs:
  ApplicationURL:
    Description: Application Load Balancer URL
    Value: !Sub 'http://${ApplicationLoadBalancer.DNSName}'
    Export:
      Name: !Sub '${AWS::StackName}-AppURL'

  LoadBalancerDNS:
    Description: Load Balancer DNS Name
    Value: !GetAtt ApplicationLoadBalancer.DNSName
    Export:
      Name: !Sub '${AWS::StackName}-ALBDNS'

  ECSClusterName:
    Description: ECS Cluster Name
    Value: !Ref ECSCluster
    Export:
      Name: !Sub '${AWS::StackName}-ClusterName'

  ECSServiceName:
    Description: ECS Service Name
    Value: !GetAtt ECSService.Name
    Export:
      Name: !Sub '${AWS::StackName}-ServiceName'

  CognitoUserPoolId:
    Description: Cognito User Pool ID
    Value: !Ref UserPool
    Export:
      Name: !Sub '${AWS::StackName}-UserPoolId'

  CognitoUserPoolClientId:
    Description: Cognito User Pool Client ID
    Value: !Ref UserPoolClient
    Export:
      Name: !Sub '${AWS::StackName}-UserPoolClientId'

  # Generation Infrastructure Outputs
  GenerationClusterName:
    Description: ECS Cluster for AI generation tasks
    Value: !Ref GenerationCluster
    Export:
      Name: !Sub '${AWS::StackName}-GenerationCluster'

  GenerationTaskDefArn:
    Description: Task definition ARN for Python orchestrator
    Value: !Ref GenerationTaskDefinition
    Export:
      Name: !Sub '${AWS::StackName}-GenerationTaskDef'

  CodeBucketName:
    Description: S3 bucket for backend code storage
    Value: !Ref CodeStorageBucket
    Export:
      Name: !Sub '${AWS::StackName}-CodeBucket'

  FrontendBucketName:
    Description: S3 bucket for frontend assets
    Value: !Ref FrontendAssetsBucket
    Export:
      Name: !Sub '${AWS::StackName}-FrontendBucket'

  AppsTableName:
    Description: DynamoDB table for app metadata
    Value: !Ref AppsTable
    Export:
      Name: !Sub '${AWS::StackName}-AppsTable'

  TasksTableName:
    Description: DynamoDB table for task-based execution
    Value: !Ref TasksTable
    Export:
      Name: !Sub '${AWS::StackName}-TasksTable'

  GeneratorStatusTopicArn:
    Description: SNS topic for generation status updates
    Value: !Ref GeneratorStatusTopic
    Export:
      Name: !Sub '${AWS::StackName}-StatusTopic'
