// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package tofu

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/opentofu/opentofu/internal/addrs"
	"github.com/opentofu/opentofu/internal/configs"
)

func TestParseResourceAddressInternal(t *testing.T) {
	cases := map[string]struct {
		Input    string
		Expected *ResourceAddress
		Output   string
	}{
		"basic resource": {
			"aws_instance.foo",
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"aws_instance.foo",
		},

		"basic resource with count": {
			"aws_instance.foo.1",
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        1,
			},
			"aws_instance.foo[1]",
		},

		"data resource": {
			"data.aws_ami.foo",
			&ResourceAddress{
				Mode:         DataResourceMode,
				Type:         "aws_ami",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"data.aws_ami.foo",
		},

		"data resource with count": {
			"data.aws_ami.foo.1",
			&ResourceAddress{
				Mode:         DataResourceMode,
				Type:         "aws_ami",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        1,
			},
			"data.aws_ami.foo[1]",
		},

		"non-data resource with 4 elements": {
			"aws_instance.foo.bar.1",
			nil,
			"",
		},
	}

	for tn, tc := range cases {
		t.Run(tc.Input, func(t *testing.T) {
			out, err := parseResourceAddressInternal(tc.Input)
			if (err != nil) != (tc.Expected == nil) {
				t.Fatalf("%s: unexpected err: %#v", tn, err)
			}
			if err != nil {
				return
			}

			if !reflect.DeepEqual(out, tc.Expected) {
				t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
			}

			// Compare outputs if those exist
			expected := tc.Input
			if tc.Output != "" {
				expected = tc.Output
			}
			if out.String() != expected {
				t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
			}

			// Compare equality because the internal parse is used
			// to compare equality to equal inputs.
			if !out.Equals(tc.Expected) {
				t.Fatalf("expected equality:\n\n%#v\n\n%#v", out, tc.Expected)
			}
		})
	}
}

func TestParseResourceAddress(t *testing.T) {
	cases := map[string]struct {
		Input    string
		Expected *ResourceAddress
		Output   string
		Err      bool
	}{
		"implicit primary managed instance, no specific index": {
			"aws_instance.foo",
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"implicit primary data instance, no specific index": {
			"data.aws_instance.foo",
			&ResourceAddress{
				Mode:         DataResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"implicit primary, explicit index": {
			"aws_instance.foo[2]",
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        2,
			},
			"",
			false,
		},
		"implicit primary, explicit index over ten": {
			"aws_instance.foo[12]",
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        12,
			},
			"",
			false,
		},
		"explicit primary, explicit index": {
			"aws_instance.foo.primary[2]",
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceType:    TypePrimary,
				InstanceTypeSet: true,
				Index:           2,
			},
			"",
			false,
		},
		"tainted": {
			"aws_instance.foo.tainted",
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceType:    TypeTainted,
				InstanceTypeSet: true,
				Index:           -1,
			},
			"",
			false,
		},
		"deposed": {
			"aws_instance.foo.deposed",
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceType:    TypeDeposed,
				InstanceTypeSet: true,
				Index:           -1,
			},
			"",
			false,
		},
		"with a hyphen": {
			"aws_instance.foo-bar",
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo-bar",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"managed in a module": {
			"module.child.aws_instance.foo",
			&ResourceAddress{
				Path:         []string{"child"},
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"data in a module": {
			"module.child.data.aws_instance.foo",
			&ResourceAddress{
				Path:         []string{"child"},
				Mode:         DataResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"nested modules": {
			"module.a.module.b.module.forever.aws_instance.foo",
			&ResourceAddress{
				Path:         []string{"a", "b", "forever"},
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"just a module": {
			"module.a",
			&ResourceAddress{
				Path:         []string{"a"},
				Type:         "",
				Name:         "",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"just a nested module": {
			"module.a.module.b",
			&ResourceAddress{
				Path:         []string{"a", "b"},
				Type:         "",
				Name:         "",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"",
			false,
		},
		"module missing resource type": {
			"module.name.foo",
			nil,
			"",
			true,
		},
	}

	for tn, tc := range cases {
		t.Run(tn, func(t *testing.T) {
			out, err := ParseResourceAddress(tc.Input)
			if (err != nil) != tc.Err {
				t.Fatalf("%s: unexpected err: %#v", tn, err)
			}
			if tc.Err {
				return
			}

			if !reflect.DeepEqual(out, tc.Expected) {
				t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
			}

			expected := tc.Input
			if tc.Output != "" {
				expected = tc.Output
			}
			if out.String() != expected {
				t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
			}
		})
	}
}

func TestResourceAddressContains(t *testing.T) {
	tests := []struct {
		Address *ResourceAddress
		Other   *ResourceAddress
		Want    bool
	}{
		{
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			true,
		},
		{
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           0,
			},
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			true,
		},
		{
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			true,
		},
		{
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			true,
		},
		{
			&ResourceAddress{
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			true,
		},
		{
			&ResourceAddress{
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"bar"},
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			true,
		},
		{
			&ResourceAddress{
				Path:            []string{"bar"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"bar"},
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			true,
		},
		{
			&ResourceAddress{
				Path:            []string{"bar"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"bar", "baz"},
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			true,
		},
		{
			&ResourceAddress{
				Path:            []string{"bar"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"bar", "baz"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			true,
		},
		{
			&ResourceAddress{
				Path:            []string{"bar"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"bar", "baz", "foo", "pizza"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			true,
		},

		{
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "bar",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			false,
		},
		{
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			&ResourceAddress{
				Mode:            DataResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			false,
		},
		{
			&ResourceAddress{
				Path:            []string{"bar"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"baz"},
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			false,
		},
		{
			&ResourceAddress{
				Path:            []string{"bar"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"baz", "bar"},
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           -1,
			},
			false,
		},
		{
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: true,
				InstanceType:    TypePrimary,
				Index:           0,
			},
			&ResourceAddress{
				Mode:            ManagedResourceMode,
				Type:            "aws_instance",
				Name:            "foo",
				InstanceTypeSet: false,
				Index:           0,
			},
			false,
		},
		{
			&ResourceAddress{
				Path:            []string{"bar", "baz"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			&ResourceAddress{
				Path:            []string{"bar"},
				InstanceTypeSet: false,
				Index:           -1,
			},
			false,
		},
		{
			&ResourceAddress{
				Type:         "aws_instance",
				Name:         "foo",
				Index:        1,
				InstanceType: TypePrimary,
				Mode:         ManagedResourceMode,
			},
			&ResourceAddress{
				Type:         "aws_instance",
				Name:         "foo",
				Index:        -1,
				InstanceType: TypePrimary,
				Mode:         ManagedResourceMode,
			},
			false,
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%s contains %s", test.Address, test.Other), func(t *testing.T) {
			got := test.Address.Contains(test.Other)
			if got != test.Want {
				t.Errorf(
					"wrong result\nrecv:  %s\ngiven: %s\ngot:   %#v\nwant:  %#v",
					test.Address, test.Other,
					got, test.Want,
				)
			}
		})
	}
}

func TestResourceAddressEquals(t *testing.T) {
	cases := map[string]struct {
		Address *ResourceAddress
		Other   interface{}
		Expect  bool
	}{
		"basic match": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: true,
		},
		"address does not set index": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        3,
			},
			Expect: true,
		},
		"other does not set index": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        3,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Expect: true,
		},
		"neither sets index": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Expect: true,
		},
		"index over ten": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        1,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        13,
			},
			Expect: false,
		},
		"different type": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_vpc",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: false,
		},
		"different mode": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Other: &ResourceAddress{
				Mode:         DataResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: false,
		},
		"different name": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "bar",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: false,
		},
		"different instance type": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypeTainted,
				Index:        0,
			},
			Expect: false,
		},
		"different index": {
			Address: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Other: &ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        1,
			},
			Expect: false,
		},
		"module address matches address of managed resource inside module": {
			Address: &ResourceAddress{
				Path:         []string{"a", "b"},
				Type:         "",
				Name:         "",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Other: &ResourceAddress{
				Path:         []string{"a", "b"},
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: true,
		},
		"module address matches address of data resource inside module": {
			Address: &ResourceAddress{
				Path:         []string{"a", "b"},
				Type:         "",
				Name:         "",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Other: &ResourceAddress{
				Path:         []string{"a", "b"},
				Mode:         DataResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: true,
		},
		"module address doesn't match managed resource outside module": {
			Address: &ResourceAddress{
				Path:         []string{"a", "b"},
				Type:         "",
				Name:         "",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Other: &ResourceAddress{
				Path:         []string{"a"},
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: false,
		},
		"module address doesn't match data resource outside module": {
			Address: &ResourceAddress{
				Path:         []string{"a", "b"},
				Type:         "",
				Name:         "",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Other: &ResourceAddress{
				Path:         []string{"a"},
				Mode:         DataResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: false,
		},
		"nil path vs empty path should match": {
			Address: &ResourceAddress{
				Path:         []string{},
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			Other: &ResourceAddress{
				Path:         nil,
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        0,
			},
			Expect: true,
		},
	}

	for tn, tc := range cases {
		actual := tc.Address.Equals(tc.Other)
		if actual != tc.Expect {
			t.Fatalf("%q: expected equals: %t, got %t for:\n%#v\n%#v",
				tn, tc.Expect, actual, tc.Address, tc.Other)
		}
	}
}

func TestResourceAddressStateId(t *testing.T) {
	cases := map[string]struct {
		Input    *ResourceAddress
		Expected string
	}{
		"basic resource": {
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"aws_instance.foo",
		},

		"basic resource with index": {
			&ResourceAddress{
				Mode:         ManagedResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        2,
			},
			"aws_instance.foo.2",
		},

		"data resource": {
			&ResourceAddress{
				Mode:         DataResourceMode,
				Type:         "aws_instance",
				Name:         "foo",
				InstanceType: TypePrimary,
				Index:        -1,
			},
			"data.aws_instance.foo",
		},
	}

	for tn, tc := range cases {
		t.Run(tn, func(t *testing.T) {
			actual := tc.Input.stateId()
			if actual != tc.Expected {
				t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, tc.Expected, actual)
			}
		})
	}
}

func TestResourceAddressHasResourceSpec(t *testing.T) {
	cases := []struct {
		Input string
		Want  bool
	}{
		{
			"module.foo",
			false,
		},
		{
			"module.foo.module.bar",
			false,
		},
		{
			"null_resource.baz",
			true,
		},
		{
			"null_resource.baz[0]",
			true,
		},
		{
			"data.null_data_source.baz",
			true,
		},
		{
			"data.null_data_source.baz[0]",
			true,
		},
		{
			"module.foo.null_resource.baz",
			true,
		},
		{
			"module.foo.data.null_data_source.baz",
			true,
		},
		{
			"module.foo.module.bar.null_resource.baz",
			true,
		},
	}

	for _, test := range cases {
		t.Run(test.Input, func(t *testing.T) {
			addr, err := ParseResourceAddress(test.Input)
			if err != nil {
				t.Fatalf("error parsing address: %s", err)
			}
			got := addr.HasResourceSpec()
			if got != test.Want {
				t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want)
			}
		})
	}
}

func TestResourceAddressWholeModuleAddress(t *testing.T) {
	cases := []struct {
		Input string
		Want  string
	}{
		{
			"module.foo",
			"module.foo",
		},
		{
			"module.foo.module.bar",
			"module.foo.module.bar",
		},
		{
			"null_resource.baz",
			"",
		},
		{
			"null_resource.baz[0]",
			"",
		},
		{
			"data.null_data_source.baz",
			"",
		},
		{
			"data.null_data_source.baz[0]",
			"",
		},
		{
			"module.foo.null_resource.baz",
			"module.foo",
		},
		{
			"module.foo.data.null_data_source.baz",
			"module.foo",
		},
		{
			"module.foo.module.bar.null_resource.baz",
			"module.foo.module.bar",
		},
	}

	for _, test := range cases {
		t.Run(test.Input, func(t *testing.T) {
			addr, err := ParseResourceAddress(test.Input)
			if err != nil {
				t.Fatalf("error parsing address: %s", err)
			}
			gotAddr := addr.WholeModuleAddress()
			got := gotAddr.String()
			if got != test.Want {
				t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want)
			}
		})
	}
}

func TestResourceAddressMatchesResourceConfig(t *testing.T) {
	root := []string(nil)
	child := []string{"child"}
	grandchild := []string{"child", "grandchild"}
	irrelevant := []string{"irrelevant"}

	tests := []struct {
		Addr       *ResourceAddress
		ModulePath []string
		Resource   *configs.Resource
		Want       bool
	}{
		{
			&ResourceAddress{
				Mode:  ManagedResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			root,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			true,
		},
		{
			&ResourceAddress{
				Path:  []string{"child"},
				Mode:  ManagedResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			child,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			true,
		},
		{
			&ResourceAddress{
				Path:  []string{"child", "grandchild"},
				Mode:  ManagedResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			grandchild,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			true,
		},
		{
			&ResourceAddress{
				Path:  []string{"child"},
				Index: -1,
			},
			child,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			true,
		},
		{
			&ResourceAddress{
				Path:  []string{"child", "grandchild"},
				Index: -1,
			},
			grandchild,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			true,
		},
		{
			&ResourceAddress{
				Mode:  DataResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			irrelevant,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			false,
		},
		{
			&ResourceAddress{
				Mode:  ManagedResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			irrelevant,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "pizza",
			},
			false,
		},
		{
			&ResourceAddress{
				Mode:  ManagedResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			irrelevant,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "aws_instance",
				Name: "baz",
			},
			false,
		},
		{
			&ResourceAddress{
				Path:  []string{"child", "grandchild"},
				Mode:  ManagedResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			child,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			false,
		},
		{
			&ResourceAddress{
				Path:  []string{"child"},
				Mode:  ManagedResourceMode,
				Type:  "null_resource",
				Name:  "baz",
				Index: -1,
			},
			grandchild,
			&configs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "null_resource",
				Name: "baz",
			},
			false,
		},
	}

	for i, test := range tests {
		t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) {
			got := test.Addr.MatchesResourceConfig(test.ModulePath, test.Resource)
			if got != test.Want {
				t.Errorf(
					"wrong result\naddr: %s\nmod:  %#v\nrsrc: %#v\ngot:  %#v\nwant: %#v",
					test.Addr, test.ModulePath, test.Resource, got, test.Want,
				)
			}
		})
	}
}

func TestResourceAddressLess(t *testing.T) {
	tests := []struct {
		A    string
		B    string
		Want bool
	}{
		{
			"foo.bar",
			"module.baz.foo.bar",
			true,
		},
		{
			"module.baz.foo.bar",
			"zzz.bar", // would sort after "module" in lexicographical sort
			false,
		},
		{
			"module.baz.foo.bar",
			"module.baz.foo.bar",
			false,
		},
		{
			"module.baz.foo.bar",
			"module.boz.foo.bar",
			true,
		},
		{
			"module.boz.foo.bar",
			"module.baz.foo.bar",
			false,
		},
		{
			"a.b",
			"b.c",
			true,
		},
		{
			"a.b",
			"a.c",
			true,
		},
		{
			"c.b",
			"b.c",
			false,
		},
		{
			"a.b[9]",
			"a.b[10]",
			true,
		},
		{
			"b.b[9]",
			"a.b[10]",
			false,
		},
		{
			"a.b",
			"a.b.deposed",
			true,
		},
		{
			"a.b.tainted",
			"a.b.deposed",
			true,
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%s < %s", test.A, test.B), func(t *testing.T) {
			addrA, err := ParseResourceAddress(test.A)
			if err != nil {
				t.Fatal(err)
			}
			addrB, err := ParseResourceAddress(test.B)
			if err != nil {
				t.Fatal(err)
			}
			got := addrA.Less(addrB)
			invGot := addrB.Less(addrA)
			if got != test.Want {
				t.Errorf(
					"wrong result\ntest: %s < %s\ngot:  %#v\nwant: %#v",
					test.A, test.B, got, test.Want,
				)
			}
			if test.A != test.B { // inverse test doesn't apply when equal
				if invGot != !test.Want {
					t.Errorf(
						"wrong inverse result\ntest: %s < %s\ngot:  %#v\nwant: %#v",
						test.B, test.A, invGot, !test.Want,
					)
				}
			} else {
				if invGot != test.Want {
					t.Errorf(
						"wrong inverse result\ntest: %s < %s\ngot:  %#v\nwant: %#v",
						test.B, test.A, invGot, test.Want,
					)
				}
			}
		})
	}
}
