package config

import (
	"io/ioutil"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

func TestErrNoConfigsFound_impl(t *testing.T) {
	var _ error = new(ErrNoConfigsFound)
}

func TestLoadFile_badType(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "bad_type.tf.nope"))
	if err == nil {
		t.Fatal("should have error")
	}
}

func TestLoadFile_gitCrypt(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "git-crypt.tf"))
	if err == nil {
		t.Fatal("should have error")
	}

	t.Logf("err: %s", err)
}

func TestLoadFile_lifecycleKeyCheck(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "lifecycle_cbd_typo.tf"))
	if err == nil {
		t.Fatal("should have error")
	}

	t.Logf("err: %s", err)
}

func TestLoadFile_varInvalidKey(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "var-invalid-key.tf"))
	if err == nil {
		t.Fatal("should have error")
	}
}

func TestLoadFile_resourceArityMistake(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "resource-arity-mistake.tf"))
	if err == nil {
		t.Fatal("should have error")
	}
	expected := "Error loading testdata/resource-arity-mistake.tf: position 2:10: resource must be followed by exactly two strings, a type and a name"
	if err.Error() != expected {
		t.Fatalf("expected:\n%s\ngot:\n%s", expected, err)
	}
}

func TestLoadFile_resourceMultiLifecycle(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "resource-multi-lifecycle.tf"))
	if err == nil {
		t.Fatal("should have error")
	}
}

func TestLoadFile_dataSourceArityMistake(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "data-source-arity-mistake.tf"))
	if err == nil {
		t.Fatal("should have error")
	}
	expected := "Error loading testdata/data-source-arity-mistake.tf: position 2:6: 'data' must be followed by exactly two strings: a type and a name"
	if err.Error() != expected {
		t.Fatalf("expected:\n%s\ngot:\n%s", expected, err)
	}
}

func TestLoadFileWindowsLineEndings(t *testing.T) {
	testFile := filepath.Join(fixtureDir, "windows-line-endings.tf")

	contents, err := ioutil.ReadFile(testFile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(string(contents), "\r\n") {
		t.Fatalf("Windows line endings test file %s contains no windows line endings - this may be an autocrlf related issue.", testFile)
	}

	c, err := LoadFile(testFile)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(windowsHeredocResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFileHeredoc(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "heredoc.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	actual := providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(heredocProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(heredocResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFileEscapedQuotes(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "escapedquotes.tf"))
	if err == nil {
		t.Fatalf("expected syntax error as escaped quotes are no longer supported")
	}

	if !strings.Contains(err.Error(), "parse error") {
		t.Fatalf("expected \"syntax error\", got: %s", err)
	}
}

func TestLoadFileBasic(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "basic.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("wrong dir %#v; want %#v", c.Dir, "")
	}

	expectedTF := &Terraform{RequiredVersion: "foo"}
	if !reflect.DeepEqual(c.Terraform, expectedTF) {
		t.Fatalf("wrong terraform block %#v; want %#v", c.Terraform, expectedTF)
	}

	expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
	if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
		t.Fatalf("wrong atlas config %#v; want %#v", c.Atlas, expectedAtlas)
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(basicVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(basicProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(basicResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want {
		t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want)
	}

	actual = outputsStr(c.Outputs)
	if actual != strings.TrimSpace(basicOutputsStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFileBasic_empty(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "empty.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}
}

func TestLoadFileBasic_import(t *testing.T) {
	// Skip because we disabled importing
	t.Skip()

	c, err := LoadFile(filepath.Join(fixtureDir, "import.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(importVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(importProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(importResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFileBasic_json(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "basic.tf.json"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
	if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
		t.Fatalf("bad: %#v", c.Atlas)
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(basicVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(basicProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(basicResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want {
		t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want)
	}

	actual = outputsStr(c.Outputs)
	if actual != strings.TrimSpace(basicOutputsStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFileBasic_modules(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "modules.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	actual := modulesStr(c.Modules)
	if actual != strings.TrimSpace(modulesModulesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFile_unnamedModule(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "module-unnamed.tf"))
	if err == nil {
		t.Fatalf("bad: expected error")
	}

	errorStr := err.Error()
	if !strings.Contains(errorStr, `"module" must be followed`) {
		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
	}
}

func TestLoadFile_outputDependsOn(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "output-depends-on.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	actual := outputsStr(c.Outputs)
	if actual != strings.TrimSpace(outputDependsOnStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFile_terraformBackend(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	{
		actual := terraformStr(c.Terraform)
		expected := strings.TrimSpace(`
backend (s3)
  foo`)
		if actual != expected {
			t.Fatalf("bad:\n%s", actual)
		}
	}
}

func TestLoadFile_terraformBackendJSON(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend.tf.json"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	{
		actual := terraformStr(c.Terraform)
		expected := strings.TrimSpace(`
backend (s3)
  foo`)
		if actual != expected {
			t.Fatalf("bad:\n%s", actual)
		}
	}
}

// test that the alternate, more obvious JSON format also decodes properly
func TestLoadFile_terraformBackendJSON2(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend-2.tf.json"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	{
		actual := terraformStr(c.Terraform)
		expected := strings.TrimSpace(`
backend (s3)
  foo`)
		if actual != expected {
			t.Fatalf("bad:\n%s", actual)
		}
	}
}

func TestLoadFile_terraformBackendMulti(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend-multi.tf"))
	if err == nil {
		t.Fatal("expected error")
	}

	errorStr := err.Error()
	if !strings.Contains(errorStr, "only one 'backend'") {
		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
	}
}

func TestLoadJSONBasic(t *testing.T) {
	raw, err := ioutil.ReadFile(filepath.Join(fixtureDir, "basic.tf.json"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	c, err := LoadJSON(raw)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
	if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
		t.Fatalf("bad: %#v", c.Atlas)
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(basicVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(basicProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(basicResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = outputsStr(c.Outputs)
	if actual != strings.TrimSpace(basicOutputsStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadJSONAmbiguous(t *testing.T) {
	js := `
{
  "variable": {
    "first": {
      "default": {
        "key": "val"
      }
    },
    "second": {
      "description": "Described",
      "default": {
        "key": "val"
      }
    }
  }
}
`

	c, err := LoadJSON([]byte(js))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if len(c.Variables) != 2 {
		t.Fatal("config should have 2 variables, found", len(c.Variables))
	}

	first := &Variable{
		Name:    "first",
		Default: map[string]interface{}{"key": "val"},
	}
	second := &Variable{
		Name:        "second",
		Description: "Described",
		Default:     map[string]interface{}{"key": "val"},
	}

	if !reflect.DeepEqual(first, c.Variables[0]) {
		t.Fatalf("\nexpected: %#v\ngot:      %#v", first, c.Variables[0])
	}

	if !reflect.DeepEqual(second, c.Variables[1]) {
		t.Fatalf("\nexpected: %#v\ngot:      %#v", second, c.Variables[1])
	}
}

func TestLoadFileBasic_jsonNoName(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "resource-no-name.tf.json"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(basicJsonNoNameResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFile_variables(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "variables.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if c == nil {
		t.Fatal("config should not be nil")
	}

	if c.Dir != "" {
		t.Fatalf("bad: %#v", c.Dir)
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(variablesVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadDir_basic(t *testing.T) {
	dir := filepath.Join(fixtureDir, "dir-basic")
	c, err := LoadDir(dir)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	dirAbs, err := filepath.Abs(dir)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if c.Dir != dirAbs {
		t.Fatalf("bad: %#v", c.Dir)
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(dirBasicVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(dirBasicProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(dirBasicResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = outputsStr(c.Outputs)
	if actual != strings.TrimSpace(dirBasicOutputsStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadDir_file(t *testing.T) {
	_, err := LoadDir(filepath.Join(fixtureDir, "variables.tf"))
	if err == nil {
		t.Fatal("should error")
	}
}

func TestLoadDir_noConfigs(t *testing.T) {
	_, err := LoadDir(filepath.Join(fixtureDir, "dir-empty"))
	if err == nil {
		t.Fatal("should error")
	}
}

func TestLoadDir_noMerge(t *testing.T) {
	c, err := LoadDir(filepath.Join(fixtureDir, "dir-merge"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	if err := c.Validate(); err == nil {
		t.Fatal("should not be valid")
	}
}

func TestLoadDir_override(t *testing.T) {
	c, err := LoadDir(filepath.Join(fixtureDir, "dir-override"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(dirOverrideVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = providerConfigsStr(c.ProviderConfigs)
	if actual != strings.TrimSpace(dirOverrideProvidersStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = resourcesStr(c.Resources)
	if actual != strings.TrimSpace(dirOverrideResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	actual = outputsStr(c.Outputs)
	if actual != strings.TrimSpace(dirOverrideOutputsStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadDir_overrideVar(t *testing.T) {
	c, err := LoadDir(filepath.Join(fixtureDir, "dir-override-var"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(dirOverrideVarsVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFile_mismatchedVariableTypes(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "variable-mismatched-type.tf"))
	if err == nil {
		t.Fatalf("bad: expected error")
	}

	errorStr := err.Error()
	if !strings.Contains(errorStr, "'not_a_map' has a default value which is not of type 'string'") {
		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
	}
}

func TestLoadFile_badVariableTypes(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "bad-variable-type.tf"))
	if err == nil {
		t.Fatalf("bad: expected error")
	}

	errorStr := err.Error()
	if !strings.Contains(errorStr, "'bad_type' type must be one of") {
		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
	}
}

func TestLoadFile_variableNoName(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "variable-no-name.tf"))
	if err == nil {
		t.Fatalf("bad: expected error")
	}

	errorStr := err.Error()
	if !strings.Contains(errorStr, `"variable" must be followed`) {
		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
	}
}

func TestLoadFile_provisioners(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "provisioners.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(provisionerResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFile_provisionersDestroy(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "provisioners-destroy.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(provisionerDestroyResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

func TestLoadFile_unnamedOutput(t *testing.T) {
	_, err := LoadFile(filepath.Join(fixtureDir, "output-unnamed.tf"))
	if err == nil {
		t.Fatalf("bad: expected error")
	}

	errorStr := err.Error()
	if !strings.Contains(errorStr, `"output" must be followed`) {
		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
	}
}

func TestLoadFile_connections(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "connection.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(connectionResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	// Check for the connection info
	r := c.Resources[0]
	if r.Name != "web" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	p1 := r.Provisioners[0]
	if p1.ConnInfo == nil || len(p1.ConnInfo.Raw) != 2 {
		t.Fatalf("Bad: %#v", p1.ConnInfo)
	}
	if p1.ConnInfo.Raw["user"] != "nobody" {
		t.Fatalf("Bad: %#v", p1.ConnInfo)
	}

	p2 := r.Provisioners[1]
	if p2.ConnInfo == nil || len(p2.ConnInfo.Raw) != 2 {
		t.Fatalf("Bad: %#v", p2.ConnInfo)
	}
	if p2.ConnInfo.Raw["user"] != "root" {
		t.Fatalf("Bad: %#v", p2.ConnInfo)
	}
}

func TestLoadFile_createBeforeDestroy(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "create-before-destroy.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(createBeforeDestroyResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	// Check for the flag value
	r := c.Resources[0]
	if r.Name != "web" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	// Should enable create before destroy
	if !r.Lifecycle.CreateBeforeDestroy {
		t.Fatalf("Bad: %#v", r)
	}

	r = c.Resources[1]
	if r.Name != "bar" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	// Should not enable create before destroy
	if r.Lifecycle.CreateBeforeDestroy {
		t.Fatalf("Bad: %#v", r)
	}
}

func TestLoadFile_ignoreChanges(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "ignore-changes.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(ignoreChangesResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	// Check for the flag value
	r := c.Resources[0]
	if r.Name != "web" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	// Should populate ignore changes
	if len(r.Lifecycle.IgnoreChanges) == 0 {
		t.Fatalf("Bad: %#v", r)
	}

	r = c.Resources[1]
	if r.Name != "bar" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	// Should not populate ignore changes
	if len(r.Lifecycle.IgnoreChanges) > 0 {
		t.Fatalf("Bad: %#v", r)
	}

	r = c.Resources[2]
	if r.Name != "baz" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	// Should not populate ignore changes
	if len(r.Lifecycle.IgnoreChanges) > 0 {
		t.Fatalf("Bad: %#v", r)
	}
}

func TestLoad_preventDestroyString(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "prevent-destroy-string.tf"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(createBeforeDestroyResourcesStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	// Check for the flag value
	r := c.Resources[0]
	if r.Name != "web" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	// Should enable create before destroy
	if !r.Lifecycle.PreventDestroy {
		t.Fatalf("Bad: %#v", r)
	}

	r = c.Resources[1]
	if r.Name != "bar" && r.Type != "aws_instance" {
		t.Fatalf("Bad: %#v", r)
	}

	// Should not enable create before destroy
	if r.Lifecycle.PreventDestroy {
		t.Fatalf("Bad: %#v", r)
	}
}

func TestLoad_temporary_files(t *testing.T) {
	_, err := LoadDir(filepath.Join(fixtureDir, "dir-temporary-files"))
	if err == nil {
		t.Fatalf("Expected to see an error stating no config files found")
	}
}

func TestLoad_hclAttributes(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "attributes.tf"))
	if err != nil {
		t.Fatalf("Bad: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(jsonAttributeStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	r := c.Resources[0]
	if r.Name != "test" && r.Type != "cloudstack_firewall" {
		t.Fatalf("Bad: %#v", r)
	}

	raw := r.RawConfig
	if raw.Raw["ipaddress"] != "192.168.0.1" {
		t.Fatalf("Bad: %s", raw.Raw["ipAddress"])
	}

	rule := raw.Raw["rule"].([]map[string]interface{})[0]
	if rule["protocol"] != "tcp" {
		t.Fatalf("Bad: %s", rule["protocol"])
	}

	if rule["source_cidr"] != "10.0.0.0/8" {
		t.Fatalf("Bad: %s", rule["source_cidr"])
	}

	ports := rule["ports"].([]interface{})

	if ports[0] != "80" {
		t.Fatalf("Bad ports: %s", ports[0])
	}
	if ports[1] != "1000-2000" {
		t.Fatalf("Bad ports: %s", ports[1])
	}
}

func TestLoad_jsonAttributes(t *testing.T) {
	c, err := LoadFile(filepath.Join(fixtureDir, "attributes.tf.json"))
	if err != nil {
		t.Fatalf("Bad: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := resourcesStr(c.Resources)
	if actual != strings.TrimSpace(jsonAttributeStr) {
		t.Fatalf("bad:\n%s", actual)
	}

	r := c.Resources[0]
	if r.Name != "test" && r.Type != "cloudstack_firewall" {
		t.Fatalf("Bad: %#v", r)
	}

	raw := r.RawConfig
	if raw.Raw["ipaddress"] != "192.168.0.1" {
		t.Fatalf("Bad: %s", raw.Raw["ipAddress"])
	}

	rule := raw.Raw["rule"].([]map[string]interface{})[0]
	if rule["protocol"] != "tcp" {
		t.Fatalf("Bad: %s", rule["protocol"])
	}

	if rule["source_cidr"] != "10.0.0.0/8" {
		t.Fatalf("Bad: %s", rule["source_cidr"])
	}

	ports := rule["ports"].([]interface{})

	if ports[0] != "80" {
		t.Fatalf("Bad ports: %s", ports[0])
	}
	if ports[1] != "1000-2000" {
		t.Fatalf("Bad ports: %s", ports[1])
	}
}

func TestLoad_onlyOverride(t *testing.T) {
	c, err := LoadDir(filepath.Join(fixtureDir, "dir-only-override"))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if c == nil {
		t.Fatal("config should not be nil")
	}

	actual := variablesStr(c.Variables)
	if actual != strings.TrimSpace(dirOnlyOverrideVariablesStr) {
		t.Fatalf("bad:\n%s", actual)
	}
}

const jsonAttributeStr = `
cloudstack_firewall.test (x1)
  ipaddress
  rule
`

const windowsHeredocResourcesStr = `
aws_instance.test (x1)
  user_data
`

const heredocProvidersStr = `
aws
  access_key
  secret_key
`

const heredocResourcesStr = `
aws_iam_policy.policy (x1)
  description
  name
  path
  policy
aws_instance.heredocwithnumbers (x1)
  ami
  provisioners
    local-exec
      command
aws_instance.test (x1)
  ami
  provisioners
    remote-exec
      inline
`

const basicOutputsStr = `
web_id
  vars
    resource: aws_instance.web.id
  description
    The ID
web_ip
  vars
    resource: aws_instance.web.private_ip
`

const basicLocalsStr = `
literal
literal_list
literal_map
security_group_ids
  vars
    resource: aws_security_group.firewall.*.id
web_ip
  vars
    resource: aws_instance.web.private_ip
`

const basicProvidersStr = `
aws
  access_key
  secret_key
do
  api_key
  vars
    user: var.foo
`

const basicResourcesStr = `
aws_instance.db (x1)
  VPC
  security_groups
  provisioners
    file
      destination
      source
  dependsOn
    aws_instance.web
  vars
    resource: aws_security_group.firewall.*.id
aws_instance.web (x1)
  ami
  network_interface
  security_groups
  provisioners
    file
      destination
      source
  vars
    resource: aws_security_group.firewall.foo
    user: var.foo
aws_security_group.firewall (x5)
data.do.depends (x1)
  dependsOn
    data.do.simple
data.do.simple (x1)
  foo
`

const basicVariablesStr = `
bar (required) (string)
  <>
  <>
baz (map)
  map[key:value]
  <>
foo
  bar
  bar
`

const basicJsonNoNameResourcesStr = `
aws_security_group.allow_external_http_https (x1)
  tags
`

const dirBasicOutputsStr = `
web_ip
  vars
    resource: aws_instance.web.private_ip
`

const dirBasicProvidersStr = `
aws
  access_key
  secret_key
do
  api_key
  vars
    user: var.foo
`

const dirBasicResourcesStr = `
aws_instance.db (x1)
  security_groups
  vars
    resource: aws_security_group.firewall.*.id
aws_instance.web (x1)
  ami
  network_interface
  security_groups
  vars
    resource: aws_security_group.firewall.foo
    user: var.foo
aws_security_group.firewall (x5)
data.do.depends (x1)
  dependsOn
    data.do.simple
data.do.simple (x1)
  foo
`

const dirBasicVariablesStr = `
foo
  bar
  bar
`

const dirOverrideOutputsStr = `
web_ip
  vars
    resource: aws_instance.web.private_ip
`

const dirOverrideProvidersStr = `
aws
  access_key
  secret_key
do
  api_key
  vars
    user: var.foo
`

const dirOverrideResourcesStr = `
aws_instance.db (x1)
  ami
  security_groups
aws_instance.web (x1)
  ami
  foo
  network_interface
  security_groups
  vars
    resource: aws_security_group.firewall.foo
    user: var.foo
aws_security_group.firewall (x5)
data.do.depends (x1)
  hello
  dependsOn
    data.do.simple
data.do.simple (x1)
  foo
`

const dirOverrideVariablesStr = `
foo
  bar
  bar
`

const dirOverrideVarsVariablesStr = `
foo
  baz
  bar
`

const dirOnlyOverrideVariablesStr = `
foo
  bar
  bar
`

const importProvidersStr = `
aws
  bar
  foo
`

const importResourcesStr = `
aws_security_group.db (x1)
aws_security_group.web (x1)
`

const importVariablesStr = `
bar (required)
  <>
  <>
foo
  bar
  bar
`

const modulesModulesStr = `
bar
  source = baz
  memory
`

const provisionerResourcesStr = `
aws_instance.web (x1)
  ami
  security_groups
  provisioners
    shell
      path
  vars
    resource: aws_security_group.firewall.foo
    user: var.foo
`

const provisionerDestroyResourcesStr = `
aws_instance.web (x1)
  provisioners
    shell
    shell (destroy)
      path
    shell (destroy)
      on_failure = continue
      path
`

const connectionResourcesStr = `
aws_instance.web (x1)
  ami
  security_groups
  provisioners
    shell
      path
    shell
      path
  vars
    resource: aws_security_group.firewall.foo
    user: var.foo
`

const outputDependsOnStr = `
value
  dependsOn
    foo
`

const variablesVariablesStr = `
bar
  <>
  <>
baz
  foo
  <>
foo (required)
  <>
  <>
`

const createBeforeDestroyResourcesStr = `
aws_instance.bar (x1)
  ami
aws_instance.web (x1)
  ami
`

const ignoreChangesResourcesStr = `
aws_instance.bar (x1)
  ami
aws_instance.baz (x1)
  ami
aws_instance.web (x1)
  ami
`
