package addrs

import (
	"fmt"
	"testing"
)

func TestTargetContains(t *testing.T) {
	for _, test := range []struct {
		addr, other Targetable
		expect      bool
	}{
		{
			mustParseTarget("module.foo"),
			mustParseTarget("module.bar"),
			false,
		},
		{
			mustParseTarget("module.foo"),
			mustParseTarget("module.foo"),
			true,
		},
		{
			RootModuleInstance,
			mustParseTarget("module.foo"),
			true,
		},
		{
			mustParseTarget("module.foo"),
			RootModuleInstance,
			false,
		},
		{
			mustParseTarget("module.foo"),
			mustParseTarget("module.foo.module.bar[0]"),
			true,
		},
		{
			mustParseTarget("module.foo"),
			mustParseTarget("module.foo.module.bar[0]"),
			true,
		},
		{
			mustParseTarget("module.foo[2]"),
			mustParseTarget("module.foo[2].module.bar[0]"),
			true,
		},
		{
			mustParseTarget("module.foo"),
			mustParseTarget("module.foo.test_resource.bar"),
			true,
		},
		{
			mustParseTarget("module.foo"),
			mustParseTarget("module.foo.test_resource.bar[0]"),
			true,
		},

		// Resources
		{
			mustParseTarget("test_resource.foo"),
			mustParseTarget("test_resource.foo[\"bar\"]"),
			true,
		},
		{
			mustParseTarget(`test_resource.foo["bar"]`),
			mustParseTarget(`test_resource.foo["bar"]`),
			true,
		},
		{
			mustParseTarget("test_resource.foo"),
			mustParseTarget("test_resource.foo[2]"),
			true,
		},
		{
			mustParseTarget("test_resource.foo"),
			mustParseTarget("module.bar.test_resource.foo[2]"),
			false,
		},
		{
			mustParseTarget("module.bar.test_resource.foo"),
			mustParseTarget("module.bar.test_resource.foo[2]"),
			true,
		},
		{
			mustParseTarget("module.bar.test_resource.foo"),
			mustParseTarget("module.bar[0].test_resource.foo[2]"),
			false,
		},
		{
			mustParseTarget("module.bar.test_resource.foo"),
			mustParseTarget("module.bar.test_resource.foo[0]"),
			true,
		},
		{
			mustParseTarget("module.bax"),
			mustParseTarget("module.bax[0].test_resource.foo[0]"),
			true,
		},

		// Config paths, while never returned from parsing a target, must still
		// be targetable
		{
			ConfigResource{
				Module: []string{"bar"},
				Resource: Resource{
					Mode: ManagedResourceMode,
					Type: "test_resource",
					Name: "foo",
				},
			},
			mustParseTarget("module.bar.test_resource.foo[2]"),
			true,
		},
		{
			mustParseTarget("module.bar"),
			ConfigResource{
				Module: []string{"bar"},
				Resource: Resource{
					Mode: ManagedResourceMode,
					Type: "test_resource",
					Name: "foo",
				},
			},
			true,
		},
		{
			mustParseTarget("module.bar.test_resource.foo"),
			ConfigResource{
				Module: []string{"bar"},
				Resource: Resource{
					Mode: ManagedResourceMode,
					Type: "test_resource",
					Name: "foo",
				},
			},
			true,
		},
		{
			ConfigResource{
				Resource: Resource{
					Mode: ManagedResourceMode,
					Type: "test_resource",
					Name: "foo",
				},
			},
			mustParseTarget("module.bar.test_resource.foo[2]"),
			false,
		},
		{
			ConfigResource{
				Module: []string{"bar"},
				Resource: Resource{
					Mode: ManagedResourceMode,
					Type: "test_resource",
					Name: "foo",
				},
			},
			mustParseTarget("module.bar[0].test_resource.foo"),
			true,
		},

		// Modules are also never the result of parsing a target, but also need
		// to be targetable
		{
			Module{"bar"},
			Module{"bar", "baz"},
			true,
		},
		{
			Module{"bar"},
			mustParseTarget("module.bar[0]"),
			true,
		},
		{
			// Parsing an ambiguous module path needs to ensure the
			// ModuleInstance could contain the Module. This is safe because if
			// the module could be expanded, it must have an index, meaning no
			// index indicates that the module instance and module are
			// functionally equivalent.
			mustParseTarget("module.bar"),
			Module{"bar"},
			true,
		},
		{
			// A specific ModuleInstance cannot contain a module
			mustParseTarget("module.bar[0]"),
			Module{"bar"},
			false,
		},
		{
			Module{"bar", "baz"},
			mustParseTarget("module.bar[0].module.baz.test_resource.foo[1]"),
			true,
		},
		{
			mustParseTarget("module.bar[0].module.baz"),
			Module{"bar", "baz"},
			false,
		},
	} {
		t.Run(fmt.Sprintf("%s-in-%s", test.other, test.addr), func(t *testing.T) {
			got := test.addr.TargetContains(test.other)
			if got != test.expect {
				t.Fatalf("expected %q.TargetContains(%q) == %t", test.addr, test.other, test.expect)
			}
		})
	}
}

func TestResourceContains(t *testing.T) {
	for _, test := range []struct {
		in, other Targetable
		expect    bool
	}{} {
		t.Run(fmt.Sprintf("%s-in-%s", test.other, test.in), func(t *testing.T) {
			got := test.in.TargetContains(test.other)
			if got != test.expect {
				t.Fatalf("expected %q.TargetContains(%q) == %t", test.in, test.other, test.expect)
			}
		})
	}
}

func mustParseTarget(str string) Targetable {
	t, diags := ParseTargetStr(str)
	if diags != nil {
		panic(fmt.Sprintf("%s: %s", str, diags.ErrWithWarnings()))
	}
	return t.Subject
}
