package ec2_test

import (
	"errors"
	"fmt"
	"regexp"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
	sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
	"github.com/hashicorp/terraform-provider-aws/internal/acctest"
	"github.com/hashicorp/terraform-provider-aws/internal/conns"
	tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2"
	"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)

func TestAccTransitGateway_serial(t *testing.T) {
	testCases := map[string]map[string]func(t *testing.T){
		"Connect": {
			"basic":      testAccTransitGatewayConnect_basic,
			"disappears": testAccTransitGatewayConnect_disappears,
			"Tags":       testAccTransitGatewayConnect_tags,
			"TransitGatewayDefaultRouteTableAssociation":                       testAccTransitGatewayConnect_TransitGatewayDefaultRouteTableAssociation,
			"TransitGatewayDefaultRouteTableAssociationAndPropagationDisabled": testAccTransitGatewayConnect_TransitGatewayDefaultRouteTableAssociationAndPropagationDisabled,
			"TransitGatewayDefaultRouteTablePropagation":                       testAccTransitGatewayConnect_TransitGatewayDefaultRouteTablePropagation,
		},
		"ConnectPeer": {
			"basic":                 testAccTransitGatewayConnectPeer_basic,
			"disappears":            testAccTransitGatewayConnectPeer_disappears,
			"BgpAsn":                testAccTransitGatewayConnectPeer_bgpASN,
			"InsideCidrBlocks":      testAccTransitGatewayConnectPeer_insideCIDRBlocks,
			"Tags":                  testAccTransitGatewayConnectPeer_tags,
			"TransitGatewayAddress": testAccTransitGatewayConnectPeer_TransitGatewayAddress,
		},
		"Gateway": {
			"basic":                       testAccTransitGateway_basic,
			"disappears":                  testAccTransitGateway_disappears,
			"AmazonSideASN":               testAccTransitGateway_AmazonSideASN,
			"AutoAcceptSharedAttachments": testAccTransitGateway_AutoAcceptSharedAttachments,
			"CidrBlocks":                  testAccTransitGateway_cidrBlocks,
			"DefaultRouteTableAssociationAndPropagationDisabled": testAccTransitGateway_DefaultRouteTableAssociationAndPropagationDisabled,
			"DefaultRouteTableAssociation":                       testAccTransitGateway_DefaultRouteTableAssociation,
			"DefaultRouteTablePropagation":                       testAccTransitGateway_DefaultRouteTablePropagation,
			"Description":                                        testAccTransitGateway_Description,
			"DnsSupport":                                         testAccTransitGateway_DNSSupport,
			"Tags":                                               testAccTransitGateway_Tags,
			"VpnEcmpSupport":                                     testAccTransitGateway_VPNECMPSupport,
		},
		"MulticastDomain": {
			"basic":         testAccTransitGatewayMulticastDomain_basic,
			"disappears":    testAccTransitGatewayMulticastDomain_disappears,
			"tags":          testAccTransitGatewayMulticastDomain_tags,
			"IGMPv2Support": testAccTransitGatewayMulticastDomain_igmpv2Support,
		},
		"MulticastDomainAssociation": {
			"basic":            testAccTransitGatewayMulticastDomainAssociation_basic,
			"disappears":       testAccTransitGatewayMulticastDomainAssociation_disappears,
			"DomainDisappears": testAccTransitGatewayMulticastDomainAssociation_Disappears_domain,
			"TwoAssociations":  testAccTransitGatewayMulticastDomainAssociation_twoAssociations,
		},
		"MulticastGroupMember": {
			"basic":            testAccTransitGatewayMulticastGroupMember_basic,
			"disappears":       testAccTransitGatewayMulticastGroupMember_disappears,
			"DomainDisappears": testAccTransitGatewayMulticastGroupMember_Disappears_domain,
			"TwoMembers":       testAccTransitGatewayMulticastGroupMember_twoMembers,
		},
		"MulticastGroupSource": {
			"basic":            testAccTransitGatewayMulticastGroupSource_basic,
			"disappears":       testAccTransitGatewayMulticastGroupSource_disappears,
			"DomainDisappears": testAccTransitGatewayMulticastGroupSource_Disappears_domain,
		},
		"PeeringAttachment": {
			"basic":            testAccTransitGatewayPeeringAttachment_basic,
			"disappears":       testAccTransitGatewayPeeringAttachment_disappears,
			"DifferentAccount": testAccTransitGatewayPeeringAttachment_differentAccount,
			"Tags":             testAccTransitGatewayPeeringAttachment_Tags,
		},
		"PeeringAttachmentAccepter": {
			"basic":            testAccTransitGatewayPeeringAttachmentAccepter_basic,
			"DifferentAccount": testAccTransitGatewayPeeringAttachmentAccepter_differentAccount,
			"Tags":             testAccTransitGatewayPeeringAttachmentAccepter_Tags,
		},
		"PolicyTable": {
			"basic":                    testAccTransitGatewayPolicyTable_basic,
			"disappears":               testAccTransitGatewayPolicyTable_disappears,
			"disappearsTransitGateway": testAccTransitGatewayPolicyTable_disappears_TransitGateway,
			"Tags":                     testAccTransitGatewayPolicyTable_Tags,
		},
		"PolicyTableAssociation": {
			"basic":      testAccTransitGatewayPolicyTableAssociation_basic,
			"disappears": testAccTransitGatewayPolicyTableAssociation_disappears,
		},
		"PrefixListReference": {
			"basic":                      testAccTransitGatewayPrefixListReference_basic,
			"disappears":                 testAccTransitGatewayPrefixListReference_disappears,
			"disappearsTransitGateway":   testAccTransitGatewayPrefixListReference_disappears_TransitGateway,
			"TransitGatewayAttachmentId": testAccTransitGatewayPrefixListReference_TransitGatewayAttachmentID,
		},
		"Route": {
			"basic":                              testAccTransitGatewayRoute_basic,
			"basicIpv6":                          testAccTransitGatewayRoute_basic_ipv6,
			"blackhole":                          testAccTransitGatewayRoute_blackhole,
			"disappears":                         testAccTransitGatewayRoute_disappears,
			"disappearsTransitGatewayAttachment": testAccTransitGatewayRoute_disappears_TransitGatewayAttachment,
		},
		"RouteTable": {
			"basic":                    testAccTransitGatewayRouteTable_basic,
			"disappears":               testAccTransitGatewayRouteTable_disappears,
			"disappearsTransitGateway": testAccTransitGatewayRouteTable_disappears_TransitGateway,
			"Tags":                     testAccTransitGatewayRouteTable_Tags,
		},
		"RouteTableAssociation": {
			"basic":      testAccTransitGatewayRouteTableAssociation_basic,
			"disappears": testAccTransitGatewayRouteTableAssociation_disappears,
		},
		"RouteTablePropagation": {
			"basic":      testAccTransitGatewayRouteTablePropagation_basic,
			"disappears": testAccTransitGatewayRouteTablePropagation_disappears,
		},
		"VpcAttachment": {
			"basic":                testAccTransitGatewayVPCAttachment_basic,
			"disappears":           testAccTransitGatewayVPCAttachment_disappears,
			"ApplianceModeSupport": testAccTransitGatewayVPCAttachment_ApplianceModeSupport,
			"DnsSupport":           testAccTransitGatewayVPCAttachment_DNSSupport,
			"Ipv6Support":          testAccTransitGatewayVPCAttachment_IPv6Support,
			"SharedTransitGateway": testAccTransitGatewayVPCAttachment_SharedTransitGateway,
			"SubnetIds":            testAccTransitGatewayVPCAttachment_SubnetIDs,
			"Tags":                 testAccTransitGatewayVPCAttachment_Tags,
			"TransitGatewayDefaultRouteTableAssociation":                       testAccTransitGatewayVPCAttachment_TransitGatewayDefaultRouteTableAssociation,
			"TransitGatewayDefaultRouteTableAssociationAndPropagationDisabled": testAccTransitGatewayVPCAttachment_TransitGatewayDefaultRouteTableAssociationAndPropagationDisabled,
			"TransitGatewayDefaultRouteTablePropagation":                       testAccTransitGatewayVPCAttachment_TransitGatewayDefaultRouteTablePropagation,
		},
		"VpcAttachmentAccepter": {
			"basic": testAccTransitGatewayVPCAttachmentAccepter_basic,
			"Tags":  testAccTransitGatewayVPCAttachmentAccepter_Tags,
			"TransitGatewayDefaultRouteTableAssociationAndPropagation": testAccTransitGatewayVPCAttachmentAccepter_TransitGatewayDefaultRouteTableAssociationAndPropagation,
		},
	}

	for group, m := range testCases {
		m := m
		t.Run(group, func(t *testing.T) {
			for name, tc := range m {
				tc := tc
				t.Run(name, func(t *testing.T) {
					tc(t)
				})
			}
		})
	}
}

func testAccTransitGateway_basic(t *testing.T) {
	var transitGateway1 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_basic(),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "amazon_side_asn", "64512"),
					acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`transit-gateway/tgw-.+`)),
					resource.TestCheckResourceAttrSet(resourceName, "association_default_route_table_id"),
					resource.TestCheckResourceAttr(resourceName, "auto_accept_shared_attachments", ec2.AutoAcceptSharedAttachmentsValueDisable),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_association", ec2.DefaultRouteTableAssociationValueEnable),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_propagation", ec2.DefaultRouteTablePropagationValueEnable),
					resource.TestCheckResourceAttr(resourceName, "description", ""),
					resource.TestCheckResourceAttr(resourceName, "dns_support", ec2.DnsSupportValueEnable),
					resource.TestCheckResourceAttr(resourceName, "multicast_support", ec2.MulticastSupportValueDisable),
					acctest.CheckResourceAttrAccountID(resourceName, "owner_id"),
					resource.TestCheckResourceAttrSet(resourceName, "propagation_default_route_table_id"),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
					resource.TestCheckResourceAttr(resourceName, "vpn_ecmp_support", ec2.VpnEcmpSupportValueEnable),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccTransitGateway_disappears(t *testing.T) {
	var transitGateway1 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_basic(),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGateway(), resourceName),
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func testAccTransitGateway_AmazonSideASN(t *testing.T) {
	var transitGateway1, transitGateway2 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_amazonSideASN(rName, 64513),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "amazon_side_asn", "64513"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_amazonSideASN(rName, 64514),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayNotRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "amazon_side_asn", "64514"),
				),
			},
		},
	})
}

func testAccTransitGateway_AutoAcceptSharedAttachments(t *testing.T) {
	var transitGateway1, transitGateway2 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_autoAcceptSharedAttachments(rName, ec2.AutoAcceptSharedAttachmentsValueEnable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "auto_accept_shared_attachments", ec2.AutoAcceptSharedAttachmentsValueEnable),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_autoAcceptSharedAttachments(rName, ec2.AutoAcceptSharedAttachmentsValueDisable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayNotRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "auto_accept_shared_attachments", ec2.AutoAcceptSharedAttachmentsValueDisable),
				),
			},
		},
	})
}

func testAccTransitGateway_cidrBlocks(t *testing.T) {
	var v1, v2, v3 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_cidrBlocks2(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &v1),
					resource.TestCheckResourceAttr(resourceName, "transit_gateway_cidr_blocks.#", "2"),
					resource.TestCheckTypeSetElemAttr(resourceName, "transit_gateway_cidr_blocks.*", "10.120.0.0/24"),
					resource.TestCheckTypeSetElemAttr(resourceName, "transit_gateway_cidr_blocks.*", "2001:1234:1234::/64"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_cidrBlocks1(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &v2),
					testAccCheckTransitGatewayNotRecreated(&v1, &v2),
					resource.TestCheckResourceAttr(resourceName, "transit_gateway_cidr_blocks.#", "1"),
					resource.TestCheckTypeSetElemAttr(resourceName, "transit_gateway_cidr_blocks.*", "10.120.0.0/24"),
				),
			},
			{
				Config: testAccTransitGatewayConfig_cidrBlocks2(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &v3),
					testAccCheckTransitGatewayNotRecreated(&v2, &v3),
					resource.TestCheckResourceAttr(resourceName, "transit_gateway_cidr_blocks.#", "2"),
					resource.TestCheckTypeSetElemAttr(resourceName, "transit_gateway_cidr_blocks.*", "10.120.0.0/24"),
					resource.TestCheckTypeSetElemAttr(resourceName, "transit_gateway_cidr_blocks.*", "2001:1234:1234::/64"),
				),
			},
		},
	})
}

func testAccTransitGateway_DefaultRouteTableAssociationAndPropagationDisabled(t *testing.T) {
	var transitGateway1 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_defaultRouteTableAssociationAndPropagationDisabled(rName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_association", ec2.DefaultRouteTableAssociationValueDisable),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_propagation", ec2.DefaultRouteTablePropagationValueDisable),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func testAccTransitGateway_DefaultRouteTableAssociation(t *testing.T) {
	var transitGateway1, transitGateway2, transitGateway3 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_defaultRouteTableAssociation(rName, ec2.DefaultRouteTableAssociationValueDisable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_association", ec2.DefaultRouteTableAssociationValueDisable),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_defaultRouteTableAssociation(rName, ec2.DefaultRouteTableAssociationValueEnable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_association", ec2.DefaultRouteTableAssociationValueEnable),
				),
			},
			{
				Config: testAccTransitGatewayConfig_defaultRouteTableAssociation(rName, ec2.DefaultRouteTableAssociationValueDisable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway3),
					testAccCheckTransitGatewayNotRecreated(&transitGateway2, &transitGateway3),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_association", ec2.DefaultRouteTableAssociationValueDisable),
				),
			},
		},
	})
}

func testAccTransitGateway_DefaultRouteTablePropagation(t *testing.T) {
	var transitGateway1, transitGateway2, transitGateway3 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_defaultRouteTablePropagation(rName, ec2.DefaultRouteTablePropagationValueDisable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_propagation", ec2.DefaultRouteTablePropagationValueDisable),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_defaultRouteTablePropagation(rName, ec2.DefaultRouteTablePropagationValueEnable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_propagation", ec2.DefaultRouteTablePropagationValueEnable),
				),
			},
			{
				Config: testAccTransitGatewayConfig_defaultRouteTablePropagation(rName, ec2.DefaultRouteTablePropagationValueDisable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway3),
					testAccCheckTransitGatewayNotRecreated(&transitGateway2, &transitGateway3),
					resource.TestCheckResourceAttr(resourceName, "default_route_table_propagation", ec2.DefaultRouteTablePropagationValueDisable),
				),
			},
		},
	})
}

func testAccTransitGateway_DNSSupport(t *testing.T) {
	var transitGateway1, transitGateway2 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_dnsSupport(rName, ec2.DnsSupportValueDisable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "dns_support", ec2.DnsSupportValueDisable),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_dnsSupport(rName, ec2.DnsSupportValueEnable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayNotRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "dns_support", ec2.DnsSupportValueEnable),
				),
			},
		},
	})
}

func testAccTransitGateway_VPNECMPSupport(t *testing.T) {
	var transitGateway1, transitGateway2 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_vpnECMPSupport(rName, ec2.VpnEcmpSupportValueDisable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "vpn_ecmp_support", ec2.VpnEcmpSupportValueDisable),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_vpnECMPSupport(rName, ec2.VpnEcmpSupportValueEnable),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayNotRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "vpn_ecmp_support", ec2.VpnEcmpSupportValueEnable),
				),
			},
		},
	})
}

func testAccTransitGateway_Description(t *testing.T) {
	var transitGateway1, transitGateway2 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"
	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_description(rName, "description1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "description", "description1"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_description(rName, "description2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayNotRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "description", "description2"),
				),
			},
		},
	})
}

func testAccTransitGateway_Tags(t *testing.T) {
	var transitGateway1, transitGateway2, transitGateway3 ec2.TransitGateway
	resourceName := "aws_ec2_transit_gateway.test"

	resource.Test(t, resource.TestCase{
		PreCheck:                 func() { acctest.PreCheck(t); testAccPreCheckTransitGateway(t) },
		ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
		CheckDestroy:             testAccCheckTransitGatewayDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccTransitGatewayConfig_tags1("key1", "value1"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway1),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"),
				),
			},
			{
				ResourceName:      resourceName,
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				Config: testAccTransitGatewayConfig_tags2("key1", "value1updated", "key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway2),
					testAccCheckTransitGatewayNotRecreated(&transitGateway1, &transitGateway2),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
					resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"),
					resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
				),
			},
			{
				Config: testAccTransitGatewayConfig_tags1("key2", "value2"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckTransitGatewayExists(resourceName, &transitGateway3),
					testAccCheckTransitGatewayNotRecreated(&transitGateway2, &transitGateway3),
					resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
				),
			},
		},
	})
}

func testAccCheckTransitGatewayExists(n string, v *ec2.TransitGateway) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No EC2 Transit Gateway ID is set")
		}

		conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

		output, err := tfec2.FindTransitGatewayByID(conn, rs.Primary.ID)

		if err != nil {
			return err
		}

		*v = *output

		return nil
	}
}

func testAccCheckTransitGatewayDestroy(s *terraform.State) error {
	conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_ec2_transit_gateway" {
			continue
		}

		_, err := tfec2.FindTransitGatewayByID(conn, rs.Primary.ID)

		if tfresource.NotFound(err) {
			continue
		}

		if err != nil {
			return err
		}

		return fmt.Errorf("EC2 Transit Gateway %s still exists", rs.Primary.ID)
	}

	return nil
}

func testAccCheckTransitGatewayNotRecreated(i, j *ec2.TransitGateway) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		if aws.StringValue(i.TransitGatewayId) != aws.StringValue(j.TransitGatewayId) {
			return errors.New("EC2 Transit Gateway was recreated")
		}

		return nil
	}
}

func testAccCheckTransitGatewayRecreated(i, j *ec2.TransitGateway) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		if aws.StringValue(i.TransitGatewayId) == aws.StringValue(j.TransitGatewayId) {
			return errors.New("EC2 Transit Gateway was not recreated")
		}

		return nil
	}
}

func testAccCheckTransitGatewayAssociationDefaultRouteTableAttachmentAssociated(transitGateway *ec2.TransitGateway, transitGatewayAttachment interface{}) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		transitGatewayRouteTableID := aws.StringValue(transitGateway.Options.AssociationDefaultRouteTableId)

		if transitGatewayRouteTableID == "" {
			return errors.New("EC2 Transit Gateway Association Default Route Table empty")
		}

		conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

		var transitGatewayAttachmentID string
		switch transitGatewayAttachment := transitGatewayAttachment.(type) {
		case *ec2.TransitGatewayVpcAttachment:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		case *ec2.TransitGatewayConnect:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		}

		_, err := tfec2.FindTransitGatewayRouteTableAssociationByTwoPartKey(conn, transitGatewayRouteTableID, transitGatewayAttachmentID)

		if tfresource.NotFound(err) {
			return errors.New("EC2 Transit Gateway Route Table Association not found")
		}

		return err
	}
}

func testAccCheckTransitGatewayAssociationDefaultRouteTableAttachmentNotAssociated(transitGateway *ec2.TransitGateway, transitGatewayAttachment interface{}) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		transitGatewayRouteTableID := aws.StringValue(transitGateway.Options.AssociationDefaultRouteTableId)

		if transitGatewayRouteTableID == "" {
			return nil
		}

		conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

		var transitGatewayAttachmentID string
		switch transitGatewayAttachment := transitGatewayAttachment.(type) {
		case *ec2.TransitGatewayVpcAttachment:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		case *ec2.TransitGatewayConnect:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		}

		_, err := tfec2.FindTransitGatewayRouteTableAssociationByTwoPartKey(conn, transitGatewayRouteTableID, transitGatewayAttachmentID)

		if tfresource.NotFound(err) {
			return nil
		}

		if err != nil {
			return err
		}

		return errors.New("EC2 Transit Gateway Route Table Association found")
	}
}

func testAccCheckTransitGatewayPropagationDefaultRouteTableAttachmentPropagated(transitGateway *ec2.TransitGateway, transitGatewayAttachment interface{}) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		transitGatewayRouteTableID := aws.StringValue(transitGateway.Options.PropagationDefaultRouteTableId)

		if transitGatewayRouteTableID == "" {
			return errors.New("EC2 Transit Gateway Propagation Default Route Table empty")
		}

		conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

		var transitGatewayAttachmentID string
		switch transitGatewayAttachment := transitGatewayAttachment.(type) {
		case *ec2.TransitGatewayVpcAttachment:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		case *ec2.TransitGatewayConnect:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		}

		_, err := tfec2.FindTransitGatewayRouteTablePropagationByTwoPartKey(conn, transitGatewayRouteTableID, transitGatewayAttachmentID)

		if tfresource.NotFound(err) {
			return errors.New("EC2 Transit Gateway Route Table Propagation not enabled")
		}

		return err
	}
}

func testAccCheckTransitGatewayPropagationDefaultRouteTableAttachmentNotPropagated(transitGateway *ec2.TransitGateway, transitGatewayAttachment interface{}) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		transitGatewayRouteTableID := aws.StringValue(transitGateway.Options.PropagationDefaultRouteTableId)

		if transitGatewayRouteTableID == "" {
			return nil
		}

		conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

		var transitGatewayAttachmentID string
		switch transitGatewayAttachment := transitGatewayAttachment.(type) {
		case *ec2.TransitGatewayVpcAttachment:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		case *ec2.TransitGatewayConnect:
			transitGatewayAttachmentID = aws.StringValue(transitGatewayAttachment.TransitGatewayAttachmentId)
		}

		_, err := tfec2.FindTransitGatewayRouteTablePropagationByTwoPartKey(conn, transitGatewayRouteTableID, transitGatewayAttachmentID)

		if tfresource.NotFound(err) {
			return nil
		}

		if err != nil {
			return err
		}

		return errors.New("EC2 Transit Gateway Route Table Propagation enabled")
	}
}

func testAccPreCheckTransitGateway(t *testing.T) {
	conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

	input := &ec2.DescribeTransitGatewaysInput{
		MaxResults: aws.Int64(5),
	}

	_, err := conn.DescribeTransitGateways(input)

	if acctest.PreCheckSkipError(err) || tfawserr.ErrCodeEquals(err, "InvalidAction") {
		t.Skipf("skipping acceptance testing: %s", err)
	}

	if err != nil {
		t.Fatalf("unexpected PreCheck error: %s", err)
	}
}

func testAccPreCheckTransitGatewayConnect(t *testing.T) {
	conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

	input := &ec2.DescribeTransitGatewayConnectsInput{
		MaxResults: aws.Int64(5),
	}

	_, err := conn.DescribeTransitGatewayConnects(input)

	if acctest.PreCheckSkipError(err) || tfawserr.ErrCodeEquals(err, "InvalidAction") {
		t.Skipf("skipping acceptance testing: %s", err)
	}

	if err != nil {
		t.Fatalf("unexpected PreCheck error: %s", err)
	}
}

func testAccPreCheckTransitGatewayVPCAttachment(t *testing.T) {
	conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn()

	input := &ec2.DescribeTransitGatewayVpcAttachmentsInput{
		MaxResults: aws.Int64(5),
	}

	_, err := conn.DescribeTransitGatewayVpcAttachments(input)

	if acctest.PreCheckSkipError(err) || tfawserr.ErrCodeEquals(err, "InvalidAction") {
		t.Skipf("skipping acceptance testing: %s", err)
	}

	if err != nil {
		t.Fatalf("unexpected PreCheck error: %s", err)
	}
}

func testAccTransitGatewayConfig_basic() string {
	return `
resource "aws_ec2_transit_gateway" "test" {}
`
}

func testAccTransitGatewayConfig_amazonSideASN(rName string, amazonSideASN int) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  amazon_side_asn = %[2]d

  tags = {
    Name = %[1]q
  }
}
`, rName, amazonSideASN)
}

func testAccTransitGatewayConfig_autoAcceptSharedAttachments(rName, autoAcceptSharedAttachments string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  auto_accept_shared_attachments = %[2]q

  tags = {
    Name = %[1]q
  }
}
`, rName, autoAcceptSharedAttachments)
}

func testAccTransitGatewayConfig_defaultRouteTableAssociationAndPropagationDisabled(rName string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"

  tags = {
    Name = %[1]q
  }
}
`, rName)
}

func testAccTransitGatewayConfig_defaultRouteTableAssociation(rName, defaultRouteTableAssociation string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  default_route_table_association = %[2]q

  tags = {
    Name = %[1]q
  }
}
`, rName, defaultRouteTableAssociation)
}

func testAccTransitGatewayConfig_defaultRouteTablePropagation(rName, defaultRouteTablePropagation string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  default_route_table_propagation = %[2]q

  tags = {
    Name = %[1]q
  }
}
`, rName, defaultRouteTablePropagation)
}

func testAccTransitGatewayConfig_dnsSupport(rName, dnsSupport string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  dns_support = %[2]q

  tags = {
    Name = %[1]q
  }
}
`, rName, dnsSupport)
}

func testAccTransitGatewayConfig_vpnECMPSupport(rName, vpnEcmpSupport string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  vpn_ecmp_support = %[2]q

  tags = {
    Name = %[1]q
  }
}
`, rName, vpnEcmpSupport)
}

func testAccTransitGatewayConfig_description(rName, description string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  description = %[2]q

  tags = {
    Name = %[1]q
  }
}
`, rName, description)
}

func testAccTransitGatewayConfig_tags1(tagKey1, tagValue1 string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  tags = {
    %[1]q = %[2]q
  }
}
`, tagKey1, tagValue1)
}

func testAccTransitGatewayConfig_tags2(tagKey1, tagValue1, tagKey2, tagValue2 string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  tags = {
    %[1]q = %[2]q
    %[3]q = %[4]q
  }
}
`, tagKey1, tagValue1, tagKey2, tagValue2)
}

func testAccTransitGatewayConfig_cidrBlocks1(rName string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  transit_gateway_cidr_blocks = ["10.120.0.0/24"]

  tags = {
    Name = %[1]q
  }
}
`, rName)
}

func testAccTransitGatewayConfig_cidrBlocks2(rName string) string {
	return fmt.Sprintf(`
resource "aws_ec2_transit_gateway" "test" {
  transit_gateway_cidr_blocks = ["10.120.0.0/24", "2001:1234:1234::/64"]

  tags = {
    Name = %[1]q
  }
}
`, rName)
}
