// 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 (
	"context"
	"errors"
	"fmt"
	"log"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/zclconf/go-cty-debug/ctydebug"
	"github.com/zclconf/go-cty/cty"

	"github.com/opentofu/opentofu/internal/addrs"
	"github.com/opentofu/opentofu/internal/configs/configschema"
	"github.com/opentofu/opentofu/internal/plugins"
	"github.com/opentofu/opentofu/internal/providers"
	"github.com/opentofu/opentofu/internal/states"
	"github.com/opentofu/opentofu/internal/tfdiags"
)

func TestContextImport_basic(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-provider")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}
	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportStr)
	if actual != expected {
		t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s", actual, expected)
	}
}

// import 1 of count instances in the configuration
func TestContextImport_countIndex(t *testing.T) {
	p := testProvider("aws")
	m := testModuleInline(t, map[string]string{
		"main.tf": `
provider "aws" {
  foo = "bar"
}

resource "aws_instance" "foo" {
  count = 2
}
`,
	})

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.IntKey(0),
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportCountIndexStr)
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_multiInstanceProviderConfig(t *testing.T) {
	// This test deals with the situation of importing into a resource instance
	// whose resource has a dynamic instance key in its "provider" argument,
	// and thus the import step needs to perform dynamic provider instance
	// selection to determine exactly which provider instance to use.

	m := testModuleInline(t, map[string]string{
		"main.tf": `
			terraform {
				required_providers {
					test = {
						source = "terraform.io/builtin/test"
					}
				}
			}

			provider "test" {
				alias = "multi"
				for_each = {
					a = {}
					b = {}
				}

				marker = each.key
			}

			resource "test_thing" "test" {
				for_each = { "foo" = "a" }
				provider = test.multi[each.value]
			}
		`,
	})

	resourceTypeSchema := providers.Schema{
		Block: &configschema.Block{
			Attributes: map[string]*configschema.Attribute{
				"id": {
					Type:     cty.String,
					Computed: true,
				},
				"import_marker": {
					Type:     cty.String,
					Computed: true,
				},
				"refresh_marker": {
					Type:     cty.String,
					Computed: true,
				},
			},
		},
	}
	providerSchema := &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"marker": {
						Type:     cty.String,
						Required: true,
					},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_thing": resourceTypeSchema,
		},
	}

	// Unlike most context tests, this one uses a real factory function so that
	// we can instantiate multiple instances and distinguish them.
	providerFactory := func() (providers.Interface, error) {
		// The following uses log.Printf instead of t.Logf so that the logs can interleave with the
		// verbose trace logs produced by the main logic in this package, to make the order of operations clearer.
		// To run just this test with trace logs:
		//   TF_LOG=trace go test ./internal/tofu -run '^TestContextImport_multiInstanceProviderConfig$'

		ret := &MockProvider{}
		var configuredMarker cty.Value
		log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: creating new instance of provider 'test' at %p", ret)

		ret.GetProviderSchemaResponse = providerSchema
		ret.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
			configuredMarker = req.Config.GetAttr("marker")
			log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: ConfigureProvider for %p with marker = %#v", ret, configuredMarker)
			return providers.ConfigureProviderResponse{}
		}
		ret.ImportResourceStateFn = func(req providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
			log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: ImportResourceState for %p with marker = %#v", ret, configuredMarker)
			if configuredMarker == cty.NilVal {
				return providers.ImportResourceStateResponse{
					Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("ImportResourceState before ConfigureProvider")),
				}
			}
			return providers.ImportResourceStateResponse{
				ImportedResources: []providers.ImportedResource{
					{
						TypeName: "test_thing",
						State: cty.ObjectVal(map[string]cty.Value{
							"id":             cty.StringVal(req.Target.ID),
							"import_marker":  configuredMarker,
							"refresh_marker": cty.NullVal(cty.String), // we'll populate this in ReadResource
						}),
					},
				},
			}
		}
		ret.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
			log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: ReadResource for %p with marker = %#v", ret, configuredMarker)
			if configuredMarker == cty.NilVal {
				return providers.ReadResourceResponse{
					Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("ReadResource before ConfigureProvider")),
				}
			}
			return providers.ReadResourceResponse{
				NewState: cty.ObjectVal(map[string]cty.Value{
					"id":             req.PriorState.GetAttr("id"),
					"import_marker":  req.PriorState.GetAttr("import_marker"),
					"refresh_marker": configuredMarker,
				}),
			}
		}
		return ret, nil
	}

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewBuiltInProvider("test"): providerFactory,
		}, nil),
	})

	existingInstanceKey := addrs.StringKey("foo")
	existingInstanceAddr := addrs.RootModuleInstance.ResourceInstance(
		addrs.ManagedResourceMode, "test_thing", "test", existingInstanceKey,
	)
	t.Logf("importing into %s, which should succeed because it's configured", existingInstanceAddr)
	log.Printf("[TRACE] TestContextImport_multiInstanceProviderConfig: importing into %s, which should succeed because it's configured", existingInstanceAddr)
	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: existingInstanceAddr,
					ID:   "fake-import-id",
				},
			},
		},
	})
	assertNoErrors(t, diags)

	resourceState := state.Resource(existingInstanceAddr.ContainingResource())

	if got, want := len(resourceState.Instances), 1; got != want {
		t.Errorf("unexpected number of instances %d; want %d", got, want)
	}

	instanceState := resourceState.Instances[existingInstanceKey]
	if instanceState == nil {
		t.Fatal("no instance with key \"foo\" in final state")
	}
	if got, want := instanceState.ProviderKey, addrs.StringKey("a"); got != want {
		t.Errorf("wrong provider key %s; want %s", got, want)
	}
	if instanceState.Current == nil {
		t.Fatal("final resource instance has no current object")
	}

	gotObjState, err := instanceState.Current.Decode(resourceTypeSchema.Block.ImpliedType())
	if err != nil {
		t.Fatalf("failed to decode final resource instance object state: %s", err)
	}
	wantObjState := &states.ResourceInstanceObject{
		Value: cty.ObjectVal(map[string]cty.Value{
			"id":             cty.StringVal("fake-import-id"),
			"import_marker":  cty.StringVal("a"),
			"refresh_marker": cty.StringVal("a"),
		}),
		Status:       states.ObjectReady,
		Dependencies: []addrs.ConfigResource{},
	}
	if diff := cmp.Diff(wantObjState, gotObjState, ctydebug.CmpOptions); diff != "" {
		t.Error("wrong final object state\n" + diff)
	}
}

func TestContextImport_importResourceWithSensitiveDataSource(t *testing.T) {
	p := testProvider("aws")
	m := testModuleInline(t, map[string]string{
		"main.tf": `
provider "aws" {
  foo = "bar"
}

data "aws_sensitive_data_source" "source" {
  id = "source_id"
}

resource "aws_instance" "foo" {
  id = "bar"
  var = data.aws_sensitive_data_source.source.value
}
`,
	})

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("bar"),
				}),
			},
		},
	}

	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
		State: cty.ObjectVal(map[string]cty.Value{
			"id":    cty.StringVal("source_id"),
			"value": cty.StringVal("pass"),
		}),
	}

	p.ReadResourceResponse = &providers.ReadResourceResponse{
		NewState: cty.ObjectVal(map[string]cty.Value{
			"id":  cty.StringVal("bar"),
			"var": cty.StringVal("pass"),
		}),
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportResourceWithSensitiveDataSource)
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}

	obj := state.ResourceInstance(mustResourceInstanceAddr("aws_instance.foo"))
	if len(obj.Current.AttrSensitivePaths) != 1 {
		t.Fatalf("Expected 1 sensitive mark for aws_instance.foo, got %#v\n", obj.Current.AttrSensitivePaths)
	}
}

func TestContextImport_collision(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-provider")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	state := states.BuildState(func(s *states.SyncState) {
		s.SetResourceInstanceCurrent(
			addrs.Resource{
				Mode: addrs.ManagedResourceMode,
				Type: "aws_instance",
				Name: "foo",
			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
			&states.ResourceInstanceObjectSrc{
				AttrsFlat: map[string]string{
					"id": "bar",
				},
				Status: states.ObjectReady,
			},
			addrs.AbsProviderConfig{
				Provider: addrs.NewDefaultProvider("aws"),
				Module:   addrs.RootModule,
			},
			addrs.NoKey,
		)
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	state, diags := ctx.Import(context.Background(), m, state, &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if !diags.HasErrors() {
		t.Fatalf("succeeded; want an error indicating that the resource already exists in state")
	}

	actual := strings.TrimSpace(state.String())
	expected := `aws_instance.foo:
  ID = bar
  provider = provider["registry.opentofu.org/hashicorp/aws"]`

	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_missingType(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-provider")

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if !diags.HasErrors() {
		t.Fatal("should error")
	}

	actual := strings.TrimSpace(state.String())
	expected := "<no state>"
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_moduleProvider(t *testing.T) {
	p := testProvider("aws")

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
		foo := req.Config.GetAttr("foo").AsString()
		if foo != "bar" {
			resp.Diagnostics = resp.Diagnostics.Append(errors.New("not bar"))
		}

		return
	}

	m := testModule(t, "import-provider")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	if !p.ConfigureProviderCalled {
		t.Fatal("didn't configure provider")
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportStr)
	if actual != expected {
		t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual)
	}
}

// Importing into a module requires a provider config in that module.
func TestContextImport_providerModule(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-module")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
		foo := req.Config.GetAttr("foo").AsString()
		if foo != "bar" {
			resp.Diagnostics = resp.Diagnostics.Append(errors.New("not bar"))
		}

		return
	}

	_, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	if !p.ConfigureProviderCalled {
		t.Fatal("didn't configure provider")
	}
}

// Test that import will interpolate provider configuration and use
// that configuration for import.
func TestContextImport_providerConfig(t *testing.T) {
	testCases := map[string]struct {
		module string
		value  string
	}{
		"variables": {
			module: "import-provider-vars",
			value:  "bar",
		},
		"locals": {
			module: "import-provider-locals",
			value:  "baz-bar",
		},
	}
	for name, test := range testCases {
		t.Run(name, func(t *testing.T) {
			p := testProvider("aws")
			m := testModule(t, test.module)
			ctx := testContext2(t, &ContextOpts{
				Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
					addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
				}, nil),
			})

			p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
				ImportedResources: []providers.ImportedResource{
					{
						TypeName: "aws_instance",
						State: cty.ObjectVal(map[string]cty.Value{
							"id": cty.StringVal("foo"),
						}),
					},
				},
			}

			state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
				Targets: []*ImportTarget{
					{
						CommandLineImportTarget: &CommandLineImportTarget{
							Addr: addrs.RootModuleInstance.ResourceInstance(
								addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
							),
							ID: "bar",
						},
					},
				},
				SetVariables: InputValues{
					"foo": &InputValue{
						Value:      cty.StringVal("bar"),
						SourceType: ValueFromCaller,
					},
				},
			})
			if diags.HasErrors() {
				t.Fatalf("unexpected errors: %s", diags.Err())
			}

			if !p.ConfigureProviderCalled {
				t.Fatal("didn't configure provider")
			}

			if foo := p.ConfigureProviderRequest.Config.GetAttr("foo").AsString(); foo != test.value {
				t.Fatalf("bad value %#v; want %#v", foo, test.value)
			}

			actual := strings.TrimSpace(state.String())
			expected := strings.TrimSpace(testImportStr)
			if actual != expected {
				t.Fatalf("bad: \n%s", actual)
			}
		})
	}
}

// Test that provider configs can't reference resources.
func TestContextImport_providerConfigResources(t *testing.T) {
	p := testProvider("aws")
	pTest := testProvider("test")
	m := testModule(t, "import-provider-resources")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"):  testProviderFuncFixed(p),
			addrs.NewDefaultProvider("test"): testProviderFuncFixed(pTest),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	_, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if !diags.HasErrors() {
		t.Fatal("should error")
	}
	if got, want := diags.Err().Error(), `The configuration for provider["registry.opentofu.org/hashicorp/aws"] depends on values that cannot be determined until apply.`; !strings.Contains(got, want) {
		t.Errorf("wrong error\n got: %s\nwant: %s", got, want)
	}
}

func TestContextImport_refresh(t *testing.T) {
	p := testProvider("aws")
	m := testModuleInline(t, map[string]string{
		"main.tf": `
provider "aws" {
  foo = "bar"
}

resource "aws_instance" "foo" {
}


// we are only importing aws_instance.foo, so these resources will be unknown
resource "aws_instance" "bar" {
}
data "aws_data_source" "bar" {
  foo = aws_instance.bar.id
}
`,
	})

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
		State: cty.ObjectVal(map[string]cty.Value{
			"id":  cty.StringVal("id"),
			"foo": cty.UnknownVal(cty.String),
		}),
	}

	p.ReadResourceFn = nil

	p.ReadResourceResponse = &providers.ReadResourceResponse{
		NewState: cty.ObjectVal(map[string]cty.Value{
			"id":  cty.StringVal("foo"),
			"foo": cty.StringVal("bar"),
		}),
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	if d := state.ResourceInstance(mustResourceInstanceAddr("data.aws_data_source.bar")); d != nil {
		t.Errorf("data.aws_data_source.bar has a status of ObjectPlanned and should not be in the state\ngot:%#v\n", d.Current)
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportRefreshStr)
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_refreshNil(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-provider")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
		return providers.ReadResourceResponse{
			NewState: cty.NullVal(cty.DynamicPseudoType),
		}
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if !diags.HasErrors() {
		t.Fatal("should error")
	}

	actual := strings.TrimSpace(state.String())
	expected := "<no state>"
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_module(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-module")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportModuleStr)
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_moduleDepth2(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-module")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).Child("nested", addrs.NoKey).ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "baz",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportModuleDepth2Str)
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_moduleDiff(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-module")
	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.Child("child", addrs.IntKey(0)).ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "baz",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportModuleStr)
	if actual != expected {
		t.Fatalf("\nexpected: %q\ngot:      %q\n", expected, actual)
	}
}

func TestContextImport_multiState(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-provider")

	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
		Provider: &configschema.Block{
			Attributes: map[string]*configschema.Attribute{
				"foo": {Type: cty.String, Optional: true},
			},
		},
		ResourceTypes: map[string]*configschema.Block{
			"aws_instance": {
				Attributes: map[string]*configschema.Attribute{
					"id": {Type: cty.String, Computed: true},
				},
			},
			"aws_instance_thing": {
				Attributes: map[string]*configschema.Attribute{
					"id": {Type: cty.String, Computed: true},
				},
			},
		},
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
			{
				TypeName: "aws_instance_thing",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("bar"),
				}),
			},
		},
	}

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportMultiStr)
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_multiStateSame(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "import-provider")

	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
		Provider: &configschema.Block{
			Attributes: map[string]*configschema.Attribute{
				"foo": {Type: cty.String, Optional: true},
			},
		},
		ResourceTypes: map[string]*configschema.Block{
			"aws_instance": {
				Attributes: map[string]*configschema.Attribute{
					"id": {Type: cty.String, Computed: true},
				},
			},
			"aws_instance_thing": {
				Attributes: map[string]*configschema.Attribute{
					"id": {Type: cty.String, Computed: true},
				},
			},
		},
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
			{
				TypeName: "aws_instance_thing",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("bar"),
				}),
			},
			{
				TypeName: "aws_instance_thing",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("qux"),
				}),
			},
		},
	}

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}

	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportMultiSameStr)
	if actual != expected {
		t.Fatalf("bad: \n%s", actual)
	}
}

func TestContextImport_nestedModuleImport(t *testing.T) {
	p := testProvider("aws")
	m := testModuleInline(t, map[string]string{
		"main.tf": `
locals {
  xs = toset(["foo"])
}

module "a" {
  for_each = local.xs
  source   = "./a"
}

module "b" {
  for_each = local.xs
  source   = "./b"
  y = module.a[each.key].y
}

resource "test_resource" "test" {
}
`,
		"a/main.tf": `
output "y" {
  value = "bar"
}
`,
		"b/main.tf": `
variable "y" {
  type = string
}

resource "test_resource" "unused" {
  value = var.y
  // missing required, but should not error
}
`,
	})

	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
		Provider: &configschema.Block{
			Attributes: map[string]*configschema.Attribute{
				"foo": {Type: cty.String, Optional: true},
			},
		},
		ResourceTypes: map[string]*configschema.Block{
			"test_resource": {
				Attributes: map[string]*configschema.Attribute{
					"id":       {Type: cty.String, Computed: true},
					"required": {Type: cty.String, Required: true},
				},
			},
		},
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_resource",
				State: cty.ObjectVal(map[string]cty.Value{
					"id":       cty.StringVal("test"),
					"required": cty.StringVal("value"),
				}),
			},
		},
	}

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
		}, nil),
	})

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "test_resource", "test", addrs.NoKey,
					),
					ID: "test",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatal(diags.ErrWithWarnings())
	}

	ri := state.ResourceInstance(mustResourceInstanceAddr("test_resource.test"))
	expected := `{"id":"test","required":"value"}`
	if ri == nil || ri.Current == nil {
		t.Fatal("no state is recorded for resource instance test_resource.test")
	}
	if string(ri.Current.AttrsJSON) != expected {
		t.Fatalf("expected %q, got %q\n", expected, ri.Current.AttrsJSON)
	}
}

// New resources in the config during import won't exist for evaluation
// purposes (until import is upgraded to using a complete plan). This means
// that references to them are unknown, but in the case of single instances, we
// can at least know the type of unknown value.
func TestContextImport_newResourceUnknown(t *testing.T) {
	p := testProvider("aws")
	m := testModuleInline(t, map[string]string{
		"main.tf": `
resource "test_resource" "one" {
}

resource "test_resource" "two" {
  count = length(flatten([test_resource.one.id]))
}

resource "test_resource" "test" {
}
`,
	})

	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
		ResourceTypes: map[string]*configschema.Block{
			"test_resource": {
				Attributes: map[string]*configschema.Attribute{
					"id": {Type: cty.String, Computed: true},
				},
			},
		},
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "test_resource",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("test"),
				}),
			},
		},
	}

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
		}, nil),
	})

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "test_resource", "test", addrs.NoKey,
					),
					ID: "test",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatal(diags.ErrWithWarnings())
	}

	ri := state.ResourceInstance(mustResourceInstanceAddr("test_resource.test"))
	expected := `{"id":"test"}`
	if ri == nil || ri.Current == nil {
		t.Fatal("no state is recorded for resource instance test_resource.test")
	}
	if string(ri.Current.AttrsJSON) != expected {
		t.Fatalf("expected %q, got %q\n", expected, ri.Current.AttrsJSON)
	}
}

func TestContextImport_33572(t *testing.T) {
	p := testProvider("aws")
	m := testModule(t, "issue-33572")

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
		}, nil),
	})

	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
		ImportedResources: []providers.ImportedResource{
			{
				TypeName: "aws_instance",
				State: cty.ObjectVal(map[string]cty.Value{
					"id": cty.StringVal("foo"),
				}),
			},
		},
	}

	state, diags := ctx.Import(context.Background(), m, states.NewState(), &ImportOpts{
		Targets: []*ImportTarget{
			{
				CommandLineImportTarget: &CommandLineImportTarget{
					Addr: addrs.RootModuleInstance.ResourceInstance(
						addrs.ManagedResourceMode, "aws_instance", "foo", addrs.NoKey,
					),
					ID: "bar",
				},
			},
		},
	})
	if diags.HasErrors() {
		t.Fatalf("unexpected errors: %s", diags.Err())
	}
	actual := strings.TrimSpace(state.String())
	expected := strings.TrimSpace(testImportStrWithDataSource)
	if diff := cmp.Diff(actual, expected); len(diff) > 0 {
		t.Fatalf("wrong final state\ngot:\n%s\nwant:\n%s\ndiff:\n%s", actual, expected, diff)
	}
}

const testImportStr = `
aws_instance.foo:
  ID = foo
  provider = provider["registry.opentofu.org/hashicorp/aws"]
`

const testImportStrWithDataSource = `
data.aws_data_source.bar:
  ID = baz
  provider = provider["registry.opentofu.org/hashicorp/aws"]
aws_instance.foo:
  ID = foo
  provider = provider["registry.opentofu.org/hashicorp/aws"]
`

const testImportCountIndexStr = `
aws_instance.foo.0:
  ID = foo
  provider = provider["registry.opentofu.org/hashicorp/aws"]
`

const testImportResourceWithSensitiveDataSource = `
data.aws_sensitive_data_source.source:
  ID = source_id
  provider = provider["registry.opentofu.org/hashicorp/aws"]
  value = pass
aws_instance.foo:
  ID = bar
  provider = provider["registry.opentofu.org/hashicorp/aws"]
  var = pass
`

const testImportModuleStr = `
<no state>
module.child[0]:
  aws_instance.foo:
    ID = foo
    provider = provider["registry.opentofu.org/hashicorp/aws"]
`

const testImportModuleDepth2Str = `
<no state>
module.child[0].nested:
  aws_instance.foo:
    ID = foo
    provider = provider["registry.opentofu.org/hashicorp/aws"]
`

const testImportMultiStr = `
aws_instance.foo:
  ID = foo
  provider = provider["registry.opentofu.org/hashicorp/aws"]
aws_instance_thing.foo:
  ID = bar
  provider = provider["registry.opentofu.org/hashicorp/aws"]
`

const testImportMultiSameStr = `
aws_instance.foo:
  ID = foo
  provider = provider["registry.opentofu.org/hashicorp/aws"]
aws_instance_thing.foo:
  ID = bar
  provider = provider["registry.opentofu.org/hashicorp/aws"]
aws_instance_thing.foo-1:
  ID = qux
  provider = provider["registry.opentofu.org/hashicorp/aws"]
`

func TestContextImport_multiInstanceProviderIdentity(t *testing.T) {
	// This test verifies that identity-based import blocks correctly resolve
	// the provider instance key when the provider uses for_each and also an alias.

	m := testModuleInline(t, map[string]string{
		"main.tf": `
			terraform {
				required_providers {
					test = {
						source = "terraform.io/builtin/test"
					}
				}
			}

			provider "test" {
				alias = "multi"
				for_each = {
					a = {}
					b = {}
				}

				marker = each.key
			}

			resource "test_thing" "test" {
				for_each = { "foo" = "a" }
				provider = test.multi[each.value]
			}

			import {
				to       = test_thing.test["foo"]
				identity = {
					name = "my-resource"
				}
				provider = test.multi["a"]
			}
		`,
	})

	identitySchemaObj := &configschema.Object{
		Attributes: map[string]*configschema.Attribute{
			"name": {Type: cty.String, Required: true},
		},
		Nesting: configschema.NestingSingle,
	}
	resourceTypeSchema := providers.Schema{
		Block: &configschema.Block{
			Attributes: map[string]*configschema.Attribute{
				"id": {
					Type:     cty.String,
					Computed: true,
				},
				"name": {
					Type:     cty.String,
					Computed: true,
				},
			},
		},
		IdentitySchema:        identitySchemaObj,
		IdentitySchemaVersion: 1,
	}
	providerSchema := &providers.GetProviderSchemaResponse{
		Provider: providers.Schema{
			Block: &configschema.Block{
				Attributes: map[string]*configschema.Attribute{
					"marker": {
						Type:     cty.String,
						Required: true,
					},
				},
			},
		},
		ResourceTypes: map[string]providers.Schema{
			"test_thing": resourceTypeSchema,
		},
	}

	providerFactory := func() (providers.Interface, error) {
		ret := &MockProvider{}
		var configuredMarker cty.Value

		log.Printf("[TRACE] TestContextImport_multiInstanceProviderIdentity: creating new instance of provider 'test' at %p", ret)

		ret.GetProviderSchemaResponse = providerSchema
		ret.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
			configuredMarker = req.Config.GetAttr("marker")
			log.Printf("[TRACE] TestContextImport_multiInstanceProviderIdentity: ConfigureProvider for %p with marker = %#v", ret, configuredMarker)
			return providers.ConfigureProviderResponse{}
		}
		ret.ImportResourceStateFn = func(req providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
			log.Printf("[TRACE] TestContextImport_multiInstanceProviderIdentity: ImportResourceState for %p with marker = %#v", ret, configuredMarker)
			if configuredMarker == cty.NilVal {
				return providers.ImportResourceStateResponse{
					Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("ImportResourceState before ConfigureProvider")),
				}
			}
			return providers.ImportResourceStateResponse{
				ImportedResources: []providers.ImportedResource{
					{
						TypeName: "test_thing",
						State: cty.ObjectVal(map[string]cty.Value{
							"id":   cty.StringVal("imported-123"),
							"name": configuredMarker,
						}),
					},
				},
			}
		}
		ret.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
			log.Printf("[TRACE] TestContextImport_multiInstanceProviderIdentity: ReadResource for %p with marker = %#v", ret, configuredMarker)
			if configuredMarker == cty.NilVal {
				return providers.ReadResourceResponse{
					Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("ReadResource before ConfigureProvider")),
				}
			}
			return providers.ReadResourceResponse{
				NewState: cty.ObjectVal(map[string]cty.Value{
					"id":   req.PriorState.GetAttr("id"),
					"name": req.PriorState.GetAttr("name"),
				}),
			}
		}
		return ret, nil
	}

	ctx := testContext2(t, &ContextOpts{
		Plugins: plugins.NewLibrary(map[addrs.Provider]providers.Factory{
			addrs.NewBuiltInProvider("test"): providerFactory,
		}, nil),
	})

	plan, diags := ctx.Plan(t.Context(), m, states.NewState(), DefaultPlanOpts)
	assertNoErrors(t, diags)

	if len(plan.Changes.Resources) == 0 {
		t.Fatal("expected import to create resource changes")
	}

	first := plan.Changes.Resources[0]
	if first.Importing == nil {
		t.Fatal("expected resource change to be an import")
	}

	identityType := identitySchemaObj.ImpliedType()
	decodedIdentity, err := first.Importing.Identity.Decode(identityType)
	if err != nil {
		t.Fatalf("failed to decode importing identity: %s", err)
	}

	expectedIdentity := cty.ObjectVal(map[string]cty.Value{
		"name": cty.StringVal("my-resource"),
	})

	if !decodedIdentity.RawEquals(expectedIdentity) {
		t.Errorf("unexpected imported identity:\ngot:  %#v\nwant: %#v", decodedIdentity, expectedIdentity)
	}
}

const testImportRefreshStr = `
aws_instance.foo:
  ID = foo
  provider = provider["registry.opentofu.org/hashicorp/aws"]
  foo = bar
`
