import boto3
from botocore.exceptions import ClientError
import time

# A script to create a basic MySQL RDS instance, including necessary prerequisites,
# and then clean up all resources using Boto3.

# --- Configuration ---
REGION = "us-east-1"
DB_INSTANCE_IDENTIFIER = "my-boto3-mysql-instance"
MASTER_USERNAME = "admin"
MASTER_USER_PASSWORD = "MySecurePassword123!" # !!! IMPORTANT: Use a strong password in production !!!
ALLOCATED_STORAGE = 20 # GB
DB_INSTANCE_CLASS = "db.t3.micro"
ENGINE = "mysql"
ENGINE_VERSION = "8.0.28"
DB_SUBNET_GROUP_NAME = "my-boto3-db-subnet-group"
SECURITY_GROUP_NAME = "my-boto3-rds-sg"

ec2_client = boto3.client('ec2', region_name=REGION)
rds_client = boto3.client('rds', region_name=REGION)

def get_default_vpc_and_subnets():
    """Gets the default VPC ID and two default subnet IDs."""
    print("--- Getting Default VPC and Subnet IDs ---")
    try:
        vpcs = ec2_client.describe_vpcs(Filters=[{'Name': 'is-default', 'Values': ['true']}])['Vpcs']
        if not vpcs:
            raise Exception("No default VPC found.")
        vpc_id = vpcs[0]['VpcId']
        print(f"Default VPC ID: {vpc_id}")

        subnets = ec2_client.describe_subnets(Filters=[{'Name': 'vpc-id', 'Values': [vpc_id]}, {'Name': 'default-for-az', 'Values': ['true']}])['Subnets']
        if len(subnets) < 2:
            raise Exception("Not enough default subnets found in the default VPC.")
        subnet_ids = [s['SubnetId'] for s in subnets[:2]] # Take first two
        print(f"Default Subnet IDs: {subnet_ids}")
        return vpc_id, subnet_ids
    except ClientError as e:
        print(f"Error getting VPC/Subnet info: {e}")
        raise

def create_db_subnet_group(subnet_ids):
    """Creates a DB Subnet Group."""
    print(f"\n--- Creating DB Subnet Group: {DB_SUBNET_GROUP_NAME} ---")
    try:
        rds_client.create_db_subnet_group(
            DBSubnetGroupName=DB_SUBNET_GROUP_NAME,
            DBSubnetGroupDescription="Subnet group for Boto3 RDS instance",
            SubnetIds=subnet_ids
        )
        print("DB Subnet Group created.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'DBSubnetGroupAlreadyExists':
            print(f"DB Subnet Group '{DB_SUBNET_GROUP_NAME}' already exists. Skipping creation.")
        else:
            print(f"Error creating DB Subnet Group: {e}")
            raise

def create_security_group(vpc_id):
    """Creates a Security Group for RDS."""
    print(f"\n--- Creating Security Group: {SECURITY_GROUP_NAME} ---")
    try:
        sg_response = ec2_client.create_security_group(
            GroupName=SECURITY_GROUP_NAME,
            Description="Allow MySQL traffic for RDS",
            VpcId=vpc_id
        )
        sg_id = sg_response['GroupId']
        print(f"Security Group '{SECURITY_GROUP_NAME}' created with ID: {sg_id}")

        ec2_client.authorize_security_group_ingress(
            GroupId=sg_id,
            IpPermissions=[
                {'IpProtocol': 'tcp', 'FromPort': 3306, 'ToPort': 3306, 'IpRanges': [{'CidrIp': '0.0.0.0/0'}]}
            ]
        )
        print("Inbound MySQL rule added to Security Group.")
        return sg_id
    except ClientError as e:
        if e.response['Error']['Code'] == 'InvalidGroup.Duplicate':
            print(f"Security Group '{SECURITY_GROUP_NAME}' already exists. Fetching ID.")
            response = ec2_client.describe_security_groups(GroupNames=[SECURITY_GROUP_NAME])
            return response['SecurityGroups'][0]['GroupId']
        else:
            print(f"Error creating security group: {e}")
            raise

def create_rds_instance(sg_id):
    """Creates a MySQL RDS instance."""
    print(f"\n--- Creating RDS Instance: {DB_INSTANCE_IDENTIFIER} ---")
    try:
        rds_client.create_db_instance(
            DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER,
            DBInstanceClass=DB_INSTANCE_CLASS,
            Engine=ENGINE,
            MasterUsername=MASTER_USERNAME,
            MasterUserPassword=MASTER_USER_PASSWORD,
            AllocatedStorage=ALLOCATED_STORAGE,
            DBSubnetGroupName=DB_SUBNET_GROUP_NAME,
            VpcSecurityGroupIds=[sg_id],
            EngineVersion=ENGINE_VERSION,
            PubliclyAccessible=True, # For demo purposes
            Tags=[{'Key': 'Name', 'Value': DB_INSTANCE_IDENTIFIER}]
        )
        print(f"RDS instance '{DB_INSTANCE_IDENTIFIER}' created. Waiting for it to be available...")
        rds_client.get_waiter('db_instance_available').wait(DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER)
        print("RDS instance is available.")
        
        response = rds_client.describe_db_instances(DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER)
        endpoint = response['DBInstances'][0]['Endpoint']['Address']
        return endpoint
    except ClientError as e:
        print(f"Error creating RDS instance: {e}")
        raise

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

    # Delete RDS Instance
    print(f"Deleting RDS instance '{DB_INSTANCE_IDENTIFIER}'...")
    try:
        rds_client.delete_db_instance(
            DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER,
            SkipFinalSnapshot=True,
            DeleteAutomatedBackups=True
        )
        rds_client.get_waiter('db_instance_deleted').wait(DBInstanceIdentifier=DB_INSTANCE_IDENTIFIER)
        print("RDS instance deleted.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'DBInstanceNotFound':
            print(f"RDS instance '{DB_INSTANCE_IDENTIFIER}' not found, skipping deletion.")
        else:
            print(f"Error deleting RDS instance: {e}")

    # Delete DB Subnet Group
    print(f"Deleting DB Subnet Group '{DB_SUBNET_GROUP_NAME}'...")
    try:
        rds_client.delete_db_subnet_group(DBSubnetGroupName=DB_SUBNET_GROUP_NAME)
        print("DB Subnet Group deleted.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'DBSubnetGroupNotFoundFault':
            print(f"DB Subnet Group '{DB_SUBNET_GROUP_NAME}' not found, skipping deletion.")
        else:
            print(f"Error deleting DB Subnet Group: {e}")

    # Delete Security Group
    print(f"Deleting Security Group '{SECURITY_GROUP_NAME}'...")
    try:
        ec2_client.delete_security_group(GroupId=sg_id)
        print("Security Group deleted.")
    except ClientError as e:
        if e.response['Error']['Code'] == 'InvalidGroup.NotFound':
            print(f"Security Group '{SECURITY_GROUP_NAME}' not found, skipping deletion.")
        elif e.response['Error']['Code'] == 'DependencyViolation':
            print(f"Security Group '{SECURITY_GROUP_NAME}' is still in use. Retrying deletion after a short delay.")
            time.sleep(10)
            ec2_client.delete_security_group(GroupId=sg_id)
            print("Security Group deleted.")
        else:
            print(f"Error deleting security group: {e}")

def main():
    sg_id = None
    try:
        vpc_id, subnet_ids = get_default_vpc_and_subnets()
        create_db_subnet_group(subnet_ids)
        sg_id = create_security_group(vpc_id)
        db_endpoint = create_rds_instance(sg_id)

        print("\n--- RDS Instance Created Successfully! ---")
        print(f"DB Instance Identifier: {DB_INSTANCE_IDENTIFIER}")
        print(f"Endpoint: {db_endpoint}")
        print(f"Username: {MASTER_USERNAME}")
        print(f"Password: {MASTER_USER_PASSWORD}")

        input("Press Enter to delete the RDS instance 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(sg_id)
        print("\n--- RDS instance demonstration and cleanup complete ---")

if __name__ == "__main__":
    main()
