name: Deploy Incode Labs Application

on:
  push:
    branches: [ main ]
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    name: Deploy Application
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pyyaml

    - name: Parse labspec.yaml
      id: config
      run: |
        python - <<EOF
        import yaml
        import os
        import json

        with open('labspec.yaml', 'r') as f:
            config = yaml.safe_load(f)

        # Use git commit SHA as image tag to ensure unique deployments
        import subprocess
        git_sha = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode().strip()

        # Set outputs for GitHub Actions using GITHUB_OUTPUT
        with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
            f.write(f"APP_NAME={config['app_name']}\n")
            f.write(f"IMAGE_NAME={config['image']}\n")
            f.write(f"IMAGE_TAG={git_sha}\n")
            f.write(f"CONTAINER_PORT={config['container_port']}\n")
            # Get description (optional)
            f.write(f"DESCRIPTION={config.get('description', '')}\n")
            # Check if destroy-app flag is set
            f.write(f"DESTROY_APP={str(config.get('destroy-app', False)).lower()}\n")
            # Convert secrets list to JSON string for later use
            f.write(f"SECRETS_LIST={json.dumps(config.get('secrets', []))}\n")
            # Get instance size (optional, defaults to 'small')
            f.write(f"INSTANCE_SIZE={config.get('instance_size', 'small')}\n")
            # Save the entire labspec.yaml for the apps database
            f.write(f"LABSPEC={json.dumps(config)}\n")
        EOF
      shell: bash

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::927715221157:role/github-actions-role
        aws-region: us-east-1
        mask-aws-account-id: false

    - name: Create S3 bucket if it doesn't exist
      run: |
        aws s3api head-bucket --bucket incode-labs-tfstate-34bb8h7 2>/dev/null || \
        aws s3api create-bucket \
          --bucket incode-labs-tfstate-34bb8h7 \
          --region us-east-1 \
          --create-bucket-configuration LocationConstraint=us-east-1

    - name: Configure S3 bucket for Terraform state
      run: |
        # Enable versioning
        aws s3api put-bucket-versioning \
          --bucket incode-labs-tfstate-34bb8h7 \
          --versioning-configuration Status=Enabled

        # Configure bucket policy
        aws s3api put-bucket-policy \
          --bucket incode-labs-tfstate-34bb8h7 \
          --policy '{
            "Version": "2012-10-17",
            "Statement": [
              {
                "Sid": "AllowTerraformStateAccess",
                "Effect": "Allow",
                "Principal": {
                  "AWS": "arn:aws:iam::927715221157:role/github-actions-role"
                },
                "Action": [
                  "s3:ListBucket",
                  "s3:GetObject",
                  "s3:PutObject",
                  "s3:DeleteObject"
                ],
                "Resource": [
                  "arn:aws:s3:::incode-labs-tfstate-34bb8h7",
                  "arn:aws:s3:::incode-labs-tfstate-34bb8h7/*"
                ]
              }
            ]
          }'

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2

    - name: Create ECR repository
      run: |
        aws ecr describe-repositories --repository-names ${{ steps.config.outputs.IMAGE_NAME }} || \
        aws ecr create-repository --repository-name ${{ steps.config.outputs.IMAGE_NAME }}

    - name: Build and push Docker image
      if: ${{ steps.config.outputs.DESTROY_APP != 'true' }}
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: ${{ steps.config.outputs.IMAGE_NAME }}
        IMAGE_TAG: ${{ steps.config.outputs.IMAGE_TAG }}
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

    - name: Export secrets to AWS Secrets Manager
      if: ${{ steps.config.outputs.DESTROY_APP != 'true' }}
      run: |
        python - <<EOF
        import json
        import os
        import subprocess

        # Get secrets list from config
        secrets_list = json.loads('${{ steps.config.outputs.SECRETS_LIST }}')

        # Get all secrets from GitHub
        github_secrets = json.loads(os.environ['SECRETS'])

        # Only process secrets that are in our list
        for secret_name in secrets_list:
            if secret_name in github_secrets:
                secret_value = github_secrets[secret_name]  # Use the plain secret value, not JSON
                aws_secret_name = f"incode-labs/${{ steps.config.outputs.APP_NAME }}/{secret_name}"

                try:
                    # Try to create the secret
                    subprocess.run([
                        "aws", "secretsmanager", "create-secret",
                        "--name", aws_secret_name,
                        "--secret-string", secret_value
                    ], check=True, capture_output=True)
                except subprocess.CalledProcessError as e:
                    if "ResourceExistsException" in e.stderr.decode():
                        # If secret exists, update its value
                        subprocess.run([
                            "aws", "secretsmanager", "put-secret-value",
                            "--secret-id", aws_secret_name,
                            "--secret-string", secret_value
                        ], check=True)
                    else:
                        # If it's a different error, raise it
                        raise
            else:
                print(f"Warning: Secret {secret_name} not found in GitHub secrets")
        EOF
      env:
        SECRETS: ${{ toJSON(secrets) }}

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v3
      with:
        terraform_version: "1.11.0"

    - name: Download Terraform module
      run: |
        mkdir -p infra/modules/app
        curl -L https://bootstrap.labs.incode.com/modules/app/v1/main.tf -o infra/modules/app/main.tf
        curl -L https://bootstrap.labs.incode.com/modules/app/v1/variables.tf -o infra/modules/app/variables.tf
        curl -L https://bootstrap.labs.incode.com/modules/app/v1/outputs.tf -o infra/modules/app/outputs.tf
        curl -L https://bootstrap.labs.incode.com/modules/app/v1/versions.tf -o infra/modules/app/versions.tf

    - name: Generate Terraform configuration
      run: |
        mkdir -p infra

        # Install PyYAML for parsing labspec.yaml
        pip install PyYAML

        # Parse secrets and instance size from labspec.yaml
        python - <<EOF
        import json
        import yaml

        # Load labspec.yaml
        with open('labspec.yaml', 'r') as f:
            labspec = yaml.safe_load(f)

        secrets = labspec.get('secrets', [])
        secrets_tf = json.dumps(secrets)

        # Instance size to CPU/memory mapping (valid Fargate combinations)
        INSTANCE_SIZES = {
            'small':   {'cpu': 256,  'memory': 512},
            'medium':  {'cpu': 512,  'memory': 1024},
            'large':   {'cpu': 1024, 'memory': 2048},
            'xlarge':  {'cpu': 2048, 'memory': 4096},
            'xxlarge': {'cpu': 4096, 'memory': 8192},
        }

        instance_size = labspec.get('instance_size', 'small')
        if instance_size not in INSTANCE_SIZES:
            print(f"Warning: Unknown instance_size '{instance_size}', using 'small'")
            instance_size = 'small'

        resources = INSTANCE_SIZES[instance_size]
        print(f"Using instance_size '{instance_size}': {resources['cpu']} CPU units, {resources['memory']} MB memory")

        # Generate Terraform configuration
        terraform_config = f'''module "app" {{
          source = "./modules/app"

          app_name = "${{ steps.config.outputs.APP_NAME }}"
          image = "${{ steps.login-ecr.outputs.registry }}/${{ steps.config.outputs.IMAGE_NAME }}:${{ steps.config.outputs.IMAGE_TAG }}"
          container_port = ${{ steps.config.outputs.CONTAINER_PORT }}
          secrets = {secrets_tf}
          cpu = {resources['cpu']}
          memory = {resources['memory']}
        }}'''

        with open('infra/main.tf', 'w') as f:
            f.write(terraform_config)
        EOF

        cat > infra/backend.tf <<EOF
        terraform {
          backend "s3" {
            bucket = "incode-labs-tfstate-34bb8h7"
            key    = "apps/${{ steps.config.outputs.APP_NAME }}/terraform.tfstate"
            region = "us-east-1"
          }
        }
        EOF

    - name: Terraform Init
      run: |
        cd infra
        terraform init

    - name: Terraform Apply
      if: ${{ steps.config.outputs.DESTROY_APP != 'true' }}
      run: |
        cd infra
        terraform apply -auto-approve

    - name: Force ECS Service Update
      if: ${{ steps.config.outputs.DESTROY_APP != 'true' }}
      run: |
        # Force update the ECS service to ensure new task definition is deployed
        aws ecs update-service \
          --cluster incode-labs-cluster \
          --service ${{ steps.config.outputs.APP_NAME }} \
          --force-new-deployment \
          --region us-east-1

        # Wait for the service to stabilize
        echo "Waiting for service to stabilize..."
        aws ecs wait services-stable \
          --cluster incode-labs-cluster \
          --services ${{ steps.config.outputs.APP_NAME }} \
          --region us-east-1

        echo "Service update completed successfully"

    - name: Update Apps Database
      if: ${{ steps.config.outputs.DESTROY_APP != 'true' }}
      run: |
        # Install boto3
        pip install boto3

        # Use Python to create a properly formatted DynamoDB item
        python - <<EOF
        import json
        import boto3
        import datetime

        # Get the app metadata
        app_name = "${{ steps.config.outputs.APP_NAME }}"
        description = "${{ steps.config.outputs.DESCRIPTION }}"
        labspec = json.loads('${{ steps.config.outputs.LABSPEC }}')

        # Get the repository URL from GitHub context
        repo_url = "https://github.com/${{ github.repository }}"

        # Create the DynamoDB item
        item = {
            "app_name": {"S": app_name},
            "description": {"S": description or "No description available"},
            "deployment_date": {"S": datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")},
            "url": {"S": f"https://{app_name}.labs.incode.com"},
            "status": {"S": "ACTIVE"},
            "repo_url": {"S": repo_url},
            "labspec": {"S": json.dumps(labspec)}
        }

        # Update the DynamoDB table
        dynamodb = boto3.client('dynamodb')
        dynamodb.put_item(
            TableName="incode-labs-apps",
            Item=item
        )

        print(f"Updated app {app_name} in DynamoDB")
        EOF

        # Trigger the Lambda function to update the index page
        aws lambda invoke \
          --function-name incode-labs-apps-updater \
          --payload '{}' \
          response.json

    - name: Terraform Destroy
      if: ${{ steps.config.outputs.DESTROY_APP == 'true' }}
      run: |
        cd infra
        terraform destroy -auto-approve

    - name: Remove App from Database
      if: ${{ steps.config.outputs.DESTROY_APP == 'true' }}
      run: |
        # Install boto3
        pip install boto3

        # Use Python to delete the item from DynamoDB
        python - <<EOF
        import boto3

        # Get the app name
        app_name = "${{ steps.config.outputs.APP_NAME }}"

        # Delete from DynamoDB
        dynamodb = boto3.client('dynamodb')
        dynamodb.delete_item(
            TableName="incode-labs-apps",
            Key={"app_name": {"S": app_name}}
        )

        print(f"Removed app {app_name} from DynamoDB")
        EOF

        # Trigger the Lambda function to update the index page
        aws lambda invoke \
          --function-name incode-labs-apps-updater \
          --payload '{}' \
          response.json
