import json
from uuid import uuid4

import boto3
import pytest
from botocore.exceptions import ClientError

from moto import mock_aws
from tests import EXAMPLE_AMI_ID
from tests.test_cloudformation.fixtures import (
    ec2_classic_eip,
    single_instance_with_ebs_volume,
    vpc_eip,
    vpc_eni,
    vpc_single_instance_in_subnet,
)

template_vpc = {
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Create VPC",
    "Resources": {
        "VPC": {"Properties": {"CidrBlock": "192.168.0.0/16"}, "Type": "AWS::EC2::VPC"}
    },
}

template_subnet = {
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Create VPC",
    "Resources": {
        "VPC": {"Properties": {"CidrBlock": "192.168.0.0/16"}, "Type": "AWS::EC2::VPC"},
        "Subnet": {
            "Properties": {"VpcId": {"Ref": "VPC"}, "CidrBlock": "192.168.0.0/18"},
            "Type": "AWS::EC2::Subnet",
        },
    },
}


@mock_aws
def test_vpc_single_instance_in_subnet():
    template_json = json.dumps(vpc_single_instance_in_subnet.template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(
        StackName=stack_name,
        TemplateBody=template_json,
        Parameters=[{"ParameterKey": "KeyName", "ParameterValue": "my_key"}],
    )

    ec2 = boto3.client("ec2", region_name="us-west-1")

    stack = cf.describe_stacks(StackName=stack_name)["Stacks"][0]

    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    vpc_id = [
        resource
        for resource in resources
        if resource["ResourceType"] == "AWS::EC2::VPC"
    ][0]["PhysicalResourceId"]

    vpc = ec2.describe_vpcs(VpcIds=[vpc_id])["Vpcs"][0]
    assert vpc["CidrBlock"] == "10.0.0.0/16"
    assert {"Key": "Application", "Value": stack["StackId"]} in vpc["Tags"]

    security_group = ec2.describe_security_groups(
        Filters=[{"Name": "vpc-id", "Values": [vpc["VpcId"]]}]
    )["SecurityGroups"][0]
    assert security_group["VpcId"] == vpc["VpcId"]

    subnet_id = [
        resource
        for resource in resources
        if resource["ResourceType"] == "AWS::EC2::Subnet"
    ][0]["PhysicalResourceId"]

    subnet = ec2.describe_subnets(SubnetIds=[subnet_id])["Subnets"][0]
    assert subnet["VpcId"] == vpc["VpcId"]

    instance_id = [
        resource
        for resource in resources
        if resource["ResourceType"] == "AWS::EC2::Instance"
    ][0]["PhysicalResourceId"]
    res = ec2.describe_instances(InstanceIds=[instance_id])["Reservations"][0]
    instance = res["Instances"][0]
    assert {"Key": "Foo", "Value": "Bar"} in instance["Tags"]

    eip_id = [
        resource
        for resource in resources
        if resource["ResourceType"] == "AWS::EC2::EIP"
    ][0]["PhysicalResourceId"]
    eip = ec2.describe_addresses(PublicIps=[eip_id])["Addresses"][0]
    assert eip["Domain"] == "vpc"
    assert eip["InstanceId"] == instance["InstanceId"]


@mock_aws
def test_delete_stack_with_vpc():
    cf = boto3.client("cloudformation", region_name="us-east-1")
    ec2 = boto3.client("ec2", region_name="us-east-1")
    name = str(uuid4())[0:6]

    cf.create_stack(StackName=name, TemplateBody=json.dumps(template_vpc))
    resources = cf.list_stack_resources(StackName=name)["StackResourceSummaries"]
    vpc_id = resources[0]["PhysicalResourceId"]

    assert len(ec2.describe_vpcs(VpcIds=[vpc_id])["Vpcs"]) == 1

    cf.delete_stack(StackName=name)
    with pytest.raises(ec2.exceptions.ClientError):
        ec2.describe_vpcs(VpcIds=[vpc_id])


@mock_aws
def test_delete_stack_with_subnet():
    cf = boto3.client("cloudformation", region_name="us-east-1")
    ec2 = boto3.client("ec2", region_name="us-east-1")
    name = str(uuid4())[0:6]

    cf.create_stack(StackName=name, TemplateBody=json.dumps(template_subnet))
    subnet_ids = [
        resource["PhysicalResourceId"]
        for resource in cf.list_stack_resources(StackName=name)[
            "StackResourceSummaries"
        ]
        if resource["ResourceType"] == "AWS::EC2::Subnet"
    ]

    assert len(ec2.describe_subnets(SubnetIds=subnet_ids)["Subnets"]) == 1

    cf.delete_stack(StackName=name)
    with pytest.raises(ec2.exceptions.ClientError):
        ec2.describe_subnets(SubnetIds=subnet_ids)


@mock_aws
def test_elastic_network_interfaces_cloudformation_boto3():
    template = vpc_eni.template
    template_json = json.dumps(template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    ec2 = boto3.client("ec2", region_name="us-west-1")
    all_enis = ec2.describe_network_interfaces()["NetworkInterfaces"]
    all_eni_ids = [eni["NetworkInterfaceId"] for eni in all_enis]
    all_ips = [eni["PrivateIpAddresses"][0]["PrivateIpAddress"] for eni in all_enis]

    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    cfn_eni = [
        resource
        for resource in resources
        if resource["ResourceType"] == "AWS::EC2::NetworkInterface"
    ][0]
    assert cfn_eni["PhysicalResourceId"] in all_eni_ids

    outputs = cf.describe_stacks(StackName=stack_name)["Stacks"][0]["Outputs"]
    received_ip = [
        o["OutputValue"] for o in outputs if o["OutputKey"] == "ENIIpAddress"
    ][0]
    assert received_ip in all_ips


@mock_aws
def test_volume_size_through_cloudformation():
    ec2 = boto3.client("ec2", region_name="us-east-1")
    cf = boto3.client("cloudformation", region_name="us-east-1")

    tag_value = str(uuid4())
    volume_template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "testInstance": {
                "Type": "AWS::EC2::Instance",
                "Properties": {
                    "ImageId": EXAMPLE_AMI_ID,
                    "KeyName": "dummy",
                    "InstanceType": "t2.micro",
                    "BlockDeviceMappings": [
                        {"DeviceName": "/dev/sda2", "Ebs": {"VolumeSize": "50"}}
                    ],
                    "Tags": [
                        {"Key": "foo", "Value": "bar"},
                        {"Key": "blah", "Value": tag_value},
                    ],
                },
            }
        },
    }
    template_json = json.dumps(volume_template)

    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)

    resource = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"][
        0
    ]
    assert resource["LogicalResourceId"] == "testInstance"
    assert resource["PhysicalResourceId"] is not None
    assert resource["ResourceType"] == "AWS::EC2::Instance"

    instances = ec2.describe_instances(InstanceIds=[resource["PhysicalResourceId"]])

    volume = instances["Reservations"][0]["Instances"][0]["BlockDeviceMappings"][0][
        "Ebs"
    ]

    volumes = ec2.describe_volumes(VolumeIds=[volume["VolumeId"]])
    assert volumes["Volumes"][0]["Size"] == 50


@mock_aws
def test_attach_internet_gateway():
    ec2 = boto3.client("ec2", region_name="us-east-1")
    cf = boto3.client("cloudformation", region_name="us-east-1")

    volume_template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "DEVLAB1": {
                "Type": "AWS::EC2::VPC",
                "Properties": {"CidrBlock": "10.0.0.0/16"},
            },
            "internetgateway": {"Type": "AWS::EC2::InternetGateway"},
            "DEVLAB1VPGAttaching": {
                "Type": "AWS::EC2::VPCGatewayAttachment",
                "Properties": {
                    "VpcId": {"Ref": "DEVLAB1"},
                    "InternetGatewayId": {"Ref": "internetgateway"},
                },
            },
        },
    }
    template_json = json.dumps(volume_template)
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    stack_resources = cf.list_stack_resources(StackName=stack_name)[
        "StackResourceSummaries"
    ]

    # Verify VPC is created
    vpc = [r for r in stack_resources if r["ResourceType"] == "AWS::EC2::VPC"][0]
    assert vpc["LogicalResourceId"] == "DEVLAB1"
    vpc_id = vpc["PhysicalResourceId"]

    # Verify Internet Gateway is created
    gateway_id = get_resource_id("AWS::EC2::InternetGateway", stack_resources)
    gateway = ec2.describe_internet_gateways(InternetGatewayIds=[gateway_id])[
        "InternetGateways"
    ][0]
    assert {"State": "available", "VpcId": vpc_id} in gateway["Attachments"]
    assert {
        "Key": "aws:cloudformation:logical-id",
        "Value": "internetgateway",
    } in gateway["Tags"]


@mock_aws
def test_attach_vpn_gateway():
    ec2 = boto3.client("ec2", region_name="us-east-1")
    cf = boto3.client("cloudformation", region_name="us-east-1")

    vpn_gateway_template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "DEVLAB1": {
                "Type": "AWS::EC2::VPC",
                "Properties": {"CidrBlock": "10.0.0.0/16"},
            },
            "DEVLAB1DCGateway": {
                "Type": "AWS::EC2::VPNGateway",
                "Properties": {"Type": "ipsec.1"},
            },
            "DEVLAB1VPGAttaching": {
                "Type": "AWS::EC2::VPCGatewayAttachment",
                "Properties": {
                    "VpcId": {"Ref": "DEVLAB1"},
                    "VpnGatewayId": {"Ref": "DEVLAB1DCGateway"},
                },
                "DependsOn": ["DEVLAB1DCGateway"],
            },
        },
    }
    template_json = json.dumps(vpn_gateway_template)
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    stack_resources = cf.list_stack_resources(StackName=stack_name)[
        "StackResourceSummaries"
    ]

    gateway_id = get_resource_id("AWS::EC2::VPNGateway", stack_resources)
    vpc_id = get_resource_id("AWS::EC2::VPC", stack_resources)

    gateway = ec2.describe_vpn_gateways(VpnGatewayIds=[gateway_id])["VpnGateways"][0]
    assert {"State": "attached", "VpcId": vpc_id} in gateway["VpcAttachments"]


def get_resource_id(resource_type, stack_resources):
    r = [r for r in stack_resources if r["ResourceType"] == resource_type][0]
    return r["PhysicalResourceId"]


@mock_aws
def test_subnet_tags_through_cloudformation_boto3():
    ec2 = boto3.client("ec2", region_name="us-west-1")
    ec2_res = boto3.resource("ec2", region_name="us-west-1")
    vpc = ec2_res.create_vpc(CidrBlock="10.0.0.0/16")

    subnet_template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "testSubnet": {
                "Type": "AWS::EC2::Subnet",
                "Properties": {
                    "VpcId": vpc.id,
                    "CidrBlock": "10.0.0.0/24",
                    "AvailabilityZone": "us-west-1b",
                    "Tags": [
                        {"Key": "foo", "Value": "bar"},
                        {"Key": "blah", "Value": "baz"},
                    ],
                },
            }
        },
    }
    template_json = json.dumps(subnet_template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    subnet_id = resources[0]["PhysicalResourceId"]

    subnet = ec2.describe_subnets(SubnetIds=[subnet_id])["Subnets"][0]
    assert subnet["CidrBlock"] == "10.0.0.0/24"
    assert {"Key": "foo", "Value": "bar"} in subnet["Tags"]
    assert {"Key": "blah", "Value": "baz"} in subnet["Tags"]


@mock_aws
def test_single_instance_with_ebs_volume():
    template_json = json.dumps(single_instance_with_ebs_volume.template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(
        StackName=stack_name,
        TemplateBody=template_json,
        Parameters=[{"ParameterKey": "KeyName", "ParameterValue": "key_name"}],
    )
    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    instance_id = [
        r["PhysicalResourceId"]
        for r in resources
        if r["ResourceType"] == "AWS::EC2::Instance"
    ][0]
    volume_id = [
        r["PhysicalResourceId"]
        for r in resources
        if r["ResourceType"] == "AWS::EC2::Volume"
    ][0]

    ec2 = boto3.client("ec2", region_name="us-west-1")
    ec2_instance = ec2.describe_instances(InstanceIds=[instance_id])["Reservations"][0][
        "Instances"
    ][0]

    volumes = ec2.describe_volumes(VolumeIds=[volume_id])["Volumes"]
    # Grab the mounted drive
    volume = [
        volume for volume in volumes if volume["Attachments"][0]["Device"] == "/dev/sdh"
    ][0]
    assert volume["State"] == "in-use"
    assert volume["Attachments"][0]["InstanceId"] == ec2_instance["InstanceId"]


@mock_aws
def test_classic_eip():
    template_json = json.dumps(ec2_classic_eip.template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    ec2 = boto3.client("ec2", region_name="us-west-1")
    all_ips = [eip["PublicIp"] for eip in ec2.describe_addresses()["Addresses"]]

    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    cfn_eip = [
        resource
        for resource in resources
        if resource["ResourceType"] == "AWS::EC2::EIP"
    ][0]
    assert cfn_eip["PhysicalResourceId"] in all_ips


@mock_aws
def test_vpc_eip():
    template_json = json.dumps(vpc_eip.template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)

    ec2 = boto3.client("ec2", region_name="us-west-1")
    all_ips = [eip["PublicIp"] for eip in ec2.describe_addresses()["Addresses"]]

    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    cfn_eip = [
        resource
        for resource in resources
        if resource["ResourceType"] == "AWS::EC2::EIP"
    ][0]
    assert cfn_eip["PhysicalResourceId"] in all_ips


@mock_aws
def test_vpc_gateway_attachment_creation_should_attach_itself_to_vpc():
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "internetgateway": {"Type": "AWS::EC2::InternetGateway"},
            "testvpc": {
                "Type": "AWS::EC2::VPC",
                "Properties": {
                    "CidrBlock": "10.0.0.0/16",
                    "EnableDnsHostnames": "true",
                    "EnableDnsSupport": "true",
                    "InstanceTenancy": "default",
                },
            },
            "vpcgatewayattachment": {
                "Type": "AWS::EC2::VPCGatewayAttachment",
                "Properties": {
                    "InternetGatewayId": {"Ref": "internetgateway"},
                    "VpcId": {"Ref": "testvpc"},
                },
            },
        },
    }

    template_json = json.dumps(template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)

    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    vpc_id = resources[1]["PhysicalResourceId"]

    ec2 = boto3.client("ec2", region_name="us-west-1")
    vpc = ec2.describe_vpcs(VpcIds=[vpc_id])["Vpcs"][0]
    assert vpc["CidrBlock"] == "10.0.0.0/16"

    igws = ec2.describe_internet_gateways(
        Filters=[{"Name": "attachment.vpc-id", "Values": [vpc["VpcId"]]}]
    )["InternetGateways"]
    assert len(igws) == 1


@mock_aws
def test_vpc_peering_creation():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    ec2_client = boto3.client("ec2", region_name="us-west-1")
    vpc_source = ec2.create_vpc(CidrBlock="10.0.0.0/16")
    peer_vpc = ec2.create_vpc(CidrBlock="10.1.0.0/16")
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "vpcpeeringconnection": {
                "Type": "AWS::EC2::VPCPeeringConnection",
                "Properties": {"PeerVpcId": peer_vpc.id, "VpcId": vpc_source.id},
            }
        },
    }

    template_json = json.dumps(template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    our_vpx_id = resources[0]["PhysicalResourceId"]

    peering_connections = ec2_client.describe_vpc_peering_connections(
        VpcPeeringConnectionIds=[our_vpx_id]
    )["VpcPeeringConnections"]
    assert len(peering_connections) == 1


@mock_aws
def test_multiple_security_group_ingress_separate_from_security_group_by_id():
    sg1 = str(uuid4())[0:6]
    sg2 = str(uuid4())[0:6]
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "test-security-group1": {
                "Type": "AWS::EC2::SecurityGroup",
                "Properties": {
                    "GroupDescription": "test security group",
                    "Tags": [{"Key": "sg-name", "Value": sg1}],
                },
            },
            "test-security-group2": {
                "Type": "AWS::EC2::SecurityGroup",
                "Properties": {
                    "GroupDescription": "test security group",
                    "Tags": [{"Key": "sg-name", "Value": sg2}],
                },
            },
            "test-sg-ingress": {
                "Type": "AWS::EC2::SecurityGroupIngress",
                "Properties": {
                    "GroupId": {"Ref": "test-security-group1"},
                    "IpProtocol": "tcp",
                    "FromPort": "80",
                    "ToPort": "8080",
                    "SourceSecurityGroupId": {"Ref": "test-security-group2"},
                },
            },
        },
    }

    template_json = json.dumps(template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    cf.create_stack(StackName=str(uuid4())[0:6], TemplateBody=template_json)
    ec2 = boto3.client("ec2", region_name="us-west-1")

    security_group1 = get_secgroup_by_tag(ec2, sg1)
    security_group2 = get_secgroup_by_tag(ec2, sg2)

    assert len(security_group1["IpPermissions"]) == 1
    assert len(security_group1["IpPermissions"][0]["UserIdGroupPairs"]) == 1
    assert (
        security_group1["IpPermissions"][0]["UserIdGroupPairs"][0]["GroupId"]
        == security_group2["GroupId"]
    )
    assert security_group1["IpPermissions"][0]["IpProtocol"] == "tcp"
    assert security_group1["IpPermissions"][0]["FromPort"] == 80
    assert security_group1["IpPermissions"][0]["ToPort"] == 8080


@mock_aws
def test_security_group_ingress_separate_from_security_group_by_id():
    ec2 = boto3.client("ec2", region_name="us-west-1")
    sg_name = str(uuid4())
    ec2.create_security_group(GroupName=sg_name, Description="test security group")

    sg_2 = str(uuid4())[0:6]
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "test-security-group2": {
                "Type": "AWS::EC2::SecurityGroup",
                "Properties": {
                    "GroupDescription": "test security group",
                    "Tags": [{"Key": "sg-name", "Value": sg_2}],
                },
            },
            "test-sg-ingress": {
                "Type": "AWS::EC2::SecurityGroupIngress",
                "Properties": {
                    "GroupName": sg_name,
                    "IpProtocol": "tcp",
                    "FromPort": "80",
                    "ToPort": "8080",
                    "SourceSecurityGroupId": {"Ref": "test-security-group2"},
                },
            },
        },
    }

    template_json = json.dumps(template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    cf.create_stack(StackName=str(uuid4())[0:6], TemplateBody=template_json)
    security_group1 = ec2.describe_security_groups(GroupNames=[sg_name])[
        "SecurityGroups"
    ][0]
    security_group2 = get_secgroup_by_tag(ec2, sg_2)

    assert len(security_group1["IpPermissions"]) == 1
    assert len(security_group1["IpPermissions"][0]["UserIdGroupPairs"]) == 1
    assert (
        security_group1["IpPermissions"][0]["UserIdGroupPairs"][0]["GroupId"]
        == security_group2["GroupId"]
    )
    assert security_group1["IpPermissions"][0]["IpProtocol"] == "tcp"
    assert security_group1["IpPermissions"][0]["FromPort"] == 80
    assert security_group1["IpPermissions"][0]["ToPort"] == 8080


@mock_aws
def test_security_group_ingress_separate_from_security_group_by_id_using_vpc():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    ec2_client = boto3.client("ec2", region_name="us-west-1")
    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")

    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "test-security-group1": {
                "Type": "AWS::EC2::SecurityGroup",
                "Properties": {
                    "GroupDescription": "test security group",
                    "VpcId": vpc.id,
                    "Tags": [{"Key": "sg-name", "Value": "sg1"}],
                },
            },
            "test-security-group2": {
                "Type": "AWS::EC2::SecurityGroup",
                "Properties": {
                    "GroupDescription": "test security group",
                    "VpcId": vpc.id,
                    "Tags": [{"Key": "sg-name", "Value": "sg2"}],
                },
            },
            "test-sg-ingress": {
                "Type": "AWS::EC2::SecurityGroupIngress",
                "Properties": {
                    "GroupId": {"Ref": "test-security-group1"},
                    "VpcId": vpc.id,
                    "IpProtocol": "tcp",
                    "FromPort": "80",
                    "ToPort": "8080",
                    "SourceSecurityGroupId": {"Ref": "test-security-group2"},
                },
            },
        },
    }

    template_json = json.dumps(template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    cf.create_stack(StackName=str(uuid4())[0:6], TemplateBody=template_json)
    security_group1 = get_secgroup_by_tag(ec2_client, "sg1")
    security_group2 = get_secgroup_by_tag(ec2_client, "sg2")

    assert len(security_group1["IpPermissions"]) == 1
    assert len(security_group1["IpPermissions"][0]["UserIdGroupPairs"]) == 1
    assert (
        security_group1["IpPermissions"][0]["UserIdGroupPairs"][0]["GroupId"]
        == security_group2["GroupId"]
    )
    assert security_group1["IpPermissions"][0]["IpProtocol"] == "tcp"
    assert security_group1["IpPermissions"][0]["FromPort"] == 80
    assert security_group1["IpPermissions"][0]["ToPort"] == 8080


@mock_aws
def test_security_group_with_update():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    ec2_client = boto3.client("ec2", region_name="us-west-1")
    vpc1 = ec2.create_vpc(CidrBlock="10.0.0.0/16")
    vpc2 = ec2.create_vpc(CidrBlock="10.1.0.0/16")

    sg = str(uuid4())[0:6]
    template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "test-security-group": {
                "Type": "AWS::EC2::SecurityGroup",
                "Properties": {
                    "GroupDescription": "test security group",
                    "VpcId": vpc1.id,
                    "Tags": [{"Key": "sg-name", "Value": sg}],
                },
            }
        },
    }

    template_json = json.dumps(template)
    cf = boto3.client("cloudformation", region_name="us-west-1")
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    security_group = get_secgroup_by_tag(ec2_client, sg)
    assert security_group["VpcId"] == vpc1.id

    template["Resources"]["test-security-group"]["Properties"]["VpcId"] = vpc2.id
    template_json = json.dumps(template)
    cf.update_stack(StackName=stack_name, TemplateBody=template_json)
    security_group = get_secgroup_by_tag(ec2_client, sg)
    assert security_group["VpcId"] == vpc2.id


@mock_aws
def test_subnets_should_be_created_with_availability_zone():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    ec2_client = boto3.client("ec2", region_name="us-west-1")
    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")

    subnet_template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Resources": {
            "testSubnet": {
                "Type": "AWS::EC2::Subnet",
                "Properties": {
                    "VpcId": vpc.id,
                    "CidrBlock": "10.0.0.0/24",
                    "AvailabilityZone": "us-west-1b",
                },
            }
        },
    }
    cf = boto3.client("cloudformation", region_name="us-west-1")
    template_json = json.dumps(subnet_template)
    stack_name = str(uuid4())[0:6]
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)
    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    subnet_id = resources[0]["PhysicalResourceId"]
    subnet = ec2_client.describe_subnets(SubnetIds=[subnet_id])["Subnets"][0]
    assert subnet["CidrBlock"] == "10.0.0.0/24"
    assert subnet["AvailabilityZone"] == "us-west-1b"


def get_secgroup_by_tag(ec2, sg_):
    return ec2.describe_security_groups(
        Filters=[{"Name": "tag:sg-name", "Values": [sg_]}]
    )["SecurityGroups"][0]


@mock_aws
def test_vpc_endpoint_creation():
    ec2 = boto3.resource("ec2", region_name="us-west-1")
    ec2_client = boto3.client("ec2", region_name="us-west-1")
    vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
    subnet1 = ec2.create_subnet(
        VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-1a"
    )

    subnet_template = {
        "AWSTemplateFormatVersion": "2010-09-09",
        "Parameters": {
            "EndpointSubnetId": {"Type": "String"},
            "EndpointVpcId": {"Type": "String"},
            "EndpointServiceName": {"Type": "String"},
        },
        "Resources": {
            "GwlbVpcEndpoint": {
                "Type": "AWS::EC2::VPCEndpoint",
                "Properties": {
                    "ServiceName": {"Ref": "EndpointServiceName"},
                    "SubnetIds": [{"Ref": "EndpointSubnetId"}],
                    "VpcEndpointType": "GatewayLoadBalancer",
                    "VpcId": {"Ref": "EndpointVpcId"},
                },
            }
        },
        "Outputs": {
            "EndpointId": {
                "Description": "Id of the endpoint created",
                "Value": {"Ref": "GwlbVpcEndpoint"},
            },
        },
    }
    cf = boto3.client("cloudformation", region_name="us-west-1")
    template_json = json.dumps(subnet_template)
    stack_name = str(uuid4())[0:6]
    cf.create_stack(
        StackName=stack_name,
        TemplateBody=template_json,
        Parameters=[
            {"ParameterKey": "EndpointSubnetId", "ParameterValue": subnet1.id},
            {"ParameterKey": "EndpointVpcId", "ParameterValue": vpc.id},
            {"ParameterKey": "EndpointServiceName", "ParameterValue": "serv_name"},
        ],
    )
    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    assert len(resources) == 1
    assert resources[0]["LogicalResourceId"] == "GwlbVpcEndpoint"
    vpc_endpoint_id = resources[0]["PhysicalResourceId"]

    outputs = cf.describe_stacks(StackName=stack_name)["Stacks"][0]["Outputs"]
    assert len(outputs) == 1
    assert outputs[0] == {
        "OutputKey": "EndpointId",
        "OutputValue": vpc_endpoint_id,
        "Description": "Id of the endpoint created",
    }

    endpoint = ec2_client.describe_vpc_endpoints(VpcEndpointIds=[vpc_endpoint_id])[
        "VpcEndpoints"
    ][0]
    assert endpoint["VpcId"] == vpc.id
    assert endpoint["ServiceName"] == "serv_name"
    assert endpoint["State"] == "available"
    assert endpoint["SubnetIds"] == [subnet1.id]
    assert endpoint["VpcEndpointType"] == "GatewayLoadBalancer"


@mock_aws
def test_launch_template_create():
    cf = boto3.client("cloudformation", region_name="us-west-1")
    ec2 = boto3.client("ec2", region_name="us-west-1")

    launch_template_name = str(uuid4())[0:6]
    logical_id = str(uuid4())[0:6]
    stack_name = str(uuid4())[0:6]

    lt_tags = [{"Key": "lt-tag-key", "Value": "lt-tag-value"}]

    template_json = json.dumps(
        {
            "AWSTemplateFormatVersion": "2010-09-09",
            "Resources": {
                logical_id: {
                    "Type": "AWS::EC2::LaunchTemplate",
                    "Properties": {
                        "LaunchTemplateName": launch_template_name,
                        "VersionDescription": "some template",
                        "LaunchTemplateData": {
                            "TagSpecifications": [
                                {
                                    "ResourceType": "instance",
                                    "Tags": [
                                        {"Key": "i-tag-key", "Value": "i-tag-value"}
                                    ],
                                }
                            ]
                        },
                        "TagSpecifications": [
                            {
                                "ResourceType": "launch-template",
                                "Tags": lt_tags,
                            }
                        ],
                    },
                }
            },
            "Outputs": {
                "LaunchTemplateId": {
                    "Description": "The ID of the created launch template",
                    "Value": {"Ref": logical_id},
                },
            },
        }
    )

    cf.create_stack(StackName=stack_name, TemplateBody=template_json)

    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    assert len(resources) == 1
    assert resources[0]["LogicalResourceId"] == logical_id
    launch_template_id = resources[0]["PhysicalResourceId"]

    outputs = cf.describe_stacks(StackName=stack_name)["Stacks"][0]["Outputs"]
    assert len(outputs) == 1
    assert outputs[0] == {
        "OutputKey": "LaunchTemplateId",
        "OutputValue": launch_template_id,
        "Description": "The ID of the created launch template",
    }

    launch_template = ec2.describe_launch_templates(
        LaunchTemplateNames=[launch_template_name]
    )["LaunchTemplates"][0]
    assert launch_template["LaunchTemplateName"] == launch_template_name
    assert launch_template["LaunchTemplateId"] == launch_template_id
    assert launch_template["Tags"] == lt_tags

    launch_template_version = ec2.describe_launch_template_versions(
        LaunchTemplateName=launch_template_name
    )["LaunchTemplateVersions"][0]
    assert launch_template_version["LaunchTemplateName"] == launch_template_name
    assert launch_template_version["LaunchTemplateId"] == launch_template_id
    assert (
        launch_template_version["LaunchTemplateData"]["TagSpecifications"][0][
            "ResourceType"
        ]
        == "instance"
    )


@mock_aws
def test_launch_template_update():
    cf = boto3.client("cloudformation", region_name="us-west-1")
    ec2 = boto3.client("ec2", region_name="us-west-1")

    launch_template_name = str(uuid4())[0:6]
    logical_id = str(uuid4())[0:6]
    stack_name = str(uuid4())[0:6]

    template_json = json.dumps(
        {
            "AWSTemplateFormatVersion": "2010-09-09",
            "Resources": {
                logical_id: {
                    "Type": "AWS::EC2::LaunchTemplate",
                    "Properties": {
                        "LaunchTemplateName": launch_template_name,
                        "VersionDescription": "some template",
                        "LaunchTemplateData": {"UserData": ""},
                    },
                }
            },
        }
    )

    cf.create_stack(StackName=stack_name, TemplateBody=template_json)

    template_json = json.dumps(
        {
            "AWSTemplateFormatVersion": "2010-09-09",
            "Resources": {
                logical_id: {
                    "Type": "AWS::EC2::LaunchTemplate",
                    "Properties": {
                        "LaunchTemplateName": launch_template_name,
                        "VersionDescription": "a better template",
                        "LaunchTemplateData": {"UserData": ""},
                    },
                }
            },
        }
    )

    cf.update_stack(StackName=stack_name, TemplateBody=template_json)

    resources = cf.list_stack_resources(StackName=stack_name)["StackResourceSummaries"]
    assert len(resources) == 1

    launch_template_versions = ec2.describe_launch_template_versions(
        LaunchTemplateName=launch_template_name
    )["LaunchTemplateVersions"]
    assert len(launch_template_versions) == 2
    assert launch_template_versions[0]["VersionDescription"] == "some template"
    assert launch_template_versions[1]["VersionDescription"] == "a better template"


@mock_aws
def test_launch_template_delete():
    cf = boto3.client("cloudformation", region_name="us-west-1")
    ec2 = boto3.client("ec2", region_name="us-west-1")

    launch_template_name = str(uuid4())[0:6]
    logical_id = str(uuid4())[0:6]
    stack_name = str(uuid4())[0:6]

    template_json = json.dumps(
        {
            "AWSTemplateFormatVersion": "2010-09-09",
            "Resources": {
                logical_id: {
                    "Type": "AWS::EC2::LaunchTemplate",
                    "Properties": {
                        "LaunchTemplateName": launch_template_name,
                        "VersionDescription": "some template",
                        "LaunchTemplateData": {"UserData": ""},
                    },
                }
            },
        }
    )
    cf.create_stack(StackName=stack_name, TemplateBody=template_json)

    cf.delete_stack(StackName=stack_name)

    with pytest.raises(ClientError) as exc:
        ec2.describe_launch_templates(LaunchTemplateNames=[launch_template_name])
    err = exc.value.response["Error"]
    assert err["Code"] == "InvalidLaunchTemplateName.NotFoundException"
    assert (
        err["Message"]
        == "At least one of the launch templates specified in the request does not exist."
    )