package config

import (
	"reflect"
	"strings"
	"testing"

	"github.com/hashicorp/hil"
)

func TestNewInterpolatedVariable(t *testing.T) {
	tests := []struct {
		Input string
		Want  InterpolatedVariable
		Error bool
	}{
		{
			"var.foo",
			&UserVariable{
				Name: "foo",
				key:  "var.foo",
			},
			false,
		},
		{
			"local.foo",
			&LocalVariable{
				Name: "foo",
			},
			false,
		},
		{
			"local.foo.nope",
			nil,
			true,
		},
		{
			"module.foo.bar",
			&ModuleVariable{
				Name:  "foo",
				Field: "bar",
				key:   "module.foo.bar",
			},
			false,
		},
		{
			"count.index",
			&CountVariable{
				Type: CountValueIndex,
				key:  "count.index",
			},
			false,
		},
		{
			"count.nope",
			&CountVariable{
				Type: CountValueInvalid,
				key:  "count.nope",
			},
			false,
		},
		{
			"path.module",
			&PathVariable{
				Type: PathValueModule,
				key:  "path.module",
			},
			false,
		},
		{
			"self.address",
			&SelfVariable{
				Field: "address",
				key:   "self.address",
			},
			false,
		},
		{
			"terraform.env",
			&TerraformVariable{
				Field: "env",
				key:   "terraform.env",
			},
			false,
		},
	}

	for i, test := range tests {
		t.Run(test.Input, func(t *testing.T) {
			got, err := NewInterpolatedVariable(test.Input)
			if err != nil != test.Error {
				t.Errorf("%d. Error: %s", i, err)
			}
			if !test.Error && !reflect.DeepEqual(got, test.Want) {
				t.Errorf(
					"wrong result\ninput: %s\ngot:   %#v\nwant:  %#v",
					test.Input, got, test.Want,
				)
			}
		})
	}
}

func TestNewResourceVariable(t *testing.T) {
	v, err := NewResourceVariable("foo.bar.baz")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if v.Mode != ManagedResourceMode {
		t.Fatalf("bad: %#v", v)
	}
	if v.Type != "foo" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Name != "bar" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Field != "baz" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Multi {
		t.Fatal("should not be multi")
	}

	if v.FullKey() != "foo.bar.baz" {
		t.Fatalf("bad: %#v", v)
	}
}

func TestNewResourceVariableData(t *testing.T) {
	v, err := NewResourceVariable("data.foo.bar.baz")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if v.Mode != DataResourceMode {
		t.Fatalf("bad: %#v", v)
	}
	if v.Type != "foo" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Name != "bar" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Field != "baz" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Multi {
		t.Fatal("should not be multi")
	}

	if v.FullKey() != "data.foo.bar.baz" {
		t.Fatalf("bad: %#v", v)
	}
}

func TestNewUserVariable(t *testing.T) {
	v, err := NewUserVariable("var.bar")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if v.Name != "bar" {
		t.Fatalf("bad: %#v", v.Name)
	}
	if v.FullKey() != "var.bar" {
		t.Fatalf("bad: %#v", v)
	}
}

func TestNewUserVariable_oldMapDotIndexErr(t *testing.T) {
	_, err := NewUserVariable("var.bar.baz")
	if err == nil || !strings.Contains(err.Error(), "Invalid dot index") {
		t.Fatalf("Expected dot index err, got: %#v", err)
	}
}

func TestResourceVariable_impl(t *testing.T) {
	var _ InterpolatedVariable = new(ResourceVariable)
}

func TestResourceVariable_Multi(t *testing.T) {
	v, err := NewResourceVariable("foo.bar.*.baz")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if v.Type != "foo" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Name != "bar" {
		t.Fatalf("bad: %#v", v)
	}
	if v.Field != "baz" {
		t.Fatalf("bad: %#v", v)
	}
	if !v.Multi {
		t.Fatal("should be multi")
	}
}

func TestResourceVariable_MultiIndex(t *testing.T) {
	cases := []struct {
		Input string
		Index int
		Field string
	}{
		{"foo.bar.*.baz", -1, "baz"},
		{"foo.bar.0.baz", 0, "baz"},
		{"foo.bar.5.baz", 5, "baz"},
	}

	for _, tc := range cases {
		v, err := NewResourceVariable(tc.Input)
		if err != nil {
			t.Fatalf("err: %s", err)
		}
		if !v.Multi {
			t.Fatalf("should be multi: %s", tc.Input)
		}
		if v.Index != tc.Index {
			t.Fatalf("bad: %d\n\n%s", v.Index, tc.Input)
		}
		if v.Field != tc.Field {
			t.Fatalf("bad: %s\n\n%s", v.Field, tc.Input)
		}
	}
}

func TestUserVariable_impl(t *testing.T) {
	var _ InterpolatedVariable = new(UserVariable)
}

func TestDetectVariables(t *testing.T) {
	cases := []struct {
		Input  string
		Result []InterpolatedVariable
	}{
		{
			"foo $${var.foo}",
			nil,
		},

		{
			"foo ${var.foo}",
			[]InterpolatedVariable{
				&UserVariable{
					Name: "foo",
					key:  "var.foo",
				},
			},
		},

		{
			"foo ${var.foo} ${var.bar}",
			[]InterpolatedVariable{
				&UserVariable{
					Name: "foo",
					key:  "var.foo",
				},
				&UserVariable{
					Name: "bar",
					key:  "var.bar",
				},
			},
		},

		{
			`foo ${module.foo.output["key"]}`,
			[]InterpolatedVariable{
				&ModuleVariable{
					Name:  "foo",
					Field: "output",
					key:   "module.foo.output",
				},
				&ModuleVariable{
					Name:  "foo",
					Field: "output",
					key:   "module.foo.output",
				},
			},
		},
	}

	for _, tc := range cases {
		ast, err := hil.Parse(tc.Input)
		if err != nil {
			t.Fatalf("%s\n\nInput: %s", err, tc.Input)
		}

		actual, err := DetectVariables(ast)
		if err != nil {
			t.Fatalf("err: %s", err)
		}
		if !reflect.DeepEqual(actual, tc.Result) {
			t.Fatalf("bad: %#v\n\nInput: %s", actual, tc.Input)
		}
	}
}
