package fastly

import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"testing"
	"text/template"

	"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"

	gofastly "github.com/fastly/go-fastly/v17/fastly"
)

func init() {
	resource.AddTestSweepers("fastly_service_vcl", &resource.Sweeper{
		Name: "fastly_service_vcl",
		F:    testSweepServices,
	})
}

const defaultTTL = 3400

func TestAccFastlyServiceVCL_updateDomain(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	nameUpdate := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domainName1 := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	domainName2 := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	domainName3 := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfig(name, domainName1),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceVCLAttributes(&service, name, []string{domainName1}),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "name", name),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "1"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "domain.#", "1"),
				),
			},

			{
				Config: testAccServiceVCLConfigDomainAdd(nameUpdate, domainName1, domainName2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceVCLAttributes(&service, nameUpdate, []string{domainName1, domainName2}),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "name", nameUpdate),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "2"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "domain.#", "2"),
				),
			},

			{
				Config: testAccServiceVCLConfigDomainUpdateComment(nameUpdate, domainName1, domainName2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceVCLAttributes(&service, nameUpdate, []string{domainName1, domainName2}),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "3"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "domain.#", "2"),
				),
			},

			{
				Config: testAccServiceVCLConfigDomainUpdate(nameUpdate, domainName1, domainName3),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceVCLAttributes(&service, nameUpdate, []string{domainName1, domainName3}),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "4"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "domain.#", "2"),
				),
			},
		},
	})
}

func TestAccFastlyServiceVCL_updateBackend(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	backendName := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
	backendName2 := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfigBackend(name, domain, backendName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName}, 1),
				),
			},

			{
				Config: testAccServiceVCLConfigBackendUpdate(name, domain, backendName, backendName2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName, backendName2}, 2),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "2"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "backend.#", "2"),
				),
			},
		},
	})
}

// TestAccFastlyServiceVCL_activateNewVersionExternally tests whether things break when a new version is cloned and
// activated outside of Terraform. There has been a bug where the version used for reading the state, and the version
// that gets cloned in order to make updates, are different when a new version is activated externally. In this case, a
// 409 conflict error is produced by this test because it reads the new version when making a plan, plans to re-add in
// the deleted backend, but clones the original version which still had the backend and fails with a conflict.
func TestAccFastlyServiceVCL_activateNewVersionExternally(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	backendName := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
	backendName2 := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))

	activateNewVersion := func(*terraform.State) error {
		conn := testAccProvider.Meta().(*APIClient).conn
		version, err := conn.CloneVersion(context.TODO(), &gofastly.CloneVersionInput{
			ServiceID:      gofastly.ToValue(service.ServiceID),
			ServiceVersion: gofastly.ToValue(service.ActiveVersion.Number),
		})
		if err != nil {
			return err
		}

		err = conn.DeleteBackend(context.TODO(), &gofastly.DeleteBackendInput{
			ServiceID:      gofastly.ToValue(service.ServiceID),
			ServiceVersion: gofastly.ToValue(version.Number),
			Name:           "tf-test-backend",
		})
		if err != nil {
			return err
		}

		_, err = conn.ActivateVersion(context.TODO(), &gofastly.ActivateVersionInput{
			ServiceID:      gofastly.ToValue(service.ServiceID),
			ServiceVersion: gofastly.ToValue(version.Number),
		})
		return err
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfigBackendUpdate(name, domain, backendName, backendName2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName, backendName2}, 1),
					activateNewVersion,
				),
				ExpectNonEmptyPlan: true,
			},

			{
				Config: testAccServiceVCLConfigBackendUpdate(name, domain, backendName, backendName2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName, backendName2}, 3),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "3"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "backend.#", "2"),
				),
			},
		},
	})
}

func TestAccFastlyServiceVCL_updateInvalidBackend(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	badBackendName := fmt.Sprintf("%s.aws.amazon.com.", acctest.RandString(3))
	backendName := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
	backendName2 := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config:      testAccServiceVCLConfigBackend(name, domain, badBackendName),
				ExpectError: regexp.MustCompile("Bad Request"),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{}, 1),
				),
			},

			{
				Config: testAccServiceVCLConfigBackendUpdate(name, domain, backendName, backendName2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName, backendName2}, 1),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "1"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "backend.#", "2"),
				),
			},
		},
	})
}

func TestAccFastlyServiceVCL_createServiceWithStaticBackend(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	snippet1 := `
	backend F_httpbin_org {
		.always_use_host_header = true;
		.between_bytes_timeout = 1s;
		.connect_timeout = 1s;
		.dynamic = true;
		.first_byte_timeout = 1s;
		.host = "httpbin.org";
		.host_header = "httpbin.org";
		.max_connections = 200;
		.port = "443";
		.share_key = "foo";
		.ssl = true;
		.ssl_cert_hostname = "httpbin.org";
		.ssl_check_cert = always;
		.ssl_sni_hostname = "httpbin.org";
		.probe = {
			.dummy = true;
			.initial = 5;
			.request = "HEAD / HTTP/1.1"  "Host: httpbin.org" "Connection: close";
			.threshold = 1;
			.timeout = 2s;
			.window = 5;
		  }
	}
	`

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfigStaticBackend(name, domain, snippet1),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{}, 1),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "1"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "backend.#", "0"),
				),
			},
		},
	})
}

func TestAccFastlyServiceVCL_basic(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	updatedName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	comment := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	versionComment1 := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	versionComment2 := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domainName1 := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	domainName2 := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfigInitWithVersionComment(name, versionComment1, domainName1),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "name", name),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "comment", "Managed by Terraform"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "version_comment", versionComment1),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "active_version", "1"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "domain.#", "1"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "backend.#", "1"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "http3", "true"),
				),
			},
			{
				// Versionless service metadata is updated even when activation is disabled.
				Config: testAccServiceVCLConfigUpdateServiceMetadata(updatedName, comment, versionComment1, domainName1, false),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "name", updatedName),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "comment", comment),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "active_version", "1"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "cloned_version", "1"),
				),
			},
			{
				Config: testAccServiceVCLConfigBasicUpdate(name, comment, versionComment2, domainName2),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "name", name),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "comment", comment),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "version_comment", versionComment2),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "active_version", "2"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "domain.#", "1"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "backend.#", "1"),
					resource.TestCheckResourceAttr("fastly_service_vcl.foo", "http3", "true"),
				),
			},
			{
				ResourceName:      "fastly_service_vcl.foo",
				ImportState:       true,
				ImportStateVerify: true,
				// These attributes are not stored on the Fastly API and must be ignored.
				ImportStateVerifyIgnore: []string{"activate", "force_destroy", "imported", "stage"},
				ImportStateIdFunc: func(_ *terraform.State) (string, error) {
					return fmt.Sprintf("%s@2", gofastly.ToValue(service.ServiceID)), nil
				},
			},
		},
	})
}

// ServiceVCL_disappears – test that a non-empty plan is returned when a Fastly
// Service is destroyed outside of Terraform, and can no longer be found,
// correctly clearing the ID field and generating a new plan.
func TestAccFastlyServiceVCL_disappears(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domainName := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))

	testDestroy := func(*terraform.State) error {
		// reach out and DELETE the service
		conn := testAccProvider.Meta().(*APIClient).conn
		// deactivate active version to destroy
		_, err := conn.DeactivateVersion(context.TODO(), &gofastly.DeactivateVersionInput{
			ServiceID:      gofastly.ToValue(service.ServiceID),
			ServiceVersion: gofastly.ToValue(service.ActiveVersion.Number),
		})
		if err != nil {
			return err
		}

		// delete service
		return conn.DeleteService(context.TODO(), &gofastly.DeleteServiceInput{
			ServiceID: gofastly.ToValue(service.ServiceID),
		})
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfig(name, domainName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testDestroy,
				),
				ExpectNonEmptyPlan: true,
			},
		},
	})
}

func TestAccFastlyServiceVCL_stage(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	backendName1 := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
	backendName2 := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))
	backendName3 := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))

	type Config struct {
		Name     string
		Domain   string
		Backends []string
		Stage    bool
	}

	tmplText := `
resource "fastly_service_vcl" "foo" {
  name = "{{ .Name }}"

  {{ if .Stage }}
  activate = false
  stage = true
  {{ end }}

  domain {
    name    = "{{ .Domain }}"
    comment = "tf-testing-domain"
  }

  {{ range .Backends }}
  backend {
    address = "{{ . }}"
    name    = "tf-test backend {{ . }}"
  }
  {{ end }}

  force_destroy = true
}`

	tmpl, err := template.New("test").Parse(tmplText)
	if err != nil {
		t.Fatal(err)
	}

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: renderTestConfigTemplate(t, tmpl, Config{
					Name:     name,
					Domain:   domain,
					Backends: []string{backendName1},
				}),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName1}, 1),
				),
			},

			{
				Config: renderTestConfigTemplate(t, tmpl, Config{
					Name:     name,
					Domain:   domain,
					Backends: []string{backendName1, backendName2},
					Stage:    true,
				}),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName1, backendName2}, 2),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "1"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "staged_version", "2"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "backend.#", "2"),
				),
			},

			{
				Config: renderTestConfigTemplate(t, tmpl, Config{
					Name:     name,
					Domain:   domain,
					Backends: []string{backendName1, backendName2, backendName3},
					Stage:    true,
				}),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName1, backendName2, backendName3}, 2),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "1"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "staged_version", "2"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "backend.#", "3"),
				),
			},
		},
	})
}

func testAccCheckServiceExists(n string, service *gofastly.ServiceDetail) 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 Service ID is set")
		}

		conn := testAccProvider.Meta().(*APIClient).conn
		latest, err := conn.GetServiceDetails(context.TODO(), &gofastly.GetServiceDetailsInput{
			ServiceID: rs.Primary.ID,
		})
		if err != nil {
			return err
		}

		*service = *latest

		return nil
	}
}

func testAccCheckFastlyServiceVCLAttributes(service *gofastly.ServiceDetail, name string, domains []string) resource.TestCheckFunc {
	return func(_ *terraform.State) error {
		if gofastly.ToValue(service.Name) != name {
			return fmt.Errorf("bad name, expected (%s), got (%s)", name, gofastly.ToValue(service.Name))
		}

		conn := testAccProvider.Meta().(*APIClient).conn
		domainList, err := conn.ListDomains(context.TODO(), &gofastly.ListDomainsInput{
			ServiceID:      gofastly.ToValue(service.ServiceID),
			ServiceVersion: gofastly.ToValue(service.ActiveVersion.Number),
		})
		if err != nil {
			return fmt.Errorf("error looking up Domains for (%s), version (%v): %s", gofastly.ToValue(service.Name), gofastly.ToValue(service.ActiveVersion.Number), err)
		}

		expected := len(domains)
		for _, d := range domainList {
			for _, e := range domains {
				if gofastly.ToValue(d.Name) == e {
					expected--
				}
			}
		}

		if expected > 0 {
			return fmt.Errorf("domain count mismatch, expected: %#v, got: %#v", domains, domainList)
		}

		return nil
	}
}

func TestAccFastlyServiceVCL_defaultTTL(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	backendName := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			// service default is 3600
			{
				Config: testAccServiceVCLConfigBackend(name, domain, backendName),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName}, 1),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "default_ttl", "3600"),
				),
			},
			// update default TTL
			{
				Config: testAccServiceVCLConfigBackendTTL(name, domain, backendName, defaultTTL),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName}, 2),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "default_ttl", "3400"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "2"),
				),
			},
			// can set 0
			{
				Config: testAccServiceVCLConfigBackendTTL(name, domain, backendName, 0),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName}, 3),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "default_ttl", "0"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "3"),
				),
			},
		},
	})
}

func TestAccFastlyServiceVCL_defaultHost(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	defaultHost := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfigDefaultHost(name, domain, defaultHost),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "default_ttl", "3600"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "stale_if_error_ttl", "43200"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "default_host", defaultHost),
				),
			},
			// remove default_host
			{
				Config: testAccServiceVCLConfigDefaultHost(name, domain, ""),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "default_host", ""),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "active_version", "2"),
				),
			},
		},
	})
}

// TestAccFastlyServiceVCL_brokenSnippet tests that a service can still be updated after it has failed during an apply.
// This avoids a bug when activate=true, where setting an invalid snippet causes the resourceServiceUpdate function to
// return early before activating the version. This broke the assumption that cloned_version always tracks the active
// version when activate=true, and means that the version we read from, and the one we clone from in order to make changes,
// are different, meaning the plan is applied to a different version and 409 conflict errors can occur.
func TestAccFastlyServiceVCL_brokenSnippet(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.test", acctest.RandString(10))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfigBrokenSnippet(name, domain, "backend1", `if (req.url !~ "^/anything") {
                       set req.url = "/anything" req.url;
                     }`),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
				),
			},
			{
				Config: testAccServiceVCLConfigBrokenSnippet(name, domain, "backend2", `if (req.url !~ "^/anything") {
                       set req.url = "/anything" req.url
                     }`),
				ExpectError: regexp.MustCompile(`invalid configuration for Fastly Service`),
			},
			{
				Config: testAccServiceVCLConfigBrokenSnippet(name, domain, "backend2", `if (req.url !~ "^/anything") {
                       set req.url = "/anything" req.url;
                     }`),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
				),
			},
		},
	})
}

func TestAccFastlyServiceVCL_createZeroDefaultTTL(t *testing.T) {
	var service gofastly.ServiceDetail
	name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
	domain := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10))
	backendName := fmt.Sprintf("%s.aws.amazon.com", acctest.RandString(3))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck: func() {
			testAccPreCheck(t)
		},
		ProviderFactories: testAccProviders,
		CheckDestroy:      testAccCheckServiceVCLDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccServiceVCLConfigBackendZeroTTL(name, domain, backendName, 0),
				Check: resource.ComposeTestCheckFunc(
					testAccCheckServiceExists("fastly_service_vcl.foo", &service),
					testAccCheckFastlyServiceAttributesBackends(&service, name, []string{backendName}, 1),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "default_ttl", "0"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "stale_if_error", "true"),
					resource.TestCheckResourceAttr(
						"fastly_service_vcl.foo", "stale_if_error_ttl", "0"),
				),
			},
		},
	})
}

func testAccCheckServiceVCLDestroy(s *terraform.State) error {
	for _, rs := range s.RootModule().Resources {
		if rs.Type != "fastly_service_vcl" {
			continue
		}

		conn := testAccProvider.Meta().(*APIClient).conn
		l, err := conn.ListServices(context.TODO(), &gofastly.ListServicesInput{})
		if err != nil {
			return fmt.Errorf("error listing services when deleting Fastly Service (%s): %s", rs.Primary.ID, err)
		}

		for _, s := range l {
			if gofastly.ToValue(s.ServiceID) == rs.Primary.ID {
				// service still found
				return fmt.Errorf("tried deleting Service (%s), but was still found", rs.Primary.ID)
			}
		}
	}
	return nil
}

func testAccServiceVCLConfig(name, domain string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  force_destroy = true
}`, name, domain)
}

func testAccServiceVCLConfigUpdateServiceMetadata(name, comment, versionComment, domain string, activate bool) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"
  comment = "%s"
  version_comment = "%s"
  http3 = true

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  activate = %t
  force_destroy = true
}`, name, comment, versionComment, domain, activate)
}

func testAccServiceVCLConfigInitWithVersionComment(name, versionComment, domain string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"
  version_comment = "%s"
  http3 = true

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  force_destroy = true
}`, name, versionComment, domain)
}

func testAccServiceVCLConfigDefaultHost(name, domain, defaultHost string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  default_host = "%s"

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  force_destroy = true
}`, name, domain, defaultHost)
}

func testAccServiceVCLConfigBasicUpdate(name, comment, versionComment, domain string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name    = "%s"
  comment = "%s"
  version_comment = "%s"
  http3 = true

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  force_destroy = true
}`, name, comment, versionComment, domain)
}

func testAccServiceVCLConfigDomainAdd(name, domain1, domain2 string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  domain {
    name    = "%s"
    comment = "tf-testing-other-domain"
  }

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  force_destroy = true
}`, name, domain1, domain2)
}

func testAccServiceVCLConfigDomainUpdateComment(name, domain1, domain2 string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

  domain {
    name    = "%s"
    comment = "tf-testing-domain-updated"
  }

  domain {
    name    = "%s"
    comment = "tf-testing-other-domain"
  }

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  force_destroy = true
}`, name, domain1, domain2)
}

func testAccServiceVCLConfigDomainUpdate(name, domain1, domain3 string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

  domain {
    name    = "%s"
    comment = "tf-testing-domain-updated"
  }

  domain {
    name    = "%s"
    comment = "tf-testing-other-domain"
  }

  backend {
    address = "aws.amazon.com"
    name    = "amazon docs"
  }

  force_destroy = true
}`, name, domain1, domain3)
}

func testAccServiceVCLConfigBackend(name, domain, backend string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  backend {
    address = "%s"
    name    = "tf -test backend"
  }

  force_destroy = true
}`, name, domain, backend)
}

func testAccServiceVCLConfigStaticBackend(name, domain, snippet string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
    name           = "%s"
    force_destroy = true

    domain {
        name = "%s"
    }

    snippet {
        content  = <<-EOT
            %s
        EOT
        name     = "vcl_init"
        priority = 50
        type     = "init"
    }
}`, name, domain, snippet)
}

func testAccServiceVCLConfigBackendTTL(name, domain, backend string, ttl uint) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

  default_ttl = %d
  stale_if_error = true

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  backend {
    address = "%s"
    name    = "tf -test backend"
  }

  force_destroy = true
}`, name, ttl, domain, backend)
}

func testAccServiceVCLConfigBackendZeroTTL(name, domain, backend string, ttl uint) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"
  default_ttl = %d
  stale_if_error = true
  stale_if_error_ttl = 0

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }
  backend {
    address = "%s"
    name    = "tf -test backend"
  }
  force_destroy = true
}`, name, ttl, domain, backend)
}

func testAccServiceVCLConfigBackendUpdate(name, domain, backend, backend2 string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
  name = "%s"

	default_ttl = %d
	stale_if_error = true

  domain {
    name    = "%s"
    comment = "tf-testing-domain"
  }

  backend {
    address = "%s"
    name    = "tf-test-backend"
  }

  backend {
    address = "%s"
    name    = "tf-test-backend-other"
  }

  force_destroy = true
}`, name, defaultTTL, domain, backend, backend2)
}

func testAccServiceVCLConfigBrokenSnippet(name, domain, backendName, snippet string) string {
	return fmt.Sprintf(`
resource "fastly_service_vcl" "foo" {
    name           = "%s"
    activate       = true
    force_destroy = true

    backend {
        address = "httpbin.org"
        name = "%s"
    }

    domain {
        name = "%s"
    }

    snippet {
        content  = <<-EOT
            %s
        EOT
        name     = "url rewrite"
        priority = 100
        type     = "recv"
    }
}`, name, backendName, domain, snippet)
}

func testSweepServices(region string) error {
	client, diagnostics := sharedClientForRegion(region)
	if diagnostics.HasError() {
		return diagToErr(diagnostics)
	}

	services, err := client.ListServices(context.TODO(), &gofastly.ListServicesInput{})
	if err != nil {
		return err
	}

	for _, service := range services {
		if strings.HasPrefix(gofastly.ToValue(service.Name), testResourcePrefix) {
			s, err := client.GetServiceDetails(context.TODO(), &gofastly.GetServiceDetailsInput{
				ServiceID: gofastly.ToValue(service.ServiceID),
			})
			if err != nil {
				return err
			}

			if s.ActiveVersion != nil {
				if gofastly.ToValue(s.ActiveVersion.Number) != 0 {
					_, err := client.DeactivateVersion(context.TODO(), &gofastly.DeactivateVersionInput{
						ServiceID:      gofastly.ToValue(service.ServiceID),
						ServiceVersion: gofastly.ToValue(s.ActiveVersion.Number),
					})
					if err != nil {
						return err
					}
				}
			}

			err = client.DeleteService(context.TODO(), &gofastly.DeleteServiceInput{
				ServiceID: gofastly.ToValue(service.ServiceID),
			})
			if err != nil {
				return err
			}
		}
	}

	return nil
}
