import boto3
from botocore.exceptions import ClientError
import time
import os
import zipfile

# A script to deploy a simple Python web application to AWS Elastic Beanstalk using Boto3.

# --- Configuration ---
REGION = "us-east-1"
APPLICATION_NAME = "MyBoto3WebApp"
ENVIRONMENT_NAME = "MyBoto3WebApp-env"
VERSION_LABEL = "v1"
PLATFORM_SOLUTION_STACK = "64bit Amazon Linux 2 v3.9.1 running Python 3.9" # Check AWS docs for latest
SOURCE_BUNDLE_ZIP = "webapp.zip"
APP_DIR = "webapp"

eb_client = boto3.client('elasticbeanstalk', region_name=REGION)
s3_client = boto3.client('s3', region_name=REGION)
sts_client = boto3.client('sts', region_name=REGION)

def create_eb_application():
    """Creates an Elastic Beanstalk application."""
    print(f"--- Creating Elastic Beanstalk Application: {APPLICATION_NAME} ---")
    try:
        eb_client.create_application(
            ApplicationName=APPLICATION_NAME,
            Description="My Boto3 Python Web App"
        )
        print("Application created.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'TooManyApplicationsException' or e.response['Error']['Code'] == 'ApplicationAlreadyExists':
            print(f"Application '{APPLICATION_NAME}' already exists. Skipping creation.")
        else:
            print(f"Error creating application: {e}")
            raise

def create_source_bundle():
    """Creates a simple Python Flask application and packages it into a zip file."""
    print(f"\n--- Creating source bundle for the web application ---")
    os.makedirs(APP_DIR, exist_ok=True)
    
    with open(os.path.join(APP_DIR, "application.py"), "w") as f:
        f.write("""
from flask import Flask
application = Flask(__name__)

@application.route('/')
def hello_world():
    return 'Hello, World from Elastic Beanstalk Boto3!'

if __name__ == '__main__':
    application.run(debug=True)
""")
    with open(os.path.join(APP_DIR, "requirements.txt"), "w") as f:
        f.write("Flask==2.0.2")
    os.makedirs(os.path.join(APP_DIR, ".ebextensions"), exist_ok=True)
    with open(os.path.join(APP_DIR, ".ebextensions", "python.config"), "w") as f:
        f.write("""
option_settings:
  aws:elasticbeanstalk:container:python:
    WSGIPath: application.py
""")

    with zipfile.ZipFile(SOURCE_BUNDLE_ZIP, 'w', zipfile.ZIP_DEFLATED) as zf:
        for root, dirs, files in os.walk(APP_DIR):
            for file in files:
                zf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), APP_DIR))
    print(f"Source bundle '{SOURCE_BUNDLE_ZIP}' created.")

def upload_source_bundle_to_s3():
    """Uploads the source bundle to the default Elastic Beanstalk S3 bucket."""
    print(f"\n--- Uploading source bundle to S3 ---")
    account_id = sts_client.get_caller_identity()['Account']
    eb_s3_bucket_name = f"elasticbeanstalk-{REGION}-{account_id}"
    
    try:
        s3_client.upload_file(SOURCE_BUNDLE_ZIP, eb_s3_bucket_name, SOURCE_BUNDLE_ZIP)
        print(f"Source bundle uploaded to s3://{eb_s3_bucket_name}/{SOURCE_BUNDLE_ZIP}")
        return eb_s3_bucket_name
    except ClientError as e:
        print(f"Error uploading source bundle to S3: {e}")
        raise

def create_application_version(eb_s3_bucket_name):
    """Creates a new application version in Elastic Beanstalk."""
    print(f"\n--- Creating Application Version: {VERSION_LABEL} ---")
    try:
        eb_client.create_application_version(
            ApplicationName=APPLICATION_NAME,
            VersionLabel=VERSION_LABEL,
            SourceBundle={'S3Bucket': eb_s3_bucket_name, 'S3Key': SOURCE_BUNDLE_ZIP},
            Description="Initial application version"
        )
        print("Application version created.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'TooManyApplicationVersionsException' or e.response['Error']['Code'] == 'ApplicationVersionAlreadyExists':
            print(f"Application version '{VERSION_LABEL}' already exists. Skipping creation.")
        else:
            print(f"Error creating application version: {e}")
            raise

def create_environment():
    """Creates an Elastic Beanstalk environment."""
    print(f"\n--- Creating Elastic Beanstalk Environment: {ENVIRONMENT_NAME} ---")
    try:
        eb_client.create_environment(
            ApplicationName=APPLICATION_NAME,
            EnvironmentName=ENVIRONMENT_NAME,
            VersionLabel=VERSION_LABEL,
            SolutionStackName=PLATFORM_SOLUTION_STACK,
            OptionSettings=[
                {'Namespace': 'aws:autoscaling:launchconfiguration', 'OptionName': 'InstanceType', 'Value': 't2.micro'},
                {'Namespace': 'aws:autoscaling:asg', 'OptionName': 'MinSize', 'Value': '1'},
                {'Namespace': 'aws:autoscaling:asg', 'OptionName': 'MaxSize', 'Value': '1'},
                {'Namespace': 'aws:elasticbeanstalk:environment', 'OptionName': 'EnvironmentType', 'Value': 'SingleInstance'}
            ]
        )
        print("Environment creation initiated. Waiting for environment to be ready (this can take several minutes)...")
        eb_client.get_waiter('environment_ready').wait(EnvironmentNames=[ENVIRONMENT_NAME])
        print("Environment is ready.")
        
        response = eb_client.describe_environments(EnvironmentNames=[ENVIRONMENT_NAME])
        env_url = response['Environments'][0]['CNAME']
        return env_url
    except ClientError as e:
        print(f"Error creating environment: {e}")
        raise

def cleanup_resources(eb_s3_bucket_name):
    """Cleans up all created resources."""
    print(f"\n--- Cleaning up resources ---")

    # Terminate Environment
    print(f"Terminating environment '{ENVIRONMENT_NAME}'...")
    try:
        eb_client.terminate_environment(EnvironmentName=ENVIRONMENT_NAME)
        eb_client.get_waiter('environment_terminated').wait(EnvironmentNames=[ENVIRONMENT_NAME])
        print("Environment terminated.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'EnvironmentNotFoundException':
            print(f"Environment '{ENVIRONMENT_NAME}' not found, skipping termination.")
        else:
            print(f"Error terminating environment: {e}")

    # Delete Application Version
    print(f"Deleting application version '{VERSION_LABEL}'...")
    try:
        eb_client.delete_application_version(ApplicationName=APPLICATION_NAME, VersionLabel=VERSION_LABEL)
        print("Application version deleted.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'ApplicationVersionDoesNotExistException':
            print(f"Application version '{VERSION_LABEL}' not found, skipping deletion.")
        else:
            print(f"Error deleting application version: {e}")

    # Delete Application
    print(f"Deleting application '{APPLICATION_NAME}'...")
    try:
        eb_client.delete_application(ApplicationName=APPLICATION_NAME)
        print("Application deleted.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'ApplicationDoesNotExistException':
            print(f"Application '{APPLICATION_NAME}' not found, skipping deletion.")
        else:
            print(f"Error deleting application: {e}")

    # Delete source bundle from S3
    print(f"Deleting source bundle '{SOURCE_BUNDLE_ZIP}' from S3 bucket '{eb_s3_bucket_name}'...")
    try:
        s3_client.delete_object(Bucket=eb_s3_bucket_name, Key=SOURCE_BUNDLE_ZIP)
        print("Source bundle deleted from S3.")
    except ClientError as e:
        print(f"Error deleting source bundle from S3: {e}")

    # Clean up local files
    if os.path.exists(SOURCE_BUNDLE_ZIP):
        os.remove(SOURCE_BUNDLE_ZIP)
    if os.path.exists(APP_DIR):
        for root, dirs, files in os.walk(APP_DIR, topdown=False):
            for name in files:
                os.remove(os.path.join(root, name))
            for name in dirs:
                os.rmdir(os.path.join(root, name))
        os.rmdir(APP_DIR)
    print("Local files cleaned up.")

def main():
    eb_s3_bucket_name = None
    try:
        create_eb_application()
        create_source_bundle()
        eb_s3_bucket_name = upload_source_bundle_to_s3()
        create_application_version(eb_s3_bucket_name)
        env_url = create_environment()

        print("\n--- Web Application Deployed Successfully! ---")
        print(f"Environment URL: http://{env_url}")

        input("Press Enter to terminate the environment and clean up resources...")

    except ClientError as e:
        print(f"An AWS client error occurred: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
    finally:
        cleanup_resources(eb_s3_bucket_name)
        print("\n--- Elastic Beanstalk demonstration and cleanup complete ---")

if __name__ == "__main__":
    main()
