import boto3
from botocore.exceptions import ClientError
import time

# A script to create a multi-tier VPC for a web application using Boto3.
# This sets up a VPC with public and private subnets, an Internet Gateway,
# a NAT Gateway, route tables, and security groups for a 3-tier architecture.

# --- Configuration ---
REGION = "us-east-1"
VPC_CIDR = "10.0.0.0/16"
PUBLIC_SUBNET_CIDR = "10.0.1.0/24"
PRIVATE_SUBNET_APP_CIDR = "10.0.2.0/24"
PRIVATE_SUBNET_DB_CIDR = "10.0.3.0/24"
VPC_NAME = "MyWebAppVPC-Boto3"
AZ_1 = f"{REGION}a"
AZ_2 = f"{REGION}b"

ec2_client = boto3.client('ec2', region_name=REGION)
ec2_resource = boto3.resource('ec2', region_name=REGION)

def create_vpc():
    """Creates a VPC and returns the VPC object."""
    print("Creating VPC...")
    vpc = ec2_resource.create_vpc(
        CidrBlock=VPC_CIDR,
        TagSpecifications=[{'ResourceType': 'vpc', 'Tags': [{'Key': 'Name', 'Value': VPC_NAME}}])
    vpc.wait_until_available()
    print(f"VPC created with ID: {vpc.id}")
    return vpc

def create_subnet(vpc, cidr, az, name):
    """Creates a subnet and returns the Subnet object."""
    print(f"Creating subnet {name}...")
    subnet = vpc.create_subnet(
        CidrBlock=cidr,
        AvailabilityZone=az,
        TagSpecifications=[{'ResourceType': 'subnet', 'Tags': [{'Key': 'Name', 'Value': name}}])
    print(f"Subnet {name} created with ID: {subnet.id}")
    return subnet

def create_internet_gateway(vpc):
    """Creates and attaches an Internet Gateway."""
    print("Creating and attaching Internet Gateway...")
    igw = ec2_resource.create_internet_gateway(
        TagSpecifications=[{'ResourceType': 'internet-gateway', 'Tags': [{'Key': 'Name', 'Value': f"{VPC_NAME}-IGW"}}]})
    vpc.attach_internet_gateway(InternetGatewayId=igw.id)
    print(f"Internet Gateway created and attached: {igw.id}")
    return igw

def create_nat_gateway(public_subnet):
    """Creates a NAT Gateway in the public subnet."""
    print("Creating NAT Gateway...")
    eip = ec2_client.allocate_address(Domain='vpc')
    nat_gw = ec2_client.create_nat_gateway(
        SubnetId=public_subnet.id,
        AllocationId=eip['AllocationId'],
        TagSpecifications=[{'ResourceType': 'nat-gateway', 'Tags': [{'Key': 'Name', 'Value': f"{VPC_NAME}-NAT-GW"}}]})
    nat_gw_id = nat_gw['NatGateway']['NatGatewayId']
    print(f"NAT Gateway created: {nat_gw_id}. Waiting for it to become available...")
    waiter = ec2_client.get_waiter('nat_gateway_available')
    waiter.wait(NatGatewayIds=[nat_gw_id])
    print("NAT Gateway is available.")
    return nat_gw_id

def create_route_table(vpc, name, gateway_id=None, nat_gateway_id=None):
    """Creates a route table and a default route."""
    print(f"Creating route table {name}...")
    rt = vpc.create_route_table(
        TagSpecifications=[{'ResourceType': 'route-table', 'Tags': [{'Key': 'Name', 'Value': name}}])
    if gateway_id:
        rt.create_route(DestinationCidrBlock='0.0.0.0/0', GatewayId=gateway_id)
    elif nat_gateway_id:
        rt.create_route(DestinationCidrBlock='0.0.0.0/0', NatGatewayId=nat_gateway_id)
    print(f"Route Table {name} created: {rt.id}")
    return rt

def create_security_group(vpc, name, description):
    """Creates a security group."""
    print(f"Creating security group {name}...")
    sg = vpc.create_security_group(
        GroupName=name,
        Description=description,
        TagSpecifications=[{'ResourceType': 'security-group', 'Tags': [{'Key': 'Name', 'Value': name}}])
    print(f"Security Group {name} created: {sg.id}")
    return sg

def main():
    """Main function to orchestrate VPC creation."""
    try:
        # Create VPC
        vpc = create_vpc()

        # Create Subnets
        public_subnet = create_subnet(vpc, PUBLIC_SUBNET_CIDR, AZ_1, f"{VPC_NAME}-Public-Subnet")
        private_app_subnet = create_subnet(vpc, PRIVATE_SUBNET_APP_CIDR, AZ_1, f"{VPC_NAME}-Private-App-Subnet")
        private_db_subnet = create_subnet(vpc, PRIVATE_SUBNET_DB_CIDR, AZ_2, f"{VPC_NAME}-Private-DB-Subnet")

        # Create Gateways
        igw = create_internet_gateway(vpc)
        nat_gw_id = create_nat_gateway(public_subnet)

        # Create and Associate Route Tables
        public_rt = create_route_table(vpc, f"{VPC_NAME}-Public-RT", gateway_id=igw.id)
        public_rt.associate_with_subnet(SubnetId=public_subnet.id)
        print(f"Associated Public RT with subnet {public_subnet.id}")

        private_rt = create_route_table(vpc, f"{VPC_NAME}-Private-RT", nat_gateway_id=nat_gw_id)
        private_rt.associate_with_subnet(SubnetId=private_app_subnet.id)
        private_rt.associate_with_subnet(SubnetId=private_db_subnet.id)
        print(f"Associated Private RT with subnets {private_app_subnet.id} and {private_db_subnet.id}")

        # Create Security Groups
        web_sg = create_security_group(vpc, f"{VPC_NAME}-Web-SG", "Allow HTTP/HTTPS")
        web_sg.authorize_ingress(IpProtocol="tcp", FromPort=80, ToPort=80, CidrIp="0.0.0.0/0")
        web_sg.authorize_ingress(IpProtocol="tcp", FromPort=443, ToPort=443, CidrIp="0.0.0.0/0")

        app_sg = create_security_group(vpc, f"{VPC_NAME}-App-SG", "Allow traffic from Web SG")
        app_sg.authorize_ingress(IpProtocol="tcp", FromPort=8080, ToPort=8080, SourceSecurityGroupId=web_sg.id)

        db_sg = create_security_group(vpc, f"{VPC_NAME}-DB-SG", "Allow traffic from App SG")
        db_sg.authorize_ingress(IpProtocol="tcp", FromPort=3306, ToPort=3306, SourceSecurityGroupId=app_sg.id)

        print("\n--- Multi-Tier VPC setup complete! ---")
        print(f"VPC ID: {vpc.id}")
        print(f"Public Subnet ID: {public_subnet.id}")
        print(f"App Subnet ID: {private_app_subnet.id}")
        print(f"DB Subnet ID: {private_db_subnet.id}")
        print(f"Web SG ID: {web_sg.id}")
        print(f"App SG ID: {app_sg.id}")
        print(f"DB SG ID: {db_sg.id}")

    except ClientError as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    main()
