import boto3
from botocore.exceptions import ClientError
import time
import uuid
import os
import json

# A script to create an AWS Glue Crawler, including necessary prerequisites,
# and then clean up all resources using Boto3.

# --- Configuration ---
REGION = "us-east-1"
RANDOM_SUFFIX = uuid.uuid4().hex[:8]
S3_BUCKET_NAME = f"my-boto3-glue-data-{RANDOM_SUFFIX}"
GLUE_DATABASE_NAME = "my_boto3_glue_database"
GLUE_CRAWLER_NAME = "MyBoto3GlueCrawler"
IAM_ROLE_NAME = "MyBoto3GlueCrawlerRole"
SAMPLE_DATA_FILE_NAME = "sample_data.csv"
SAMPLE_DATA_CONTENT = "id,name,value\n1,Alice,100\n2,Bob,200"

s3_client = boto3.client('s3', region_name=REGION)
glue_client = boto3.client('glue', region_name=REGION)
iam_client = boto3.client('iam', region_name=REGION)

def create_s3_bucket_and_upload_data():
    """Creates an S3 bucket and uploads sample data."""
    print(f"--- Creating S3 Bucket: {S3_BUCKET_NAME} ---")
    try:
        s3_client.create_bucket(
            Bucket=S3_BUCKET_NAME,
            CreateBucketConfiguration={'LocationConstraint': REGION} if REGION != 'us-east-1' else {}
        )
        print("S3 Bucket created.")

        print(f"Uploading sample data to s3://{S3_BUCKET_NAME}/input/{SAMPLE_DATA_FILE_NAME} ...")
        s3_client.put_object(
            Bucket=S3_BUCKET_NAME,
            Key=f"input/{SAMPLE_DATA_FILE_NAME}",
            Body=SAMPLE_DATA_CONTENT
        )
        print("Sample data uploaded.")
    except ClientError as e:
        print(f"Error with S3 operations: {e}")
        raise

def create_glue_database():
    """Creates a Glue Database."""
    print(f"\n--- Creating Glue Database: {GLUE_DATABASE_NAME} ---")
    try:
        glue_client.create_database(
            DatabaseInput={'Name': GLUE_DATABASE_NAME}
        )
        print("Glue Database created.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'AlreadyExistsException':
            print(f"Glue Database '{GLUE_DATABASE_NAME}' already exists. Skipping creation.")
        else:
            print(f"Error creating Glue Database: {e}")
            raise

def create_iam_role():
    """Creates an IAM role for the Glue Crawler."""
    print(f"\n--- Creating IAM Role: {IAM_ROLE_NAME} ---")
    trust_policy = {
      "Version": "2012-10-17",
      "Statement": [{"Effect": "Allow", "Principal": {"Service": "glue.amazonaws.com"}, "Action": "sts:AssumeRole"}]
    }
    try:
        role_response = iam_client.create_role(
            RoleName=IAM_ROLE_NAME,
            AssumeRolePolicyDocument=json.dumps(trust_policy),
            Description="Role for Glue Crawler"
        )
        role_arn = role_response['Role']['Arn']
        
        iam_client.attach_role_policy(
            RoleName=IAM_ROLE_NAME,
            PolicyArn='arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole'
        )
        iam_client.attach_role_policy(
            RoleName=IAM_ROLE_NAME,
            PolicyArn='arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'
        )
        print("IAM Role created and policies attached.")
        print("Waiting for IAM role to propagate...")
        time.sleep(10)
        return role_arn
    except ClientError as e:
        if e.response['Error']['Code'] == 'EntityAlreadyExists':
            print(f"IAM role '{IAM_ROLE_NAME}' already exists. Fetching ARN.")
            return iam_client.get_role(RoleName=IAM_ROLE_NAME)['Role']['Arn']
        else:
            print(f"Error creating IAM role: {e}")
            raise

def create_glue_crawler(glue_role_arn):
    """Creates a Glue Crawler."""
    print(f"\n--- Creating Glue Crawler: {GLUE_CRAWLER_NAME} ---")
    try:
        glue_client.create_crawler(
            Name=GLUE_CRAWLER_NAME,
            Role=glue_role_arn,
            DatabaseName=GLUE_DATABASE_NAME,
            Targets={'S3Targets': [{'Path': f's3://{S3_BUCKET_NAME}/input/'}]}
        )
        print("Glue Crawler created.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'AlreadyExistsException':
            print(f"Glue Crawler '{GLUE_CRAWLER_NAME}' already exists. Skipping creation.")
        else:
            print(f"Error creating Glue Crawler: {e}")
            raise

def start_glue_crawler():
    """Starts the Glue Crawler and waits for its completion."""
    print(f"\n--- Starting Glue Crawler: {GLUE_CRAWLER_NAME} ---")
    try:
        glue_client.start_crawler(Name=GLUE_CRAWLER_NAME)
        print("Waiting for crawler to complete...")
        while True:
            response = glue_client.get_crawler(Name=GLUE_CRAWLER_NAME)
            state = response['Crawler']['State']
            if state == 'READY':
                print("Glue Crawler finished.")
                break
            elif state == 'RUNNING':
                print("Crawler still running, waiting...")
            else:
                print(f"Crawler in unexpected state: {state}")
                break
            time.sleep(10)
    except ClientError as e:
        print(f"Error starting/waiting for crawler: {e}")
        raise

def get_crawler_table_name():
    """Gets the name of the table created by the crawler."""
    print("\n--- Getting table name created by crawler ---")
    try:
        response = glue_client.get_tables(DatabaseName=GLUE_DATABASE_NAME)
        if response['TableList']:
            table_name = response['TableList'][0]['Name']
            print(f"Table created: {table_name}")
            return table_name
        else:
            print("No tables found in the database after crawling.")
            return None
    except ClientError as e:
        print(f"Error getting table name: {e}")
        raise

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

    # Delete Glue Crawler
    print(f"Deleting Glue Crawler '{GLUE_CRAWLER_NAME}'...")
    try:
        glue_client.delete_crawler(Name=GLUE_CRAWLER_NAME)
        print("Glue Crawler deleted.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'EntityNotFoundException':
            print(f"Glue Crawler '{GLUE_CRAWLER_NAME}' not found, skipping deletion.")
        else:
            print(f"Error deleting Glue Crawler: {e}")

    # Delete Glue Table (if exists)
    table_name = get_crawler_table_name()
    if table_name:
        print(f"Deleting Glue Table '{table_name}'...")
        try:
            glue_client.delete_table(DatabaseName=GLUE_DATABASE_NAME, Name=table_name)
            print("Glue Table deleted.")
        except ClientError as e:
            if e.response['Error']['Code'] == 'EntityNotFoundException':
                print(f"Glue Table '{table_name}' not found, skipping deletion.")
            else:
                print(f"Error deleting Glue Table: {e}")

    # Delete Glue Database
    print(f"Deleting Glue Database '{GLUE_DATABASE_NAME}'...")
    try:
        glue_client.delete_database(Name=GLUE_DATABASE_NAME)
        print("Glue Database deleted.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'EntityNotFoundException':
            print(f"Glue Database '{GLUE_DATABASE_NAME}' not found, skipping deletion.")
        else:
            print(f"Error deleting Glue Database: {e}")

    # Detach and Delete IAM Role
    if glue_role_arn:
        print(f"Detaching policies from IAM Role '{IAM_ROLE_NAME}'...")
        try:
            iam_client.detach_role_policy(RoleName=IAM_ROLE_NAME, PolicyArn='arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole')
            iam_client.detach_role_policy(RoleName=IAM_ROLE_NAME, PolicyArn='arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess')
            print(f"Deleting IAM Role '{IAM_ROLE_NAME}'...")
            iam_client.delete_role(RoleName=IAM_ROLE_NAME)
            print("IAM Role deleted.")
        except ClientError as e:
            if e.response['Error']['Code'] == 'NoSuchEntity':
                print(f"IAM Role '{IAM_ROLE_NAME}' not found, skipping deletion.")
            else:
                print(f"Error deleting IAM role: {e}")

    # Delete S3 Bucket
    print(f"Deleting S3 Bucket '{S3_BUCKET_NAME}'...")
    try:
        s3_resource = boto3.resource('s3', region_name=REGION)
        bucket = s3_resource.Bucket(S3_BUCKET_NAME)
        bucket.objects.all().delete() # Delete all objects in the bucket
        bucket.delete()
        print("S3 Bucket deleted.")
    except ClientError as e:
        print(f"Error deleting S3 bucket: {e}")

def main():
    glue_role_arn = None
    try:
        create_s3_bucket_and_upload_data()
        create_glue_database()
        glue_role_arn = create_iam_role()
        create_glue_crawler(glue_role_arn)
        start_glue_crawler()
        
        table_name = get_crawler_table_name()
        if table_name:
            print(f"\n--- Glue Crawler Setup Complete! ---")
            print(f"Table created by crawler: {table_name} in database {GLUE_DATABASE_NAME}")
            print("You can now query this table using Amazon Athena.")

        input("Press Enter to delete the Glue resources and S3 bucket...")

    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(glue_role_arn)
        print("\n--- Glue Crawler demonstration and cleanup complete ---")

if __name__ == "__main__":
    main()
